Spring Boot 项目用 Atlas 看吞吐时,Micrometer 1.18 的 FunctionTimer 有一条需要改查询的变更:上报值由周期差值变成每秒速率。原来手写 / 60 或 / step秒数 修正数据的查询和告警,升级后得把那一段去掉。业务量没变,继续除就会再缩小一次。

Micrometer 是 JVM 应用的指标门面,本文看到的是 1.18.0-RC2 预发布版的行为。官方 GitHub release 于 2026 年 11 月 6 日 04:18(北京时间)发布。

FunctionTimer 的计数为何会突然缩小

有些指标不适合在每次请求里自己调 record()。例如导入器只保留累计完成数和累计耗时,Micrometer 的 FunctionTimer 定期读取这两个值更省事。缓存、线程池、Hibernate 和 Tomcat 请求等 binder 也有这种实现。

过去,Atlas registry 把 FunctionTimer 当作普通 meter 注册。statistic=count 和 statistic=totalTime 发的是一个上报周期内的增量,却标成 atlas.dstype=gauge。按默认 1 分钟周期看,它表示每分钟多少次、累计工作多少秒,并不是每秒速率。

1.18 把这两路接到 Spectator 的单调计数器轮询。网络数据里的类型变为 rate,值也按秒归一化,与普通 Timer、FunctionCounter 对齐。对已经给旧数据除以周期秒数的看板,这时最重要的动作是删掉手工除法。先定位依赖 FunctionTimer 的指标,别对所有 Atlas 面板做全局替换。

两版独立进程,逐批增加 60 个任务

用 Spring Boot 3.4.5、Java 21 搭了一个导入任务示例,对照 Micrometer 1.17.2 和 1.18.0-RC2。每个版本各起独立进程,两次请求各递增 60 个完成任务和 120 000 毫秒工作。发送周期缩到 10 秒,能尽快看到差异,也方便核对比例。

核心 pom.xml 只需 Web、Actuator 和 io.micrometer:micrometer-registry-atlas,版本通过下面的属性切换:

<properties>
    <java.version>21</java.version>
    <micrometer.version>1.17.2</micrometer.version>
    <!-- 第二包改为 1.18.0-RC2 -->
</properties>
management.atlas.metrics.export.enabled=true
management.atlas.metrics.export.uri=http://127.0.0.1:7101/api/v1/publish
management.atlas.metrics.export.step=10s

任务类保留两个累计值:completed() 返回完成数,totalMillis() 返回累计工作毫秒。它们来自同一个 Spring Bean,HTTP 请求和指标读取的是同一份状态。

@Bean
MeterBinder batchTimer(BatchJobs jobs) {
    return registry -> FunctionTimer.builder("batch.jobs", jobs,
                    BatchJobs::completed, BatchJobs::totalMillis,
                    TimeUnit.MILLISECONDS)
            .tag("job", "import")
            .register(registry);
}

@GetMapping("/run")
Map<String, Object> run() {
    // 每次模拟 60 个任务,总工作 120 秒
    jobs.record(60, 120_000);
    Map<String, Object> result = new LinkedHashMap<>();
    result.put("jobsInInterval", 60);
    result.put("workMillisInInterval", 120_000);
    result.put("jobsTotal", jobs.completed());
    result.put("workMillisTotal", jobs.totalMillis());
    return result;
}

同一份应用代码分别打两个可执行 jar,再逐个启动。接收方只监听本地端口,记录真正的 Atlas HTTP 发布请求并返回 200,不接外部账号。发布请求用 gzip 加 Jackson Smile 编码,解码后就可核对发送端实物,无需猜测曲线如何归一化。

mvn -B -DskipTests package 2>&1 | tee logs/build-old.log
mvn -B -Dmicrometer.version=1.18.0-RC2 -DskipTests package \
  2>&1 | tee logs/build-rc2.log

实验按版本启动 target/atlas-${micrometer.version}.jar,期间请求两次 /run。两次返回都来自真实 Boot 应用:

{"jobsInInterval":60,"workMillisInInterval":120000,"jobsTotal":60,"workMillisTotal":120000.0}
{"jobsInInterval":60,"workMillisInInterval":120000,"jobsTotal":120,"workMillisTotal":240000.0}

差距正好是 10 秒周期,平均耗时却没变

两边第二个上报批次都包含新增的 60 次、120 秒。抽出 batch.jobs,差异很干净:

依赖statisticatlas.dstype发布值
1.17.2countgauge60.0
1.17.2totalTimegauge120.0
1.18.0-RC2countrate6.0
1.18.0-RC2totalTimerate12.0
# 1.17.2,第二个上报批次(首个非空)
statistic=totalTime  atlas.dstype=gauge  value=120.0
statistic=count      atlas.dstype=gauge  value=60.0

# 1.18.0-RC2,第二个上报批次(首个非零)
statistic=totalTime  atlas.dstype=rate   value=12.0
statistic=count      atlas.dstype=rate   value=6.0

应用真实记录仍是 60 个任务和 120 秒工作,RC2 并没有丢任务。除以 10 秒上报周期正好得到 6 次/秒、12 工作秒/秒;按默认 step=1m 查看,就是官方提示的约 60 倍差异。换一个小周期不只利于实验,也说明旧查询里写死的 60 本身值得检查。

平均延迟无需跟着改口径。本次旧版为 120 / 60 = 2 秒,RC2 为 12 / 6 = 2 秒。分子分母缩放相同,totalTime/count 可以保留。官方迁移指南提到的空闲指标失效变化则是另一条,别让短暂无流量的旧看板行为也成为升级判断依据。

先看待升级的查询,再改 Micrometer 版本

1.18 迁移指南明确要求移除对 FunctionTimer 的手工周期除法。按 name、statistic 和所用 binder 查一遍看板及告警公式,重点找「拿到 count/totalTime 后再除周期秒数」的补偿;普通 Timer 已经是每秒速率,别套进这次替换。本次对照依赖是 1.18.0-RC2 预发布版;生产侧版本跟随所用 Spring Boot 的依赖管理。

如果是私有封装指标,也能像这里一样把 URI 指到本地接收器,抓升级前后同一操作的数据。预期是类型从 gauge 变成 rate,值按 periodSeconds 缩放,而平均耗时保持。这样验收的是发出端行为,不依赖图表峰值碰巧对得上。

Micrometer 仓库为 micrometer-metrics/micrometer,采用 Apache License 2.0;这条改动的官方实现说明在 PR #8108,版本主源为 1.18.0-RC2 release。1.18 的改动只负责发布正确的速率,旧查询会不会误报,仍得看面板里的补偿公式。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。