1. 简介

Micrometer 是一个简单的外观层,为多种主流监控系统提供统一的指标采集客户端。目前支持以下监控系统:Atlas、Datadog、Graphite、Ganglia、Influx、JMX 和 Prometheus。

本文将介绍 Micrometer 的基础用法及其与 Spring 的集成。为简化演示,我们以 Micrometer Atlas 为例说明大部分用例。

2. Maven 依赖

首先在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-atlas</artifactId>
    <version>1.12.3</version>
</dependency>

最新版本可在 这里 查询。

3. MeterRegistry

在 Micrometer 中,MeterRegistry 是注册指标的核心组件。通过遍历注册表,我们可以将指标及其维度值组合生成后端时间序列数据。

最简单的注册表是 SimpleMeterRegistry,但实际使用时应选择针对监控系统的专用实现(如 Atlas 对应 AtlasMeterRegistry)。

CompositeMeterRegistry 允许同时添加多个注册表,实现指标向多个监控系统的同步发布:

CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry();
AtlasMeterRegistry atlasMeterRegistry 
  = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM);

compositeRegistry.add(oneSimpleMeter);
compositeRegistry.add(atlasMeterRegistry);

Micrometer 还提供全局静态注册表 Metrics.globalRegistry,并通过 Metrics 的静态构建器生成指标:

@Test
public void givenGlobalRegistry_whenIncrementAnywhere_thenCounted() {
    class CountedObject {
        private CountedObject() {
            Metrics.counter("objects.instance").increment(1.0);
        }
    }
    Metrics.addRegistry(new SimpleMeterRegistry());

    Metrics.counter("objects.instance").increment();
    new CountedObject();

    Optional<Counter> counterOptional = Optional.ofNullable(Metrics.globalRegistry
      .find("objects.instance").counter());
    assertTrue(counterOptional.isPresent());
    assertTrue(counterOptional.get().count() == 2.0);
}

4. Tags 和 Meters

4.1. Tags

Meter 的标识由名称和标签组成。命名规范建议用点号分隔单词,确保指标名称跨监控系统的可移植性

Counter counter = registry.counter("page.visitors", "age", "20s");

Tags 用于对指标进行切片分析。上例中 page.visitors 是指标名称,age=20s 是标签,表示统计 20-30 岁年龄段的页面访问量。

大型系统中可为注册表添加通用标签(如区域标识):

registry.config().commonTags("region", "ua-east");

4.2. Counter

Counter 用于统计应用属性的计数。可通过流式构建器或注册表辅助方法创建:

Counter counter = Counter
  .builder("instance")
  .description("indicates instance count of the object")
  .tags("dev", "performance")
  .register(registry);

counter.increment(2.0);
 
assertTrue(counter.count() == 2);
 
counter.increment(-1);
 
assertTrue(counter.count() == 1);

⚠️ 注意:Counter 只能单调递增,尝试递减操作会被忽略(如上例中 increment(-1) 实际未生效)。

4.3. Timers

使用 Timers 测量事件延迟或频率。Timer 至少会记录特定时间序列的总时间和事件数:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
    try {
        TimeUnit.MILLISECONDS.sleep(15);
    } catch (InterruptedException ignored) {
    }
    });

timer.record(30, TimeUnit.MILLISECONDS);

assertTrue(2 == timer.count());
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);

对于长时间运行的任务,使用 LongTaskTimer

SimpleMeterRegistry registry = new SimpleMeterRegistry();
LongTaskTimer longTaskTimer = LongTaskTimer
  .builder("3rdPartyService")
  .register(registry);

LongTaskTimer.Sample currentTaskId = longTaskTimer.start();
try {
    TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException ignored) { }
long timeElapsed = currentTaskId.stop();
 
 assertEquals(2L, timeElapsed/((int) 1e6),1L);

4.4. Gauge

Gauge 展示指标的瞬时值。与其他指标不同,Gauges 仅在观察时报告数据,适合监控缓存或集合状态:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
List<String> list = new ArrayList<>(4);

Gauge gauge = Gauge
  .builder("cache.size", list, List::size)
  .register(registry);

assertTrue(gauge.value() == 0.0);
 
list.add("1");
 
assertTrue(gauge.value() == 1.0);

4.5. DistributionSummary

DistributionSummary 提供事件分布和简单摘要统计:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
DistributionSummary distributionSummary = DistributionSummary
  .builder("request.size")
  .baseUnit("bytes")
  .register(registry);

distributionSummary.record(3);
distributionSummary.record(4);
distributionSummary.record(5);

assertTrue(3 == distributionSummary.count());
assertTrue(12 == distributionSummary.totalAmount());

