스프링 부트 액츄에이터의 메트릭, 마이크로미터의 프로메테우스 매트릭 수집을 위한 도움, 프로메테우스의 메트릭 수집, 그라파나 의 시각화까지.
지난 포스팅에서 서버에 들어오는 CPU, RAM의 부하, DB 커넥션 풀 관리, 로그 등등의
시스템 매트릭들에 대해 살펴보았다.
https://velog.io/@dlsrjsdl6505/spring-promethus-grafana%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%84%9C%EB%B2%84-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81
이번시간에는,
음.. 내 서비스중 어떤 요청이 서버에 가장 많이 들어올까? 와 같이, 커스텀으로 매트릭을 만들어서 사용하는 것,
즉 비즈니스 매트릭을 포스팅하려고 한다.
소스코드는 여기서 볼 수 있다.
https://github.com/ingeon2/actuator_grafana
public interface OrderService {
void request();
void cancel();
AtomicInteger getStock();
//멀티스레드 환경에서 안전하게 실행해주는 AtomicInteger
}
@Slf4j
@RestController
public class OrderController {
public final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/request")
public String request() {
log.info("request");
orderService.request();
return "request";
}
@GetMapping("/cancel")
public String cancel() {
log.info("cancel");
orderService.cancel();
return "cancel";
}
@GetMapping("/stock")
public int stock() {
log.info("stock");
orderService.getStock();
return orderService.getStock().get();
}
}
위에서 작성한 요청과 취소처럼, 단조롭게 증가하는 단일 누적 항목에 대해서 사용한다.
하지만 누적 항목의 쿼리문을 바꿔주는 방법을 통해,
실무에서는 말 그대로 해당 매서드로 HTTP 요청이 어느정도 들어왔는지 알 수 있다.
@Configuration
public class OrderConfigV1 {
@Bean
OrderService orderService() {
return new OrderServiceV1();
}
//여기 내용이 중요! @Counted를 AOP로 사용할 수 있게 Bean등록해줌
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
}
다음과 같이 Config를 구성하고,
@Slf4j
public class OrderServiceV1 implements OrderService{
private AtomicInteger stock = new AtomicInteger(100);
@Counted("my.request") //★메트릭 이름 지정★
@Override
public void request() { //매서드 이름은 태그네임.
log.info("요청");
stock.decrementAndGet();
}
@Counted("my.request")
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
해당 측정을 원하는 매서드에 (여기서는 요청 취소)
@Counted 애너테이션을 적용한다.
@Counted 애너테이션의 옆에 붙인 괄호 안의 내용은,
매트릭 이름을 지정한것이다.
또한 매서드 이름을 태그네임으로 사용가능하다.
즉,
actuator/metrics에 들어가면
위와 같이 내가 만든 my.request 메트릭이 생성되고,
직접 actuator/metrics/my.request 에 들어가보면
(COUNT : 2, method : request->cancel 순으로 총 두번 실행했다.)
(class : hello.order.OrderServiceV1)
어떤 매서드들이 차례대로 몇회 실행되었는지, 어디 클래스에서 실행되었는지 볼 수 있다.
마지막으로 태그를 사용하여 원하는 매서드로 들어가보자.
actuator/metrics/my.request?tag=method:request 에 들어가보면
my.request에서 내가 태그로 지정한 request 매서드의 정보까지 따로 볼 수 있다.
이제, 해당 매트릭을 프로메테우스와 그라파나를 통해 시각화하면,
쿼리 : my_request_total{method="request"}
위와 같이 누적되는 요청 횟수를 알 수 있다.
누적 횟수를 이제 1분당 요청 횟수로 바꾸어 보기 위해,
쿼리 : increase(my_request_total{method="request"}[1m])
쿼리문을 다음과 같이 바꾸고 실행하면,
위와 같이 1분당 요청이 들어온 수 또한 알 수 있다.
(기존에는 그저 누적되던 요청 수가, 1분당 얼마나 들어왔는지로 바뀜)
이렇게 내가 설정해준 매서드와 매트릭을 기반으로,
increase(my_request_total{method="cancel"}
과 같은 쿼리를 대쉬보드에 추가하면
분당 취소가 각각 어느정도 호출되는지도 알 수 있다!
시간 측정하는 메트릭 측정 도구이다.
Timer를 사용하면, 위에서의 Count와 함께 실행 시간 또한 측정할 수 있다.
어떤 요청에서 시간이 오래 걸리는지, 서버의 유지보수에 있어 중요한 요소중 하나이다.
아래는 액추에이터와 그라파나에서 사용될 쿼리이다.
second_count : 누적 실행 수 = @Count
second_sum : 실행 시간 합
second_max : 최대 실행 시간 (내부 로직에 따라 최근 몇분 시간동안에서의 매서드 최대 실행 시간.)
백문이 불여일타, 코드를 통해 확인하자!
@Timed 애너테이션을 사용하고 해당 애너테이션의 매트릭을 수집하기 위해, Service클래스와 Config를 작성했다.
(아까와 비슷하다! 설명은 주석으로 달아놓았다.)
@Timed(value = "my.request") //actuactor timer 기능을 사용하기 위해 클래스 단위로 붙여주는 애너테이션, 당연히 매서드 단위도 된다!
//이렇게 클래스단위로 지정하면, 해당 클래스의 모든 public 매서드들에 적용된다.
@Slf4j
public class OrderServiceV2 implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Counted("my.request")
@Override
public void request() {
log.info("요청");
stock.decrementAndGet();
sleep(500);
}
@Counted("my.request")
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
sleep(200);
}
@Override
public AtomicInteger getStock() {
return stock;
}
//매서드마다 실행 시간 다른것을 @Timed로 체크해야 하므로
//스레드를 강제로 지연시키기 위한 매서드 생성
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(200));
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public class OrderConfigV2 {
@Bean
OrderService orderService() {
return new OrderServiceV2();
}
//Timed AOP 기능을 위해 등록하는 Bean!
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
이렇게 만들어준 후,
다시 actuator/metrics/my.request 를 보면,
COUNT : 매서드가 총 몇번 호출되었는지
TOTAL_TIME : 총 몇초의 시간이 걸렸는지
MAX : 요청중 가장 오래걸린 시간은 몇초인지
이렇게 세가지 항목을 볼 수 있다.
@Countd 보다 더 많은 항목을 확인할 수 있다.
마찬가지로 그라파나에서 해당 매트릭의 내용을
increase(my_request_seconds_count{method="request"}[1m]) //1분당 요청 수
increase(my_request_seconds_count{method="cancel"}[1m]) //1분당 취소 수
을 통해 아래와 같이 분당 요청, 취소 수를 @Timed를 통해 알 수 있고,
my_request_seconds_max
을 통해 아래와 같이 매서드당 최대 실행 시간을 알 수 있다.
(method = "request" 이런식으로 보여진다)
마지막으로
increase(my_request_seconds_sum[1m]) / increase(my_request_seconds_count[1m])
라는 쿼리문을 통해, 요청이 회당 평균적으로 몇초가 걸리는지를 매서드별로 알 수 있다.
(마찬가지로 method = "request" 이런식으로 보여진다)
위와 같은 커스텀 매트릭을 통해
내 비즈니스로직 안에서
어떤 요청이 많이 들어오는지,
해당 요청의 단위시간에 대한 수,
어떤 요청이 가장 오래걸리는지,
어떤 요청이 평균적으로 어느정도의 시간이 걸리는지 등등을
비즈니스 매트릭으로 쿼리문의 조작을 통해 알아낼 수 있다.
해당 내용은 서버 유지, 보수에 있어 중요하다.
API개발도 중요하지만,
이미 만들어진 서버를 유지보수하는 업무 또한 굉장히 중요하므로
위의 내용을 잘 숙지해야 한다.
대쉬보드 를 통해 매트릭을 확인한다 (CPU, RAM, 서버에서 알아야 할 어떠한 요청에 대한 커스텀 매트릭, DB 커넥션 풀 등등..)
애플리케이션 추적 : MSA환경에서 분산 추적을 위해, 각각의 HTTP 요청을 추적 (MSA 환경에서의 요청은, 맨 처음 요청이 어딘지를 파악해야 하므로 - PinPoint 오픈소스 추천) https://github.com/pinpoint-apm/pinpoint/
로그 : 추적을 위해 자세하게 적기 + 사용자 한명의 로그는 보고 바로 알 수 있게 적용해야 한다.
정리가 잘 된 글이네요. 도움이 됐습니다.