Spring 기반 Prometheus + Grafana를 이용한 모니터링 (3)

꾸준하게 달리기~·2023년 8월 9일
0

스프링 + 자바

목록 보기
12/20

들어가기 앞서

스프링 부트 액츄에이터의 메트릭, 마이크로미터의 프로메테우스 매트릭 수집을 위한 도움, 프로메테우스의 메트릭 수집, 그라파나 의 시각화까지.
지난 포스팅에서 서버에 들어오는 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();
    }
    
}




@Counted

위에서 작성한 요청과 취소처럼, 단조롭게 증가하는 단일 누적 항목에 대해서 사용한다.
하지만 누적 항목의 쿼리문을 바꿔주는 방법을 통해,
실무에서는 말 그대로 해당 매서드로 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"} 과 같은 쿼리를 대쉬보드에 추가하면
분당 취소가 각각 어느정도 호출되는지도 알 수 있다!




@Timed

시간 측정하는 메트릭 측정 도구이다.
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/

  • 로그 : 추적을 위해 자세하게 적기 + 사용자 한명의 로그는 보고 바로 알 수 있게 적용해야 한다.

profile
반갑습니다~! 좋은하루 보내세요 :)

1개의 댓글

comment-user-thumbnail
2023년 8월 9일

정리가 잘 된 글이네요. 도움이 됐습니다.

답글 달기