마이크로미터 기능을 제공하는 핵심 컴포넌트
스프링을 통해서 주입 받아서 사용하고, 이곳을 통해서 카운터, 게이지 등을 등록한다.
@Slf4j
public class OrderServiceV1 implements OrderService {
private final MeterRegistry registry;
private AtomicInteger stock = new AtomicInteger(100);
public OrderServiceV1(MeterRegistry registry) {
this.registry = registry;
}
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
Counter.builder("my.order")
.tag("class", this.getClass().getName())
.tag("method", "order")
.description("order")
.register(registry).increment();
}
...
앞서 만든 OrderServiceV1
의 가장 큰 단점은 메트릭을 관리하는 로직이 핵심 비즈니스 개발 로직에 침투했다는 점이다. 이런 부분을 분리하려면 어떻게 해야할까? 바로 스프링 AOP를 사용하면 된다.
@Slf4j
public class OrderServiceV2 implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Counted("my.order")
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
}
...
tag
에 method
를 기준으로 분류해서 적용한다.@Configuration
public class OrderConfigV2 {
@Bean
public OrderService orderService() {
return new OrderServiceV2();
}
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
...
CountedAspect
를 등록하면 @Counted
를 인지해서 Counter
를 사용하는 AOP를 적용한다.@Counted
를 사용하면 result
, exception
, method
, class
같은 다양한 tag 를 자동으로 적용한다.
시간을 측정하는데 사용된다.
Timer
를 사용하면 실행 시간도 함께 측정seconds_count
: 누적 실행 수 - 카운터
seconds_sum
: 실행 시간의 합 - sum
seconds_max
: 최대 실행 시간(가장 오래걸린 실행 시간) - 게이지
@Slf4j
public class OrderServiceV3 implements OrderService {
private final MeterRegistry registry;
private AtomicInteger stock = new AtomicInteger(100);
public OrderServiceV3(MeterRegistry registry) {
this.registry = registry;
}
@Override
public void order() {
Timer timer = Timer.builder("my.order")
.tag("class", this.getClass().getName())
.tag("method", "order")
.description("order")
.register(registry);
timer.record(() -> {
log.info("주문");
stock.decrementAndGet();
sleep(500);
});
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
...
타이머는 @Timed
라는 애노테이션을 통해 AOP를 적용할 수 있다.
@Timed("my.order")
@Slf4j
public class OrderServiceV4 implements OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Override
public void order() {
log.info("주문");
stock.decrementAndGet();
sleep(500);
}
private static void sleep(int l) {
try {
Thread.sleep(l + new Random().nextInt(200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
...
@Timed("my.order")
타입이나 메서드 중에 적용할 수 있다. 타입에 적용하면 해당 타입의 모든 public 메서드에 타이머가 적용된다. 참고로 이 경우 getStock()
에도 타이머가 적용된다카운터와 게이지를 구분할 때는 값이 감소할 수 있는가를 생각하면 된다.
@Configuration
public class StockConfigV1 {
@Bean
public MyStockMetric myStockMetric(OrderService rderService, MeterRegistry
egistry) {
return new MyStockMetric(orderService, registry);
}
@Slf4j
static class MyStockMetric {
private OrderService orderService;
private MeterRegistry registry;
public MyStockMetric(OrderService orderService, MeterRegistry registry)
{
this.orderService = orderService;
this.registry = registry;
}
@PostConstruct
public void init() {
Gauge.builder("my.stock", orderService, service -> {
log.info("stock gauge call");
return service.getStock().get();
}).register(registry);
}
}
}
@Slf4j
@Configuration
public class StockConfigV2 {
@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);
}
}
카디널리티가 높다면 로그를 사용하자!
전체를 한눈에 볼 수 있는 가장 윗 단계의 뷰
제품
마이크로미터, 프로메테우스, 그라파나 등등
모니터링 대상
시스템 메트릭(CPU, 메모리)
애플리케이션 메트릭(톰캣 쓰레드 풀, DB 커넥션 풀, 애플리케이션 호출 수)
비즈니스 메트릭(주문수, 취소수)
주로 각각의 HTTP 요청을 추적, 일부는 마이크로서비스 환경에서 분산 추적
제품
핀포인트(오픈소스), 스카우트(오픈소스), 와탭(상용), 제니퍼(상용)
파일로 직접 로그를 남기는 경우
클라우드에 로그를 저장하는 경우
모니터링 툴에서 일정 이상 수치가 넘어가면, 슬랙, 문자 등을 연동
알람은 2가지 종류로 꼭 구분해서 관리
예)