섹션 10 : 모니터링 메트릭 활용

최용석·2024년 2월 2일
0

메트릭 등록1 - 카운터

MeterRegistry

마이크로미터 기능을 제공하는 핵심 컴포넌트
스프링을 통해서 주입 받아서 사용하고, 이곳을 통해서 카운터, 게이지 등을 등록한다.

Counter(카운터)

  • 단조롭게 증가하는 단일 누적 측정항목
    • 단일 값
    • 보통 하나씩 증가
    • 누적이므로 전체 값을 포함(total)
    • 프로메테우스에서는 일반적으로 카운터의 이름 마지막에 _total 을 붙여서 my_order_total 과 같이 표현함
  • 값을 증가하거나 0으로 초기화 하는 것만 가능
  • 마이크로미터에서 값을 감소하는 기능도 지원하지만, 목적에 맞지 않음
  • 예) HTTP 요청수
@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();
 }
 ...

메트릭 등록2 - @Counted

앞서 만든 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();
	}
...
  • tagmethod 를 기준으로 분류해서 적용한다.
@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를 적용한다.
  • CountedAspect를 빈으로 등록하지 않으면 @Counted 관련 AOP가 동작하지 않는다.

@Counted 를 사용하면 result , exception , method , class 같은 다양한 tag 를 자동으로 적용한다.

메트릭 등록3 - Timer

Timer

시간을 측정하는데 사용된다.

  • 카운터와 유사, Timer를 사용하면 실행 시간도 함께 측정
    • seconds_count: 누적 실행 수 - 카운터
    • seconds_sum: 실행 시간의 합 - sum
    • seconds_max: 최대 실행 시간(가장 오래걸린 실행 시간) - 게이지
      • 내부에 타임 윈도우라는 개념이 있어서 1~3분 마다 최대 실행 시간이 다시 계산된다.
@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);
		}
	}
...

메트릭 등록4 - @Timed

타이머는 @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() 에도 타이머가 적용된다

메트릭 등록5 - 게이지

Gauge(게이지)

  • 게이지는 임의로 오르내릴 수 있는 단일 숫자 값을 나타내는 메트릭
  • 값의 현재 상태를 보는데 사용
  • 값이 증가하거나 감소할 수 있음
  • 예) 차량의 속도, CPU 사용량, 메모리 사용량

카운터와 게이지를 구분할 때는 값이 감소할 수 있는가를 생각하면 된다.

@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);
	}
}

Tag, 레이블

  • Tag를 사용하면 데이터를 나누어서 확인할 수 있다.
  • Tag는 카디널리티가 낮으면서 그룹화 할 수 있는 단위에 사용해야 한다.
    • 예) 성별, 주문 상태, 결제 수단[신용카드, 현금] 등등
  • 카디널리티가 높으면 안된다.
    • 예) 주문번호, PK 같은 것

카디널리티가 높다면 로그를 사용하자!

실무 모니터링 환경 팁

모니터링 3단계

  • 대시보드
  • 애플리케이션 추적 - 핀포인트
  • 로그

대시보드

전체를 한눈에 볼 수 있는 가장 윗 단계의 뷰

제품
마이크로미터, 프로메테우스, 그라파나 등등

모니터링 대상
시스템 메트릭(CPU, 메모리)
애플리케이션 메트릭(톰캣 쓰레드 풀, DB 커넥션 풀, 애플리케이션 호출 수)
비즈니스 메트릭(주문수, 취소수)

애플리케이션 추적

주로 각각의 HTTP 요청을 추적, 일부는 마이크로서비스 환경에서 분산 추적

제품
핀포인트(오픈소스), 스카우트(오픈소스), 와탭(상용), 제니퍼(상용)

로그

  • 가장 자세한 추적, 원하는데로 커스텀 가능
  • 같은 HTTP 요청을 묶어서 확인할 수 있는 방법이 중요, MDC 적용

파일로 직접 로그를 남기는 경우

  • 일반 로그와 에러 로그는 파일을 구분
  • 에러 로그만 확인해서 문제를 바로 정리할 수 있음

클라우드에 로그를 저장하는 경우

  • 검색이 잘 되도록 구분

모니터링 정리

  • 관찰을 할 때는 전체 -> 점점 좁게
  • 하나만 사용 가능하다면 핀포인트를 사용하자.
    • 마이크로 서비스 분산 모니터링도 가능, 대용량 트래픽에 대응 가능

알람

모니터링 툴에서 일정 이상 수치가 넘어가면, 슬랙, 문자 등을 연동

알람은 2가지 종류로 꼭 구분해서 관리

  • 경고, 심각
  • 경고 - 하루 1번 정도 사람이 직접 확인하는 수준
  • 심각 - 즉시 확인, 슬랙 또는 문자, 전화 등을 통한 알림

예)

  • 디스크 사용량 70% -> 경고
  • 디스크 사용량 80% -> 심각
  • CPU 사용량 40% -> 경고
  • CPU 사용량 50% -> 심각
profile
호기심이 많은 백엔드 개발자

0개의 댓글

관련 채용 정보