DistributionSummary 和 Timers 可通过百分位增强:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = Timer
  .builder("test.timer")
  .publishPercentiles(0.3, 0.5, 0.95)
  .publishPercentileHistogram()
  .register(registry);

注册表会生成三个带标签 percentile=0.3percentile=0.5percentile=0.95 的 Gauge,分别表示 30%、50%、95% 观测值的阈值。添加记录后验证:

timer.record(2, TimeUnit.SECONDS);
timer.record(2, TimeUnit.SECONDS);
timer.record(3, TimeUnit.SECONDS);
timer.record(4, TimeUnit.SECONDS);
timer.record(8, TimeUnit.SECONDS);
timer.record(13, TimeUnit.SECONDS);

提取百分位值验证:

Map<Double, Double> actualMicrometer = new TreeMap<>();
ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues();
for (ValueAtPercentile percentile : percentiles) {
    actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS));
}

Map<Double, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(0.3, 1946.157056);
expectedMicrometer.put(0.5, 3019.89888);
expectedMicrometer.put(0.95, 13354.663936);

assertEquals(expectedMicrometer, actualMicrometer);

Micrometer 还支持 服务等级目标(直方图):

DistributionSummary hist = DistributionSummary
  .builder("summary")
  .serviceLevelObjectives(1, 10, 5)
  .register(registry);

添加记录后验证直方图计算:

Map<Integer, Double> actualMicrometer = new TreeMap<>();
HistogramSnapshot snapshot = hist.takeSnapshot();
Arrays.stream(snapshot.histogramCounts()).forEach(p -> {
  actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count());
});

Map<Integer, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(1,0D);
expectedMicrometer.put(10,2D);
expectedMicrometer.put(5,1D);

assertEquals(expectedMicrometer, actualMicrometer);

直方图可直接对比不同分桶数据,还支持时间缩放(如分析后端服务响应时间):

Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)};
Timer timer = Timer
  .builder("timer")
  .sla(durations)
  .publishPercentileHistogram()
  .register(registry);

5. Binders

Micrometer 内置多种 Binders 监控 JVM、缓存、ExecutorService 和日志服务。

JVM 和系统监控支持:

缓存监控(支持 Guava、EhCache、Hazelcast、Caffeine):

日志服务监控通过绑定 LogbackMetrics 实现:

new LogbackMetrics().bind(registry);

其他 Binders 用法类似,此处不再赘述。

6. Spring 集成

Spring Boot Actuator 为 Micrometer 提供依赖管理和自动配置,支持 Spring Boot 2.0/1.x 和 Spring Framework 5.0/4.x。

添加以下依赖(最新版本见此处):

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-spring-legacy</artifactId>
    <version>1.3.20</version>
</dependency>

无需修改现有代码即可启用 Spring 集成。JVM 内存指标将自动注册到全局注册表,并发布到默认 Atlas 接口:http://localhost:7101/api/v1/publish

可通过 spring.metrics.atlas.* 配置属性控制指标导出行为,完整配置项参考 AtlasConfig

需绑定额外指标时,将其声明为 @Bean 即可。例如添加 JvmThreadMetrics:

@Bean
JvmThreadMetrics threadMetrics(){
    return new JvmThreadMetrics();
}

Web 监控默认自动配置所有接口,可通过 spring.metrics.web.autoTimeServerRequests 管理。默认实现为接口提供四个维度的指标:HTTP 请求方法、HTTP 响应码、接口 URI 和异常信息

请求响应后,Atlas 将发布请求方法(GET、POST 等)相关指标。通过 Atlas Graph API 可生成不同方法响应时间对比图:

methods

默认还会报告 20x、30x、40x、50x 响应码:

status

可对比不同 URI:

uri

或检查异常指标:

exception

可通过 @Timed 注解在控制器类或方法上自定义标签、长任务、分位数和百分位:

@RestController
@Timed("people")
public class PeopleController {

    @GetMapping("/people")
    @Timed(value = "people.all", longTask = true)
    public List<String> listPeople() {
        //...
    }

}

通过 Atlas 接口 http://localhost:7101/api/v1/tags/name 可查看以下标签:

["people", "people.all", "jvmBufferCount", ... ]

Micrometer 也支持 Spring Boot 2.0 的函数式 Web 框架,通过过滤 RouterFunction 启用指标:

RouterFunctionMetrics metrics = new RouterFunctionMetrics(registry);
RouterFunctions.route(...)
  .filter(metrics.timer("server.requests"));

还可收集数据源和定时任务指标,详情参考官方文档

7. 总结

本文介绍了指标门面 Micrometer。通过抽象并支持多种监控系统的通用语义,该工具使切换不同监控平台变得简单。

本文完整实现代码可在 GitHub 获取。


原始标题:Quick Guide to Micrometer | Baeldung