≣ 목차
이번 포스팅에서는 간단한 주문 관련 메트릭을 구현하는 방법에 대해 알아보겠습니다.
스프링 엑추에이터, 프로메테우스, 그라파나의 기초 설정에 대해서는 이미 알고 있다고 가정하겠습니다.
카운터란?
카운터는 단순하게 증가하는 단일 누적 측정 항목이며, 일반적으로 전체 값을 의미합니다. 이 측정 항목은 값이 증가하거나 0으로 초기화하는 기능만 가능하고, 마이크로미터에서는 값을 감소시키는 기능도 지원하지만 일반적으로 감소시키는 기능을 사용하는 것은 이러한 목적에는 적합하지 않습니다. 예를 들어 HTTP 요청 수를 카운터로 사용하는데 일반적으로 HTTP 요청 수를 감소 하는 일은 없습니다.
직접 카운터 생성
@Slf4j
@Service
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
private final MeterRegistry meterRegistry;
public OrderService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
Counter.builder("my.order")
.tag("class", this.getClass().getName())
.tag("method", "order")
.description("order")
.register(meterRegistry).increment();
}
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
Counter.builder("my.order")
.tag("class", this.getClass().getName())
.tag("method", "cancel")
.description("order")
.register(meterRegistry).increment();
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
- build(): 기초적인 카운터를 구성하며, 해당 메트릭에 이름을 짓는 메서드
- tag(): 메트릭에 추가적인 정보를 제공하기 위해 key-value 쌍으로 레이블을 설정하는 메서드
- description(): 메트릭의 설명을 설정하여 해당 메트릭이 무엇을 나타내는지 설명하는 메서드
- register(): 만든 카운터를 MeterRegistry에 등록해주는 메소드
- increment() : 카운터의 값을 하나 증가하는 메소드
현재 MeterRegistry는 프로메테우스 구현체이기 때문에 MeterRegistry == Prometheus라고 생각해도 됩니다. 그리고 Prometheus에서는 카운터 이름의 끝에 _total을 붙입니다. 따라서, 해당 카운터의 메트릭 이름은 my_order_total이 됩니다.
@Counted 활용
메트릭을 직접 생성하는 공통 로직을 애노테이션으로 개발할 수 있다는 점은 다들 알고 계실겁니다. 그렇다고 이를 직접 구현할 필요는 없습니다. 왜냐하면 스프링에서는 이미 이러한 기능을 제공하는 애노테이션이 존재하기 때문입니다.
@Slf4j
@Service
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Counted("my.order")
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
}
@Counted("my.order")
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
@Counted 애노테이션을 사용하면 카운터 관련 메트릭을 쉽게 적용할 수 있습니다. 이 메트릭은 메서드 이름을 통해 구분되므로, 각 메서드의 호출 횟수를 명확하게 추적할 수 있습니다.
특히, 직접 카운터 생성 코드와의 차별점으로는 예외(exception)와 결과(result) 레이블이 추가된다는 점입니다. 이를 통해 메서드 실행 시 발생하는 예외나 반환 결과에 따라 카운터를 세분화할 수 있습니다.
Timer란?
Timer는 특별한 메트릭 측정 도구로, 주로 실행 시간을 측정하는 데 사용됩니다. 카운터와 유사하지만, Timer는 실행 시간도 함께 기록할 수 있는 점이 특징입니다.
직접 Timer 생성
@Slf4j
@Service
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
private final MeterRegistry meterRegistry;
public OrderService(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@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(500);
});
}
@Override
public AtomicInteger getStock() {
return stock;
}
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(2000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
record() 메소드를 사용하지 않으면 Timer는 카운터와 거의 동일하다고 생각하시면 됩니다. record() 메소드는 내부에 있는 코드의 실행 시간을 측정하는 역할을 합니다. 즉, record()를 통해 특정 코드 블록의 실행 시간을 기록하고, 이를 통해 정확한 성능 분석이 가능합니다.
@Timed 활용
해당 메트릭도 이미 스프링이 구현해놨습니다.
@Timed("my.order")
@Slf4j
@Service
public class OrderService implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
sleep(500);
}
@Override
public void cancel() {
log.info("취소");
stock.incrementAndGet();
sleep(200);
}
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public AtomicInteger getStock() {
return stock;
}
}
@Timed는 클래스나 메서드에 적용할 수 있으며, 클래스에 적용할 경우 해당 클래스의 모든 public 메서드에 자동으로 @Timed가 적용됩니다. 또한, 메서드 이름과 레이블을 통해 각 메서드를 구분할 수 있습니다.
Gauge
게이지는 임의로 오르내릴 수 있는 값으로, 현재 상태를 파악하는 데 사용됩니다. 카운터와 큰 차이점은 값이 감소할 수 있다는 점입니다. 보통 해당 차이점으로 게이지를 사용할 지 카운터를 사용할 지 구분합니다.
직접 게이지 생성 및 등록 - AtomicInteger 활용
@Slf4j
@Configuration
public class MyGaugeConfig {
@Bean
public MeterBinder stockSize(OrderService orderService) {
return registry -> Gauge.builder("my.stock", orderService, service -> {
log.info("stock gauge call");
return service.getStock().get();
}).register(registry);
}
}
해당 코드는 스프링의 MeterBinder를 사용하여 게이지(Gauge) 메트릭을 등록하는 코드입니다. @Configuration과 @Bean을 통해 MeterBinder를 빈으로 등록하며, Gauge.builder를 사용해 "my.stock"이라는 이름의 게이지를 생성하고 orderService를 통해 재고 수치(Gauge)를 가져옵니다.
그리고 register(registry)에서 registry가 전달될 수 있는 이유는 MeterBinder 인터페이스의 bindTo(MeterRegistry registry 메서드 덕분입니다. MeterBinder는 반드시 이 메서드를 구현해야 하며, 이때 MeterRegistry가 매개변수로 주어집니다. 따라서 코드에서 register(registry)의 registry는, MeterBinder가 bindTo 메서드를 실행할 때 MeterRegistry를 매개변수로 받기 때문에 자연스럽게 사용할 수 있습니다.
'Spring' 카테고리의 다른 글
[Spring Data JPA] Custom Repository - 사용자 정의 리포지토리 (0) | 2025.01.13 |
---|---|
[Spring Data JPA] 페이징과 정렬 (1) | 2025.01.03 |
[Actuator + Prometheus + Grafana] 모니터링 간단한 구현 #1 (0) | 2024.10.20 |
[Spring Boot] micrometer의 역할 (시스템 모니터링) (0) | 2024.10.18 |
[Spring Boot] 액추에이터(Actuator) 문제 및 해결 방법 (2) | 2024.10.16 |