Micrometer란?
JVM 기반의 어플리케이션에서 Vendor의 Lock-In 없이 사용할 수 있게 facade를 제공하고 있는 대표적인 모니터링 시스템
Micrometer provides a simple facade over the instrumentation clients for the most popular observability systems, allowing you to instrument your JVM-based application code without vendor lock-in. Think SLF4J, but for observability. (https://micrometer.io)
Atlas, Datadog, Elastic, Graphite, Ganglia, Influx, JMX, and Prometheus 등 다양한 Vendor에서 micrometer를 지원하고 있다.
Application에서 수집한 Metric을 전송하는 방법
Micometer에서는 MeterRegistry 추상 클래스가 존재하고 각각의 Vendor에서 해당 클래스를 상속 받아 구현하고 있다.
대부분의 Vendor 모니터링 시스템은 Application에서 일정 간격으로 Metirc 정보를 Push 하는 방식으로 구현되어 있는데, MeterRegistry를 상속 받은 PushMeterRegistry 추상 클래스를 구현하고 있다.
- Atlas, Datadog, Elastic, Graphite, Ganglia, Influx, JMX 등
일부 모니터링 시스템은 Application에서 Polling 하는 방식으로 동작하는데, 대표적으로 Prometheus(프로메테우스) 가 있다.
Micrometer에서 제공하는 타입
- Counter
- Guage
- Timer
- DistributionSummary
- LongTaskTimer
- FunctionCounter
- FunctionTimer
- TimeGuage
1. Counter
특정 속성에 대한 카운트 수를 기록하는데, 이 때 Counter는 증가만 가능하다.
EX) 트래픽 확인을 위해 모든 트래픽마다 Counter::increment 를 호출하는 방식으로 사용
val counter = meterRegistry.counter("counter_name")
counter.increments(1.23)
2. Guage
특정 속성에 대해 현재 값을 기록하는데, Counter와 다르게 Set을 사용해 직접 설정도 가능하고 increment를 사용해 증가도 가능하다.
EX) 현재 Memory, CPU Usage를 지속적으로 확인하기 위해 사용
val guage = meterRegistry.guage("guage_name")
guage.set(memoryUsage)
3. Timer
특정 작업 소요 시간을 기록하여 소요 시간의 평균/최대 값을 구하거나, 몇번 해당 작업이 발생되었는지 확인하기 위해 사용한다.
EX) DB, 외부 API 호출 시 어느 정도의 Latency가 걸리는지, 몇번 호출 되었는지 확인하기 위해 사용
val timer = meterRegistry.timer("timer_name")
val startTime = System.currentTimeMillis()
// 특정 작업 (api 호출, 복잡한 연산)
val endTime = System.currentTimeMillis()
timer.record(endTime - startTime, TimeUnit.MILLISECONDS)
4. DistributionSummary
여러 값으로 분포되어 있는 값의 통계적 특성을 파악할 때 사용한다. DistributionSummary::record를 사용해 기록하고 max, min, takeSnapshot 등을 통해 최대, 최소 등의 통계자료를 얻을 수 있다.
val distributionSummary = meterRegistry.summary("summary_name")
distributionSummary.record(543.2)
distributionSummary.record(123.4)
val mean = distributionSummary.max()
val snapshot = distributionSummary.takeSnapshot()
5. LongTaskTimer
일반 Timer와 다르게 특정 작업이 계속 실행되는 동안에도 시간을 측정할 수 있다. 여러 긴 작업들이 병렬로 진행될 때 실행 중인 작업의 수, 총 진행된 작업의 기간, 최대 작업 시간을 기록하고 일반 Timer와 같이 완료된 작업에 통계는 제공하지 않는다.
EX) Batch 작업을 여러 Chunk로 나눠서 진행할 때 모니터링을 위해 사용
val longTaskTimer = meterRegistry.more().longTaskTimer("long_task_name")
longTaskTimer.record {
key -> api.call(key)
}
val activeTaskCount = longTaskTimer.activeTasks()
val snapshot = longTaskTimer.takeSnapshot()
Spring 쉽게 적용하기
DataDog을 사용해서 CustomMetric을 만들어보겠다. (이미 DataDog Agent 설정이 되어있다고 가정)
io.micrometer:micrometer-registry-datadog
의존성 추가합니다.org.springframework.boot:spring-boot-starter-actuator
의존성 사용하고 있을 경우 autoConfig 됩니다.spring-boot-actuator 2.7.2
버전 기준으로 아래 export 들이 등록되어 있습니다.- appoptics, atlas, datadog, dynatrace, elastic, ganglia, graphite, humio, influx, jmx, kairos, newrelic, prometheus, properties, signalfx, simple, stackdriver, statsd, wavefront
- 환경 변수 설정
management.metrics.export.datadog.enabled = true
- Spring에 Condition을 사용한
OnMetricsExportEnabledCondition
있어 해당 환경 변수 설정시DatadogMeterRegistry
가 Bean으로 등록됩니다. - 다른 Vendor를 사용하고 있을 경우
management.metrics.export.{vendor}.enabled = true
설정하시면 됩니다.
- Spring에 Condition을 사용한
- Bean으로 등록되어 있기 때문에 의존성 주입 받아 사용할 수 있습니다.
@Component
class CustomMetric(
meterRegistry: MeterRegistry
) {
val cpuGuage = meterRegistry.gauge("cpu_usage")
@Scheduled(cron = "* * * * * *")
fun recordCpuUsage() {
val osbean = ManagementFactory.getOperatingSystemMXBean() as OperatingSystemMXBean
cpuGuage.set(osbean.cpuLoad)
}
}