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