Springboot - 모니터링 메트릭 활용

Kwon Yongho·2023년 7월 7일
0

Spring

목록 보기
37/37
post-thumbnail
  1. 메트릭 등록

1. 메트릭 등록

1-1. 예제 만들기

비즈니스에 특화된 부분을 모니터링 하고 싶으면 어떻게 해야할까?
비즈니스 메트릭은 직접 등록하고 확인해야 한다.

예제

  • 주문수, 취소수
    • 상품을 주문하면 주문수가 증가한다.
    • 상품을 취소해도 주문수는 유지한다. 대신에 취소수를 증가한다.
  • 재고 수량
    • 상품을 주문하면 재고 수량이 감소한다.
    • 상품을 취소하면 재고 수량이 증가한다.
    • 재고 물량이 들어오면 재고 수량이 증가한다.
  • 주문수, 취소수는 계속 증가하므로 카운터를 사용하자.
  • 재고 수량은 증가하거나 감소하므로 게이지를 사용하자.

OrderService

package hello.order;

import java.util.concurrent.atomic.AtomicInteger;

public interface OrderService {
    void order();
    void cancel();
    AtomicInteger getStock();
}

OrderServiceV0

package hello.order.v0;

import hello.order.OrderService;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV0 implements OrderService {

    private AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        log.info("주문");
        stock.decrementAndGet();
    }

    @Override
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}
  • new AtomicInteger(100): 초기값을 100으로 설정

OrderConfigV0

package hello.order.v0;

import hello.order.OrderService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV0 {

    @Bean
    OrderService orderService() {
        return new OrderServiceV0();
    }
}
  • OrderService 빈을 직접 등록

OrderController

package hello.controller;

import hello.order.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class OrderController {

    public final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/order")
    public String order() {
        log.info("order");
        orderService.order();
        return "order";
    }

    @GetMapping("/cancel")
    public String cancel() {
        log.info("cancel");
        orderService.cancel();
        return "cancel";
    }

    @GetMapping("/stock")
    public int stock() {
        log.info("stock");
        return orderService.getStock().get();
    }
}

ActuatorApplicationOrderConfigV0.class Import 후 실행

1-2. 카운터

마이크로미터를 사용해서 메트릭을 직접 등록하는 방법을 알아보자. 먼저 주문수, 취소수를 대상으로 카운터 메트릭을 등록해보자.

Counter(카운터)

  • 단조롭게 증가하는 단일 누적 측정항목
    • 단일 값
    • 보통 하나씩 증가
    • 누적이므로 전체 값을 포함(total)
    • 프로메테우스에서는 일반적으로 카운터의 이름 마지막에 _total을 붙여서 my_order_total과 같이 표현함

OrderServiceV1

package hello.order.v1;

import hello.order.OrderService;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

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

    @Override
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();

        Counter.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "cancel")
                .description("order")
                .register(registry).increment();
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}
  • Counter.builder(name)를 통해서 카운터를 생성
  • tag를 사용했는데, 프로메테우스에서 필터할 수 있는 레이블로 사용
  • 주문과 취소는 메트릭 이름은 같고 tag를 통해서 구분
  • register(registry): 만든 카운터를 MeterRegistry에 등록
  • increment(): 카운터의 값을 하나 증가

OrderConfigV1

package hello.order.v1;

import hello.order.OrderService;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV1 {

    @Bean
    OrderService orderService(MeterRegistry registry) {
        return new OrderServiceV1(registry);
    }
}

액츄에이터 메트릭 확인

프로메테우스 포멧 메트릭 확인

  • ordercancel이 한번 씩 호출 된 것을 확인 할 수 있다.

그라파나 등록 - 주문수, 취소수

확인

1-3. @Counted

OrderServiceV1의 가장 큰 단점은 메트릭을 관리하는 로직이 핵심 비즈니스 개발 로직에 침투했다는 점이다. 해결하기 위해선 스프링 AOP를 활용하면 된다.

