跳至主要內容

自实现一个动态线程池组件

chenxi编程Java并发编程线程池开源大约 5 分钟

自实现一个动态线程池组件

在上一篇文章,Java 线程池实现原理中,基于美团技术团队的文章以及 JDK17 中 ThreadPoolExecutor 的源码对 Java 线程池的实现原理进行了分析。

而在这一篇文章中,将接续美团技术团队的文章中第三部分:线程池在业务中的实践,去自实现一个动态线程池组件。

1 Why 动态线程池

在实现动态线程池组件前,首先要明白,为什么需要动态线程池?或者说动态线程池解决了什么问题?

假设在一个业务场景中,需要使用到线程池,那么最大线程数、核心线程数应该设置为多大合适呢?网上也有着一些公式,例如:

  • 计算密集型任务,核心线程数应为 CPU 核数 + 1(减少任务调度的开销)
  • IO 密集型任务,核心线程数应为 CPU 核数 * 2(减少 IO 等待浪费,提供吞吐量)

这样的计算公式未免过于笼统,也不够贴切实际。

一种比较好的方案是,根据过往经验,在业务场景下进行压测,找到合适的参数配置,但实际的业务场景也有发生变化的可能。

从上述内容来看,线程池参数不好确定是在使用线程池时的一大问题,而动态线程池就是为了解决线程池参数不好配置的问题而出现的,不要求在创建线程池时就将参数的设置一步到位,而是能够在实际的使用过程中做到动态调整。

2 实现原理

动态线程池的实现原理并不复杂,主要思路就是用到 ThreadPoolExecutor 所提供的 setter 方法,例如,setCorePoolSizesetMaximumPoolSize,当修改参数信息时,线程池内部会处理好当前状态做到平滑修改。

但既然修改线程池参数的信息,就应该要对线程池当前的参数信息进行监控,也就是根据 ThreadPoolExecutor 所提供的 getter 方法,让开发人员能够实时获取线程池当前的参数信息,以便在需要时做出相应的修改。

故,动态线程池的实现主要依据 2 点:

  1. 通过 ThreadPoolExecutor 所提供的 setter 方法,实现线程池参数的修改。
  2. 通过 ThreadPoolExecutor 所提供的 getter 方法,实现线程池参数的监控。

3 初版实现

依据上文提到的实现原理,尝试实现了一个初版的动态线程池组件。

img

主要的实现思路:

  • 后端服务需将要使用到的线程池注册到 Spring 容器中。
  • 动态线程池组件从 Spring 中获取线程池,通过 getter 获取线程池参数信息,也可基于 setter 对线程池参数进行修改。
  • 前端与动态线程池组件之间维护一条 WebSocket 连接,动态线程池组件基于 Spring Task 定时向前端传输线程池参数供前端展示,而当开发人员通过前端界面对线程池参数信息进行修改后,前端需要将修改后的参数信息传递到动态线程池组件。
  • 基于 SpringBoot 的自动装配,当其他项目引入动态线程池组件后,组件即可生效,无需特别配置。

动态线程池组件的具体实现代码见 Git 仓库,这里主要介绍下使用方式及效果:

  1. 要使用动态线程池组件的模块引入动态线程池组件的依赖:
<dependency>
    <groupId>com.chenxi</groupId>
    <artifactId>dynamic-slim</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  1. 将动态线程池注册到 Spring 中,在需要使用的地方再从 Spring 中拿取就好,例:
@Configuration
public class UserPoolConfig {
    @Bean
    public DynamicThreadPool userThreadPool() {
        return new DynamicThreadPool(10, 20, 10L, TimeUnit.SECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    }
}

// 需要使用时,从 Spring 中进行注入
@Autowired
private DynamicThreadPool userThreadPool;
  1. 通过前端可实时查看动态线程池的参数信息,也可直接对核心线程数与最大线程数进行修改:

img

4 后续优化方向

4.1 引入分布式配置中心

在当前的实现中,前端与组件的 WebSocket 连接是写死的状态,一个前端页面配套一个组件服务,且前端与组件需要在同一 IP 地址下。

当在微服务架构下,当有多个服务要使用动态线程池组件时,就需要配套多个前端页面,这并不合理。

所以,后续的改进思路就是,引入分布式配置中心,如 Nacos,动态线程池组件将线程池参数信息传输到配置中心中集中进行管理。

可直接基于 Nacos 进行观测与修改,修改主要是基于 Nacos 的回调机制。

如果为了方便用户使用与观测,也可再抽取出一个前端与服务端,服务端拉取 Nacos 中的参数信息定时传递给前端,用户通过前端修改参数后,将参数传递给服务端,服务端再对 Nacos 配置中心的信息进行修改,以触发对应的动态线程池组件完成最终的参数修改。

4.2 引入告警机制

目前动态线程池组件仅实现了监控与修改的功能,但缺乏对用户的告警机制。

例如,当线程池的活跃度(activeCount/maximumPoolSize)过高,线程池触发拒绝策略、阻塞队列中等待任务过多时,应对用户实现告警,将信息传递给用户,而不是让用户自行监控。

5 参考资料

  1. Java线程池实现原理及其在美团业务中的实践open in new window
上次编辑于: