SPRING/Spring Boot

[SpringBoot] 커스텀 메트릭(Metric) 등록하기 -Timer

IT록흐 2023. 5. 18. 12:01
반응형
 

[SpringBoot] 커스텀 메트릭(Metric) 등록하기

[SpringBoot] 메트릭(Metric) 이란? [SpringBoot] 모니터링 - 엑츄에이터(Actuator)(1) 개발이 완료되면 App을 모니터링해야 한다. ( Production-Ready ) App상태정보, 트래픽, DB상태정보 등등 다양한 모니터링 데이터

lordofkangs.tistory.com

 

이전 포스팅에서 예제프로그램을 만들어 보았다. 여기에 타이머(Timer) 개념을 추가해보겠다.

 

 

 

 

1) 카운터 ( Counter )

시간에 따라 '증가'만하는 데이터이다. 주문수는 항상 증가한다. 중간에 감소하지 않는다.

2) 게이지 ( Gauage )

시간에 따라 '증가'와 '감소'를 반복하는 데이터이다. 재고량은 쌓였다가 줄었다가 반복한다. 

3) 타이머 ( Timer )

시간 관련된 데이터이다.  실행횟수,실행시간의 합, 최장실행시간 등 시간과 관련된 정보를 제공한다. 

 

 

타이머(Timer)

 

MeterResistry는 데이터를 메트릭으로 등록하는 마이크로미터 모듈이다. MeterResistry는 스프링이 자동으로 IOC컨테이너에 Bean으로 등록한다. 우리는 의존관계 주입만 하면 사용할 수 있다. 

 

OrderConfigTimer 클래스

@Configuration
public class OrderConfigTimer {

    @Bean
    public OrderService orderService(MeterRegistry meterRegistry) {
        return new OrderServiceTimerV0(meterRegistry);
    }

}

 

OrderConfigTimer 클래스는 OrderServiceTimerV0 클래스를 Bean으로 등록하는 설정클래스이다. MeterRegistry는 Spring이 엑츄에이터 라이브러리를 읽고 자동등록한 Bean인데, 커스텀 메트릭을 등록할때 사용하는 클래스이다. 의존관계 주입으로 해당 Bean을 생성자파라미터로 넣어준다.

 

OrderServiceTimerV0 클래스 

 

타이머 메트릭 등록은 2가지로 나뉜다. 

 

1. Timer 메트릭 등록

2. Timer가 기록할 로직

 

order() 메소드가 호출되면 타이머 메트릭이 생성되고 order() 메소드 로직 실행 관련 시간정보를 수집 및 측정한다. sleep() 메소드는 로직 실행시간이 다양하게 측정되도록 임의로 넣은 것이다. 

 

@Slf4j
@RequiredArgsConstructor
public class OrderServiceTimerV0 implements OrderService {
    private final MeterRegistry meterRegistry; //메트릭 등록모듈
    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        //타이머 메트릭 등록하기
        Timer timer = Timer.builder("my.order")
                    .tag("class",this.getClass().getName())
                    .tag("method","order")
                    .description("order")
                    .register(meterRegistry);

        // 타이머 메트릭이 기록할 로직
        timer.record(()->{
            log.info("주문...");
            stock.decrementAndGet();
            sleep(500); // 측정시간 랜덤하게 설정하기
        });
    }
    @Override
    public void cancel() {

        Timer timer = Timer.builder("my.order")
                .tag("class",this.getClass().getName())
                .tag("method","cancel")
                .description("order")
                .register(meterRegistry);

        timer.record(()->{
            log.info("주문취소...");
            stock.incrementAndGet();
            sleep(200);
        });

    }
    @Override
    public AtomicInteger getStock() {
        return stock;
    }


    private void sleep(int time) {
        try {
            Thread.sleep( time + new Random().nextInt(200)); // 측정시간은 time과 time + 200ms 사이의 임의의 시간이다.
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

 

전체코드는 위와 같다. 그럼 프로젝트를 실행해보자.

 

DemoApplication 클래스

@Import(OrderConfigTimer.class)
@SpringBootApplication(scanBasePackages = "com.example.demo.controller")
public class DemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}
}

 

main 함수를 실행하고 아래 URL로 접근한다.

 

☑︎ http://localhost:8080/order

☑︎ http://localhost:8080/cancel

 

주문과 취소를 실행하고 정상작동되면 아래 URL로 이동하여 메트릭이 잘 생성되었는지 확인해본다.

 

☑︎ http://localhost:8080/actuator/metrics/my.order

 

// 20230518112310
// http://localhost:8080/actuator/metrics/my.order

{
  "name": "my.order",
  "description": "order",
  "baseUnit": "seconds",
  "measurements": [
    {
      "statistic": "COUNT",
      "value": 47.0
    },
    {
      "statistic": "TOTAL_TIME",
      "value": 20.902607671000002
    },
    {
      "statistic": "MAX",
      "value": 0.0
    }
  ],
  "availableTags": [
    {
      "tag": "method",
      "values": [
        "cancel",
        "order"
      ]
    },
    {
      "tag": "class",
      "values": [
        "com.example.demo.orderservice.timer.OrderServiceTimerV0"
      ]
    }
  ]
}

 