OrderServiceV2

package hello.order.v2;

import hello.order.OrderService;
import io.micrometer.core.annotation.Counted;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV2 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애노테이션을 측정을 원하는 메서드에 적용한다. 주문과 취소 메서드에 적용했다.
  • 그리고 메트릭 이름을 지정하면 된다. 여기서는 이전과 같은 my.order를 적용했다.

OrderConfigV2

package hello.order.v2;

import hello.order.OrderService;
import io.micrometer.core.aop.CountedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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를 적용한다.

그라파나 대시보드 확인

1-4. Timer

Timer
Timer는 좀 특별한 메트릭 측정 도구인데, 시간을 측정하는데 사용된다.

  • 카운터와 유사한데, Timer 를 사용하면 실행 시간도 함께 측정할 수 있다.
  • Timer는 다음과 같은 내용을 한번에 측정해준다.
    • seconds_count: 누적 실행 수 - 카운터
    • seconds_sum: 실행 시간의 합 - sum
    • seconds_max: 최대 실행 시간(가장 오래걸린 실행 시간) - 게이지

OrderServiceV3

package hello.order.v3;

import hello.order.OrderService;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

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

    @Override
    public void cancel() {
        Timer timer = Timer.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "cancel")
                .description("order")
                .register(registry);

        timer.record(() -> {
            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;
    }
}
  • register(registry): 만든 타이머를 MeterRegistry에 등록한다. 이렇게 등록해야 실제 동작 한다.
  • 타이머를 사용할 때는 timer.record() 를 사용하면 된다. 그 안에 시간을 측정할 내용을 함수로 포함 하면 된다.

OrderConfigV3

package hello.order.v3;

import hello.order.OrderService;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV3 {

    @Bean
    OrderService orderService(MeterRegistry registry) {
        return new OrderServiceV3(registry);
    }
}

액츄에이터 메트릭 확인

  • COUNT: 누적 실행 수(카운터와 같다)
  • TOTAL_TIME: 실행 시간의 합(각각의 실행 시간의 누적 합이다)
  • MAX: 최대 실행 시간(가장 오래 걸린 실행시간이다)

프로메테우스 포멧 메트릭 확인

  • seconds_count: 누적 실행 수
  • seconds_sum: 실행 시간의 합
  • seconds_max: 최대 실행 시간(가장 오래걸린 실행 시간)

그라파나 패널 추가

  • increase(my_order_seconds_count{method="order"}[1m])
  • increase(my_order_seconds_count{method="cancel"}[1m])
  • my_order_seconds_max
  • increase(my_order_seconds_sum[1m]) / increase(my_order_seconds_count[1m])

1-5. @Timed

타이머는 @Timed라는 애노테이션을 통해 AOP를 적용할 수 있다.

OrderServiceV4

package hello.order.v4;

import hello.order.OrderService;
import io.micrometer.core.annotation.Timed;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

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

    @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("my.order")타입이나 메서드 중에 적용할 수 있다.

OrderConfigV4

package hello.order.v4;

import hello.order.OrderService;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV4 {

    @Bean
    OrderService orderService() {
        return new OrderServiceV4();
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry);
    }
}
  • TimedAspect를 적용해야 @Timed 에 AOP가 적용된다.

그라파나 대시보드 확인

1-6. 게이지

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

재고 수량을 통해 게이지를 등록하는 간단 예제를 해보겠습니다.

StockConfigV1

package hello.order.gauge;

import hello.order.OrderService;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class StockConfigV1 {

    @Bean
    public MyStockMetric myStockMetric(OrderService orderService, MeterRegistry registry) {
        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);
        }
    }
}

액츄에이터 메트릭 확인

게이지 단순 등록
StockConfigV2

package hello.order.gauge;

import hello.order.OrderService;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.binder.MeterBinder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

액츄에이터 메트릭 확인

그라파나 등록 - 재고

  • my_stock

0개의 댓글