背景
廢話不多說,做這個監控的背景很簡單,我們的項目都是以spring boot框架為基礎開發的,代碼里所有的異步線程都是通過@Async標簽標注的,並且標注的時候都是指定對應線程池的,如果不知@Async標注的,可以參考@Async異步線程池用法總結, 如果你用的不是spring,就參考上文提到的公眾號文章就好。再回到背景,我們當時經常遇到的問題就是這些線程池的隊列滿了之后,新的異步任務無法添加進去的錯誤,因此我們想對所有這種類型的線程池進行監控。
監控方式
再來介紹一下我們最終采用的方式 —— spring boot + statsd, 通過添加以下代碼就可以使statd能夠讀取/metrics接口中所有的指標並發送監控數據到statsd后端,statsd並不是本篇文章的重點,之后有時間再細講這部分的配置。
@Bean
public MetricsEndpointMetricReader metricsEndpointMetricReader(final MetricsEndpoint metricsEndpoint) {
return new MetricsEndpointMetricReader(metricsEndpoint);
}
代碼及效果
我們所需要做的就是向/metrics接口添加線程池的指標,慶幸的是spring boot提供了良好的擴展機制,只需要實現PublicMetrics接口,實現其中的metrics方法你就能在/metrics中看到你新增的指標,上代碼:
@Component
public class AsyncThreadPoolMetrics implements PublicMetrics {
public static final Logger LOG = LoggerFactory.getLogger(AsyncThreadPoolMetrics.class);
private Map<String, ThreadPoolTaskExecutor> targetAsyncThreadPool;
private static final String pattern = "async.task.%s.%s";
@Autowired
ApplicationContext context;
@Override
public Collection<Metric<?>> metrics() {
try {
if(targetAsyncThreadPool == null || targetAsyncThreadPool.size() == 0) {
targetAsyncThreadPool = context.getBeansOfType(ThreadPoolTaskExecutor.class);
}
Collection<Metric<?>> result = new ArrayList<>();
for (Map.Entry<String, ThreadPoolTaskExecutor> entry: targetAsyncThreadPool.entrySet()) {
ThreadPoolTaskExecutor executor = entry.getValue();
result.add(new Metric<Number>(String.format(pattern, entry.getKey(), "aciveCount"), executor.getActiveCount(), new Date()));
result.add(new Metric<Number>(String.format(pattern, entry.getKey(), "currentPoolSize"), executor.getPoolSize(), new Date()));
result.add(new Metric<Number>(String.format(pattern, entry.getKey(), "maxPoolSize"), executor.getMaxPoolSize(), new Date()));
result.add(new Metric<Number>(String.format(pattern, entry.getKey(), "currentSizeInQueue"),
executor.getThreadPoolExecutor().getQueue().size(), new Date()));
}
return result;
} catch (Exception e) {
LOG.error("Async thread pool monitor exception", e);
}
return Collections.emptyList();
}
}
從上面的代碼可以看出,我們監控的是多個線程池,原因很簡單,為了使多種類型異步任務不互相影響,我們配置了多個線程池,多個線程池有不同的名字區分。
其實代碼很簡單,首先從容器中找到所有實現ThreadPoolTaskExecutor的bean,也就是我們要監控的線程池對象,取出正在活動的線程數,線程池的大小,配置最大可容納的線程數以及當前排隊的任務,當然還有很多其他指標,大家都可以試試,我只是選出當時我們需要的指標。當我們訪問此時應用的/metrics,效果如下,紅色框標注的便是我們添加的指標: