码农戏码

新生代农民工的自我修养

0%

Vibe Coding-先跑起来再说,AI自拉自吃

[[使用ExecutorServiceMetrics对线程池进行监控]]

为了实现这个目标,就让AI进行代码生成,生成得还不错,还自己加戏做了一些额外工作

自定义了一些tag,看着应该有用

1
2
3
4
5
6
private Iterable<Tag> buildTags(String poolType) {  
return Tags.of(
"thread.pool.source", "ThreadPoolUtil",
"thread.pool.type", poolType
);
}
1
2
Iterable<Tag> tags = buildTags(poolType);  
new ExecutorServiceMetrics(executor, poolName, tags).bindTo(meterRegistry);

/actuator/prometheus 中应该能持续看到类似指标:

1
2
3
4
5
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 集合必须一致

也就是说,如果同一个指标名下有两组数据:

第一组

1
executor_active_threads{application="north",name="csmThreadPoolTaskExecutor"} 1

第二组

1
2
3
4
5
6
executor_active_threads{
application="north",
name="Test_xxx",
thread_pool_source="ThreadPoolUtil",
thread_pool_type="Test"
} 0

那么这两组数据的 label key 并不一致:

第一组 label key

  • application
  • name

第二组 label key

  • 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 的自动绑定方式相当于:

1
new ExecutorServiceMetrics(threadPoolExecutor, beanName, Collections.emptyList()).bindTo(registry);

也就是说,这批自动注册的 executor.* 指标通常只有:

  • name
  • 再加全局 common tags(如 application

它们没有

  • thread.pool.source
  • thread.pool.type

自己拉屎,自己再吃

推荐修复方案

针对这个问题,推荐优先使用以下方案。

方案一:给 ThreadPoolUtil 指标加独立 metric prefix(最推荐)

不要继续使用默认的:

  • executor.*

而是改成带前缀的独立指标族,例如:

  • threadpoolutil.executor.*
  • north.threadpool.executor.*

代码上可以改为:

1
new ExecutorServiceMetrics(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 指标同名冲突

方案二:去掉额外 tag,仅保留与默认 executor 指标一致的标签集

也可以把自定义 tags 去掉,改成只保留默认的:

  • name

这样就能和 Spring Boot 自动线程池指标共用同一个 family。

但缺点是:

  • 无法区分这些指标是否来自 ThreadPoolUtil
  • 无法直接按 thread_pool_type 做筛选

所以一般不如方案一。

当前处理结果

本次已经按方案二落地:

  • ThreadPoolExecutorMetricsBinder 不再为 ThreadPoolUtilexecutor.* 指标附加:
    • thread.pool.source
    • thread.pool.type
  • 改为只使用 ExecutorServiceMetrics 的默认标签集

这样做的直接效果是:

  1. ThreadPoolUtil 创建的线程池指标可以和 Spring Boot 自动注册的 executor.* 指标使用同一套 label key
  2. /actuator/prometheus 中不再因为 label schema 不一致导致 family 冲突
  3. 查询时应主要通过 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