메트릭 데이터가 우리가 설정한대로 잘 생성되었다.

 

한 가지 리팩토링 할 부분이 있다. 타이머 메트릭을 등록하고 기록하는 부분의 코드가 반복된다. Spring은 AOP 개념을 이용하여 코드를 단순화할 수 있도록 지원한다.

 

 

AOP로 리팩토링하기

 

 

 

 

 

TimeAspect 클래스는 MeterRegistry를 주입받아 @Timed 어노테이션으로 표시된 메소드를 메트릭으로 자동등록한다. 개발자가 MeterRegistry에 직접접근하여 등록하지 않고 자동화 모듈 하나를 두는 방식이다.

 

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

build.gradle의 AOP 디펜던시 하나를 추가한다.

 

OrderConfigTimer 클래스

@Configuration
public class OrderConfigTimer {
    @Bean
    OrderService orderService() {
        return new OrderServiceTimerV1();
    }
    @Bean // TimedAspect Bean으로 등록
    TimedAspect timedAspect(MeterRegistry meterRegistry){
        return new TimedAspect(meterRegistry);
    }
}

 

TimedAspect 클래스를 MeterRegistry 객체를 주입받아 Beand으로 생성한다. OrderServiceTimerV1은 @Timed로 메트릭을 등록한다. 

 

OrderServiceTimerV1 클래스

@Slf4j
public class OrderServiceTimerV1 implements OrderService {
    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    @Timed("my.order") //어노테이션 등록
    public void order() {
        log.info("주문...");
        stock.decrementAndGet();
        sleep(500); // 측정시간 랜덤하게 설정하기
    }
    @Override
    @Timed("my.order")
    public void cancel() {
        log.info("주문취소...");
        stock.incrementAndGet();
        sleep(200);
    }
    @Override
    public AtomicInteger getStock() {
        return stock;
    }


    private void sleep(int time) {
        try {
            Thread.sleep( time + new Random().nextInt(200)); // 측정시간은 time과 time + 200ms 사이의 임의의 시간이다.
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

 

 

코드가 한결 깔끔해졌다.

 

그럼 제대로 실행되는지 확인해보자.

 

☑︎ http://localhost:8080/order

☑︎ http://localhost:8080/cancel

 

주문과 취소를 실행하고 정상작동되면 아래 URL로 이동하여 메트릭이 잘 생성되었는지 확인해본다.

 

☑︎ http://localhost:8080/actuator/metrics/my.order

 

JSON 데이터가 잘 뜨면 메트릭등록이 완료됨을 의미한다.

 

 

프로메테우스, 그라파나 연동하기

 

프로메테우스 서버를 실행하고 SpringBoot 프로젝트와 연동하였다.

 

[Prometheus] 프로메테우스 연동하기 ( With SpringBoot )

프로메테우스(Prometheus)란? 시간이 지남에 따라 추이가 변하는 데이터를 메트릭(Metric)이라고 한다. CPU사용량, 메모리 사용량 등이 메트릭에 해당한다. SpringBoot는 Metric 수집을 마이크로미터(MicroMet

lordofkangs.tistory.com

위 그림과 같이,

spring-actuator Endpoint의 status가 up이면 정상적으로 연동이 완료된 것이다.

그라파나 서버를 실행하고 Prometheus와 연동하였다.

 

 

[Grafana] 그라파나 연동하기 ( With SpringBoot, Prometheus )

그라파나(Grafana)란? 시간이 지남에 따라 추이가 변하는 데이터를 메트릭(Metric)이라 부른다. CPU 사용률, 메모리 사용률, 트래픽 등이 메트릭(Metric)에 해당된다. 메트릭은 시간별로 데이터가 수집

lordofkangs.tistory.com

 

그라파나는 프로메테우스에 쿼리를 보내 데이터를 가져온다.

 

▹ 주문기능 평균실행시간 추이 쿼리 : my_order_seconds_sum/my_order_seconds_count{method="order"}

▹ 취소기능 평균실행시간 추이 쿼리 : my_order_seconds_sum/my_order_seconds_count{method="cancel"}

 

주문 기능과 취소 기능의  평균실행시간이 어떻게 변화하는지 추이를 그래프로 보여준다. 이를 통해, 특정기능의 지연여부를 시각화할 수 있다. 주문기능과 취소기능을 연달아 수행해보자.

 

▹ 주문수행 : http://localhost:8080/order

▹ 취소수행 : http://localhost:8080/cancel 

 

 

주문기능은 0.5~0.7초 사이로, 취소기능은 0.2~0.4초 사이로 설정하여 평균실행시간이 위 그림과 같이 시각회되었다. 이렇게 커스텀 메트릭도 데이터가 시각화를 완료하였다. 마이크로미터가 제공하는 기본 메트릭이 아닌 커스텀 메트릭도 마이크로미터에 등록할 수 있다.

 

 

 

반응형