一、应用背景
在支付系统中,线程池在批量支付中有着十分重要的作用。随着业务量的增多,支付接口的处理能力越来越弱,甚至出现一个支付批次的处理时间超过5分钟的情况。这对于调用方来说,是无法容忍的。最简单粗暴的解决办法,便是增加线程数和阻塞队列容量。但至于怎么调整,才能做到既保证接口对支付请求的处理能力,又不浪费系统资源,好像只能凭感觉来了。Prometheus + Grafana 是目前比较流行的监控体系,在管理线程池方面的表现也十分出色。
二、线程池配置
采用 Spring 提供的 ThreadPoolTaskExecutor 来完成线程池的配置。根据业务需要,创建两个线程池:同步线程池和异步线程池。具体配置如下:
| 1 | 
 | 
三、监控指标
综合线程池的核心要素和生产业务的关键要素,提出以下几种监控指标:
- 核心线程数; 
- 最大线程数; 
- 活跃线程数; 
- 当前线程数; 
- 队列中任务数; 
- 已完成任务数; - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64- import io.micrometer.core.instrument.Gauge; 
 import io.micrometer.core.instrument.MeterRegistry;
 import io.micrometer.core.instrument.Metrics;
 public class ExecutorMetricsSupport implements InitializingBean {
 
 /**
 * Prometheus 数据采集中心
 */
 
 private MeterRegistry meterRegistry;
 
 
 (SYNC_EXECUTOR)
 private ThreadPoolTaskExecutor syncExecutor;
 
 
 (ASYNC_EXECUTOR)
 private ThreadPoolTaskExecutor asyncExecutor;
 
 
 public void afterPropertiesSet() throws Exception {
 initServiceExecutorMetrics(syncExecutor, "executor.sync");
 initServiceExecutorMetrics(asyncExecutor, "executor.async");
 }
 /**
 * 线程池metrics指标监控
 * @param serviceExecutor 线程池
 * @param namePrefix 指标名称前缀
 */
 private void initServiceExecutorMetrics(ThreadPoolTaskExecutor serviceExecutor, String namePrefix) {
 Gauge
 .builder(namePrefix.concat(".active"),
 serviceExecutor, ThreadPoolTaskExecutor::getActiveCount)
 .register(meterRegistry);
 
 Gauge
 .builder(namePrefix.concat(".core"),
 serviceExecutor, ThreadPoolTaskExecutor::getCorePoolSize)
 .register(meterRegistry);
 
 Gauge
 .builder(namePrefix.concat(".max"),
 serviceExecutor, ThreadPoolTaskExecutor::getMaxPoolSize)
 .register(meterRegistry);
 
 Gauge
 .builder(namePrefix.concat(".pool"),
 serviceExecutor, ThreadPoolTaskExecutor::getPoolSize)
 .register(meterRegistry);
 
 Gauge
 .builder(namePrefix.concat(".queue"), serviceExecutor,
 executor -> executor.getThreadPoolExecutor().getQueue().size())
 .register(meterRegistry);
 
 Gauge
 .builder(namePrefix.concat(".completetask"), serviceExecutor,
 executor -> executor.getThreadPoolExecutor().getCompletedTaskCount())
 .register(meterRegistry);
 }
 }
四、指标分析
通过 PSQL 将以上各项指标展示在 Grafana 中。 Prometheus 默认 15s 拉取一次数据,对于线程池这种波定性较大的指标,建议将拉取时间调整至 10s,以便灵活且准确地反应线程池的运行情况。
- 异步线程池

- 同步线程池
由监控结果可以作出以下分析:
- 异步线程池的负担非常小,目前还没出现过队列积压的情况,可以适当减少核心线程和最大线程数;
- 同步线程池配置较为妥当,峰值期间未启用非核心线程,队列积压任务量在合理区间,请求处理效率非常高。
