码农戏码

新生代农民工的自我修养

0%

使用ExecutorServiceMetrics对线程池进行监控

最近想对线程池增加一下监控,先从https://grafana.com/grafana/dashboards/ 找到一个关于Spring Boot Executors的Grafana面板

尽然发现,已经有部分线程池被监控到了,记得以前都得使用ExecutorServiceMetrics包装一下,原来在Springboot 2.6.0版本提供了TaskExecutorMetricsAutoConfiguration,可以自动给线程池加上metrics

TaskExecutorMetricsAutoConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@AutoConfiguration(after = { MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class,  
TaskExecutionAutoConfiguration.class, TaskSchedulingAutoConfiguration.class })
@ConditionalOnClass(ExecutorServiceMetrics.class)
@ConditionalOnBean({ Executor.class, MeterRegistry.class })
public class TaskExecutorMetricsAutoConfiguration {

@Autowired
public void bindTaskExecutorsToRegistry(Map<String, Executor> executors, MeterRegistry registry) {
executors.forEach((beanName, executor) -> {
if (executor instanceof ThreadPoolTaskExecutor) {
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskExecutor) executor), beanName);
}
else if (executor instanceof ThreadPoolTaskScheduler) {
monitor(registry, safeGetThreadPoolExecutor((ThreadPoolTaskScheduler) executor), beanName);
}
});
}

看源码,原理跟之前手动操作一样遍历executors,然后执行monitor方法,而monitor方法则是创建ExecutorServiceMetrics然后绑定到meterRegistry

ExecutorServiceMetrics

ExecutorServiceMetrics 是 Micrometer 中专门用于监控 Java 线程池(ExecutorService)的工具。它可以帮助你实时了解线程池的运行状态,比如活跃线程数、队列积压、任务执行耗时等,这些指标对排查性能问题和优化线程池配置很有价值。

核心的使用方法有两种:直接包装(推荐)构造器绑定

📊 暴露的监控指标

指标名称会根据你的线程池类型(ThreadPoolExecutorForkJoinPool)略有不同:

指标名称 类型 描述
executor.active Gauge 当前正在执行任务的活跃线程数
executor.queued Gauge 队列中等待执行的任务数量
executor.completed FunctionCounter 线程池已完成的任务总数
executor.pool.size Gauge 线程池中当前的线程总数
executor Timer 记录任务执行的耗时分布(P99、P95等)
executor.idle Timer 记录任务在队列中的等待耗时
executor.queue.remaining Gauge 队列剩余容量(ThreadPoolExecutor 特有)
executor.steals FunctionCounter 工作窃取总数(ForkJoinPool 特有)

🛠️ 使用步骤

1. 添加依赖

确保你的项目中引入了 Micrometer 的核心依赖。

1
2
3
4
5
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>最新版本</version>
</dependency>

2. 方法一:直接包装(monitor 方法)

这是最常用的方式。monitor 方法会返回一个被 Micrometer 增强过的 ExecutorService 代理对象。你只需要像平常一样用它提交任务即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorMonitorDemo {

private ExecutorService monitoredExecutor;

public ExecutorMonitorDemo(MeterRegistry registry) {
// 1. 创建原始的线程池
ExecutorService originalExecutor = Executors.newFixedThreadPool(10);

// 2. 用 Monitor 方法进行包装
// 参数说明:MeterRegistry, 原始Executor, 线程池名称(用于标签), 可选的额外标签
this.monitoredExecutor = ExecutorServiceMetrics
.monitor(registry, originalExecutor, "my-service-pool");

// 3. 使用被包装过的线程池执行任务
monitoredExecutor.submit(() -> {
System.out.println("Task is being monitored!");
});
}
}

3. 方法二:构造器绑定(bindTo 方法)

这种方式只注册指标,不返回代理对象。适用于你不想改变现有线程池引用的情况。

1
2
3
4
5
6
7
8
9
10
11
12
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import io.micrometer.core.instrument.Tags;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

// 假设你已有一个 threadPool 实例
ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);

// 创建 ExecutorServiceMetrics 实例,并绑定到 MeterRegistry
new ExecutorServiceMetrics(threadPool, "my-pool-name", Tags.empty())
.bindTo(registry);