
使用ExecutorServiceMetrics对线程池进行监控
为了实现这个目标,就让AI进行代码生成,生成得还不错,还自己加戏做了一些额外工作
自定义了一些tag,看着应该有用
private Iterable<Tag> buildTags(String poolType) {
return Tags.of(
"thread.pool.source", "ThreadPoolUtil",
"thread.pool.type", poolType
);
}
Iterable<Tag> tags = buildTags(poolType);
new ExecutorServiceMetrics(executor, poolName, tags).bindTo(meterRegistry);
在 /actuator/prometheus 中应该能持续看到类似指标:
executor_active_threads{...,name="Test_xxx",thread_pool_source="ThreadPoolUtil",thread_pool_type="Test"} 1.0
executor_pool_core_threads{...,name="Test_xxx",thread_pool_source="ThreadPoolUtil",thread_pool_type="Test"} 25.0
executor_pool_max_threads{...,name="Test_xxx",thread_pool_source="ThreadPoolUtil",thread_pool_type="Test"} 25.0
executor_queued_tasks{...,name="Test_xxx",thread_pool_source="ThreadPoolUtil",thread_pool_type="Test"} 0.0
executor_completed_tasks_total{...,name="Test_xxx",thread_pool_source="ThreadPoolUtil",thread_pool_type="Test"} 0.0
理想很美好,现实却打脸。在/actuator/prometheus一直没有看到收集到的指标
让AI再分析一下,给了几个猜测
结合当前代码实现,最常见原因通常不是“完全没绑定”,而是以下几类问题:
1. 查询的指标名不对
2. 线程池生命周期太短,抓取时已经结束
3. 实际线程池没有走 ThreadPoolUtil.createFixedThreadPool(...)
4. binder 没有真正初始化成功
看着第2点,有点道理。防止他在不停地怀疑1、3、4,他不相信人类,只能让他自己干了,让他自己写测试代码进行测试。
分析一通后,又开始猜测了
最可能根因:同名 executor.* 指标的 label 集不一致,Prometheus 无法合并
Prometheus 对同一个 metric family 有一个重要要求:
• 同名指标的所有时间序列,label key 集合必须一致
也就是说,如果同一个指标名下有两组数据:
executor_active_threads{application="north",name="csmThreadPoolTaskExecutor"} 1
executor_active_threads{
application="north",
name="Test_xxx",
thread_pool_source="ThreadPoolUtil",
thread_pool_type="Test"
} 0
那么这两组数据的 label key 并不一致:
• application
• name
• application
• name
• thread_pool_source
• thread_pool_type
这会导致同一个 executor_active_threads family 出现 label schema 冲突。
为什么项目里很可能已经存在另一组 executor.* 指标
Spring Boot 2.7 自带:
• TaskExecutorMetricsAutoConfiguration
它会自动为 Spring 容器里的 ThreadPoolTaskExecutor / ThreadPoolTaskScheduler 绑定 ExecutorServiceMetrics。
本项目里已经存在多个 ThreadPoolTaskExecutor Bean,
Spring Boot 的自动绑定方式相当于:
newExecutorServiceMetrics(threadPoolExecutor, beanName,Collections.emptyList()).bindTo(registry);
也就是说,这批自动注册的 executor.* 指标通常只有:
• name
• 再加全局 common tags(如 application)
它们没有:
• thread.pool.source
• thread.pool.type
自己拉屎,自己再吃
针对这个问题,推荐优先使用以下方案。
不要继续使用默认的:
• executor.*
而是改成带前缀的独立指标族,例如:
• threadpoolutil.executor.*
• 或 north.threadpool.executor.*
代码上可以改为:
newExecutorServiceMetrics(executor, poolName,"threadpoolutil", tags).bindTo(meterRegistry);
这样 Micrometer 原始名会变成:
• threadpoolutil.executor.active
• threadpoolutil.executor.completed
• ...
在 Prometheus 中会变成类似:
• threadpoolutil_executor_active_threads
• threadpoolutil_executor_completed_tasks_total
这样它就不会和 Spring Boot 自动注册的:
• executor_active_threads
发生 metric family 冲突。
这是最推荐方案,因为它:
• 保留了 thread.pool.source
• 保留了 thread.pool.type
• 避免和已有 executor 指标同名冲突
也可以把自定义 tags 去掉,改成只保留默认的:
• name
这样就能和 Spring Boot 自动线程池指标共用同一个 family。
但缺点是:
• 无法区分这些指标是否来自 ThreadPoolUtil
• 无法直接按 thread_pool_type 做筛选
所以一般不如方案一。
理论上也可以让所有 executor.* 指标都带同样的 label keys。
但这意味着:
• 要覆盖 Spring Boot 自动注册逻辑
• 或自己接管所有线程池指标绑定
实现复杂度高,不适合作为当前问题的首选方案。
最后选择了方案二,把label key统一。还真成功了。
这次使用的 gpt-5.4 high 模型,感觉就差一口气。就不让一开始就规避这个低级错误吗?
是不是故意烧点token