bigywater.tech
Spring · 80

Micrometer 指标:自带的与自定义的

Boot 自带哪些指标?uri 标签会不会因为路径变量爆炸?自己注册的 Gauge 为什么会变成 NaN?标签基数大了会怎样?Prometheus 里的名字为什么和代码里不一样?这篇在 Spring Boot 4.0.8 上逐个实测。

31 → 1
31 个 /user/{id} 请求在 http.server.requests 里只对应 uri 标签的 1 个取值
NaN
reg.gauge 只弱引用对象:注册后读 42.0,两次 System.gc() 之后读 NaN
100108 / 53 MB
10 万个不同 id 标签的 Counter:Meter 总数,以及 GC 之后多占的堆

自带指标

实测 · 本机执行输出
  刚启动、还没有任何请求:/actuator/metrics 共 53 个指标名;按第一段分组:application×2, disk×2, executor×7, http×1, jvm×23, logback×1, process×6, queue×1, system×3, tomcat×6, weak×1
  发过 2 个请求之后:54 个;多出来的:['http.server.requests']
  jvm.memory*:4 个,例如 ['jvm.memory.committed', 'jvm.memory.max', 'jvm.memory.usage.after.gc', 'jvm.memory.used']
  jvm.gc*:6 个,例如 ['jvm.gc.live.data.size', 'jvm.gc.max.data.size', 'jvm.gc.memory.allocated', 'jvm.gc.memory.promoted']
  jvm.threads*:5 个,例如 ['jvm.threads.daemon', 'jvm.threads.live', 'jvm.threads.peak', 'jvm.threads.started']
  process*:6 个,例如 ['process.cpu.time', 'process.cpu.usage', 'process.files.max', 'process.files.open']
  system*:3 个,例如 ['system.cpu.count', 'system.cpu.usage', 'system.load.average.1m']
  tomcat*:6 个,例如 ['tomcat.sessions.active.current', 'tomcat.sessions.active.max', 'tomcat.sessions.alive.max', 'tomcat.sessions.created']
  executor*:7 个,例如 ['executor.active', 'executor.completed', 'executor.pool.core', 'executor.pool.max']
  disk*:2 个,例如 ['disk.free', 'disk.total']
  logback*:1 个,例如 ['logback.events']
  application*:2 个,例如 ['application.ready.time', 'application.started.time']
  http*:2 个,例如 ['http.server.requests', 'http.server.requests.active']

JVM、进程、系统、Tomcat、线程池、日志、启动耗时;http.server.requests 在第一个请求之后才出现。

请求指标的标签

实测 · 本机执行输出
  总次数(COUNT)= 86;统计量 ['COUNT', 'TOTAL_TIME', 'MAX'];baseUnit=seconds
  标签 exception:2 个取值 ['IllegalStateException', 'none']
  标签 method:1 个取值 ['GET']
  标签 error:2 个取值 ['IllegalStateException', 'none']
  标签 uri:13 个取值 ['/queue', '/many', '/weak', '/user/{id}', '/fast', '/work', '/**', '/actuator/metrics/{requiredMetricName}', '/order', '/size', '/actuator/metrics', '/boom']
  标签 outcome:3 个取值 ['CLIENT_ERROR', 'SUCCESS', 'SERVER_ERROR']
  标签 status:3 个取值 ['404', '500', '200']
  tag=uri:/user/{id} → COUNT 31
  tag=status:404 → COUNT 30
  tag=outcome:SERVER_ERROR → COUNT 5
  tag=uri:/** → COUNT 30
  tag=uri:/nope/1 → 
  tag=status:500 的 exception 标签取值:[['IllegalStateException']]

uri 用路由模板,404 落在 /**;exception、outcome、status 都是低基数标签。

自定义指标

实测 · 本机执行输出
  orders.created:baseUnit=None,[('COUNT', 3.0)],标签 [('type', ['a', 'b'])]
  work.duration:baseUnit=seconds,[('COUNT', 3.0), ('TOTAL_TIME', 0.073), ('MAX', 0.035)],标签 []
  payload.size:baseUnit=bytes,[('COUNT', 3.0), ('TOTAL', 600.0)],标签 []
  orders.created?tag=type:a → COUNT 2
  Gauge queue.size(Gauge.builder 引用字段 queue):7;g(/queue?n=3) 之后 → 3
  Gauge weak.demo(reg.gauge("weak.demo", Tags.empty(), new AtomicInteger(42)),没有任何地方持有这个对象):注册后立刻读 = 42.0;紧接着两次 System.gc() 之后读 = NaN;后来通过 /actuator/metrics/weak.demo 读 = NaN

Counter、Timer、DistributionSummary、Gauge 的用法;Gauge 弱引用被观察对象。

标签基数

实测 · 本机执行输出
  创建 100000 个同名不同 id 标签的 Counter(many.ids):Meter 总数 105 → 100108,用时 220 ms,GC 之后堆占用 17 MB → 70 MB
  创建 100000 个同名不同 id 标签的 Counter(capped.ids):Meter 总数 100109 → 100209,用时 38 ms,GC 之后堆占用 70 MB → 70 MB

10 万个不同标签的 Counter 多占约 53 MB;MeterFilter 能把标签截在上限。

Prometheus

实测 · 本机执行输出
  抓取前(Meter 数较少):/actuator/prometheus 共 258 行,17 KB,用时 13 ms
  创建了 100000 个 many.ids 之后:100381 行,3137 KB,用时 232 ms
  # TYPE jvm_memory_used_bytes gauge
  jvm_memory_used_bytes{area="heap",id="G1 Eden Space"} 1.6777216E7 | jvm_memory_used_bytes{area="heap",id="G1 Old Gen"} 1.5126088E7 | jvm_memory_used_bytes{area="heap",id="G1 Survivor Space"} 3513792.0
  # TYPE http_server_requests_seconds summary | # TYPE http_server_requests_seconds_max gauge
  http_server_requests_seconds_count{error="none",exception="none",method="GET",outcome="SUCCESS",status="200",uri="/user/{id}"} 31
  # TYPE orders_total counter | orders_total{type="a"} 2.0 | orders_total{type="b"} 1.0
  # TYPE work_duration_seconds summary | # TYPE work_duration_seconds_max gauge
  work_duration_seconds_count 3 | work_duration_seconds_sum 0.072557124 | work_duration_seconds_max 0.035021791
  payload_size_bytes_count 3 | payload_size_bytes_sum 600.0 | payload_size_bytes_max 300.0
  queue_size 3.0

每次抓取输出全部 Meter;名字会被转换,orders.created 变成 orders_total。

分步演示

Micrometer
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。被测应用是 spring-boot-starter-webmvc、spring-boot-starter-actuator 和 micrometer-registry-prometheus(版本由 Boot 管理)的单个程序,-Xmx1g;指标通过 /actuator/metrics 和 /actuator/prometheus 读取;堆占用是在应用内两次 System.gc() 之后用 totalMemory - freeMemory 取得;指标名个数在不同次运行之间会差 1 个(某些 JVM 指标要等到发生过相应事件才出现)
    • 没有验证:@Timed、@Counted 与 AOP 的用法;直方图和百分位(publishPercentileHistogram、SLO);Prometheus 以外的 registry(OTLP、InfluxDB 等);自定义 MeterBinder;http.server.requests 的 uri 标签在更复杂的路由(多个 DispatcherServlet、静态资源、WebFlux)下的取值;Grafana 看板;指标对请求吞吐的开销(没有测);GC 触发时机对 Gauge 变 NaN 的影响(这里是显式调用 System.gc())
    京ICP备2026067502号-1京公网安备11010502063487号