java动态线程池原理及实现

2,725 阅读8分钟

一、背景

尽管经过谨慎的评估,仍然不能够保证一次计算出来合适的参数,当我们对线程池参数进行调整时,需要重新对应用进行部署才能生效,那么我们是否可以将修改线程池参数的成本降下来,这样至少可以发生故障的时候可以快速调整从而缩短故障恢复的时间呢?基于这个思考,我们是否可以将线程池的相关参数从代码或者配置文件中迁移到分布式配置中心如zookeeper、appllo、nacos上,从而实现线程池参数可动态配置和即时生效。

线程池参数动态化前后的参数修改流程对比如下:

动态线程池对比图.png

二、整体设计

动态化线程池的核心设计包括以下三个方面:

  1. 简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。考虑到在实际应用中我们获取并发性的场景主要是两种:(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。所以线程池只需要提供这三个关键参数的配置

  2. 参数可动态修改:为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。将线程池的配置放置在平台侧,允许开发同学简单的查看、修改线程池配置。

  3. 增加线程池监控:对某事物缺乏状态的观测,就对其改进无从下手。在线程池执行任务的生命周期添加监控能力,帮助开发同学了解线程池状态。 整体设计.png

三、模块分类

动态线程池主要分为以下几大模块:

动态线程池整体设计.png

四、功能架构

动态化线程池提供如下功能:

1. 动态调整线程池参数

支持线程池参数动态调整、界面化操作;包括修改线程池核心大小、最大核心大小、队列长度、队列类型等;参数修改后及时生效

image.png

原理:通过线程池可视化管理平台对线程池参数进行更改,推送到nacos配置中心,dynamic-thread-pool-autoconfigure模块会对nacos监听,实时感应线程池参数变化,然后设置响应参数,实时生效。ThreadPoolExecutor提供了setCorePoolSize()、setMaximumPoolSzie()、setRejectedExecutionHandle()等方法,可对线程池进行动态参数配置。但是阻塞队列本身没有对外提供修改队列大小的方法,那我们需要继承AbstractQueue然后增加一个setCapacity()方法即可实现改功能。

image.png

image.png

2. 实时监控

可以实时查看线程池运行状态,包括当前线程活跃数、队列大小、完成线程数等指标。

image.png

3. 负载告警

除了参数动态化之外,为了更好地使用线程池,我们需要对线程池的运行状况有感知,比如当前线程池的负载是怎么样的?分配的资源够不够用?任务的执行情况是怎么样的?基于对这些问题的思考,动态化线程池提供了多个维度的监控和告警能力,包括:线程池活跃度、Reject异常、线程池内部统计信息等等,既能帮助用户从多个维度分析线程池的使用情况,又能在出现问题第一时间通知到用户,从而避免故障或加速故障恢复。

线程池负载关注的核心问题是:基于当前线程池参数分配的资源够不够。对于这个问题,我们可以从事前和事中两个角度来看。事前,线程池定义了“活跃度”这个概念,来让用户在发生Reject异常之前能够感知线程池负载问题,线程池活跃度计算公式为:线程池活跃度 = activeCount/maximumPoolSize。这个公式代表当活跃线程数趋向于maximumPoolSize的时候,代表线程负载趋高。事中,也可以从两方面来看线程池的过载判定条件,一个是发生了Reject异常,一个是队列中有等待任务(支持定制阈值)。以上两种情况发生了都会触发告警,告警信息会通过通过邮箱、钉钉、企业微信等方式推送给服务所关联的负责人。并且通过管理平台也能查看历史告警记录。

image.png

image.png

五、接入方式

1、Maven 引用方式

<!--在pom.xml中添加依赖-->
   <dependency>
      <groupId>com.cyy.threadpool</groupId>
      <artifactId>cxm-dynamic-thread-pool-starter</artifactId>
      <version>1.0-SNAPSHOT</version>
   </dependency>

2、配置文件

## application.yml
nacos:
 config:
   bootstrap:
     enable: true
     log:
       enable: true
   type: yaml
   server-addr: mse-6fa4aba2-nacos-ans.mse.aliyuncs.com:8848
   namespace: bc619399-bc3f-4af1-8b11-bbb5d964e528
   context-path: nacos
   data-id: fcs-web
   auto-refresh: true
   group: DEFAULT_GROUP
  ##在nacos原有配置中增加一下配置
   ext-config:
     - auto-refresh: true
       config-long-poll-timeout: 46000
       config-retry-time: 30000
       data-id: fcs-web-dynamic-thread-pool ##当前应用线程池的data-id,对应下方线程池配置
       enable-remote-sync-config: true
       group: dynamic_thread_pool   ##固定不变
       max-retry: 10
       type: yaml



## dynamic-thread-pool.yml 为了动态刷新线程池配置,修改线程池配置实时生效
dynamic:
 threadpools:
   nacosDataId: fcs-web-dynamic-thread-pool
   nacosGroup: dynamic_thread_pool
   nacosWaitRefreshConfigSeconds: 1
   db:
     type: mysql
   alarm:
     enabled: true
     alarmTimeInterval: 2
     apiAlarmUrl: https://treecem.com/api/test
     wxRobotApiUrl: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=192547a4-8d93-4b55-a885
     dingDing:
       accessToken: widaiwe9123h129
       secret: 2381je912hj39123ij
     email:
       host: smtp.exmail.qq.com
       username: admin@qq.com
       password: 9123nbidaawiudi
       port: 587
       properties:
       toUsers:
       - laoduan@qq.com
       - mazong@qq.com
   executors:
   - threadPoolName: dynamic-thread-pool
     owner: admin
     alarmEnable: true
     corePoolSize: 5
     maximumPoolSize: 10
     queueCapacity: 10
     keepAliveTime: 0
     queueType: DynamicLinkedBlockingQueue
     rejectedExecutionType: AbortPolicy
     activeRateCapacityThreshold: 80
     queueCapacityThreshold: 10
     fair: true

3、参数描述

参数名必须默认值说明
dynamic.threadpools.nacosDataIdnacos配置中心的dataId,如果要实现动态调整线程池参数,则必须配置
dynamic.threadpools.nacosGroupdynamic_thread_poolnacos配置中心的nacosGroup,固定为dynamic_thread_pool,不能修改,如果要实现动态调整线程池参数,则必须配置
dynamic.threadpools.db.type持久化类型 目前支持mysql mongodb
dynamic.threadpools.alarm.enabledfalse是否开启预警推送
dynamic.threadpools.alarm.alarmTimeInterval1分钟预警推送间隔时间,单位为分钟
dynamic.threadpools.alarm.apiAlarmUrl预警推送的url,为post,json请求
dynamic.threadpools.alarm.wxRobotApiUrl企业微信机器人推送地址
dynamic.threadpools.alarm.dingDing.accessToken钉钉token
dynamic.threadpools.alarm.dingDing.secret钉钉secret
dynamic.threadpools.alarm.email.hosthost
dynamic.threadpools.alarm.email.username邮箱账号
dynamic.threadpools.alarm.email.password邮箱密码
dynamic.threadpools.alarm.email.port端口
dynamic.threadpools.alarm.email.properties额外参数
dynamic.threadpools.alarm.email.toUsers接收人邮箱列表
dynamic.threadpools.executors.threadPoolNameDynamic-Thread-Pool线程池名称
dynamic.threadpools.executors.ower线程池负责人
dynamic.threadpools.executors.alarmEnablefalse是否开启预警
dynamic.threadpools.executors.corePoolSize1常驻线程数大小
dynamic.threadpools.executors.maximumPoolSizecpu核心数最大线程数,默认为cpu核心数
dynamic.threadpools.executors.queueCapacity1000任务队列大小
dynamic.threadpools.executors.keepAliveTime0多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止
dynamic.threadpools.executors.queueTypeDynamicLinkedBlockingQueue任务队列类型,DynamicLinkedBlockingQueue(可动态调整大小)、LinkedBlockingQueue、SynchronousQueue、ArrayBlockingQueue、DelayQueue、LinkedTransferQueue、LinkedBlockingDeque、PriorityBlockingQueue
dynamic.threadpools.executors.rejectedExecutionTypeDISCARD_POLICY拒绝策略,CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy
dynamic.threadpools.executors.activeRateCapacityThreshold-1活跃度预警阈值,默认-1,为不预警
dynamic.threadpools.executors.queueCapacityThreshold-1队列堆积预警阈值,默认-1,为不预警
dynamic.threadpools.executors.fairfalse是否公平策略,当queueType为SynchronousQueue时配置生效

4、告警信息

返回信息
[告警应用]:shop-service
[ip]:127.0.0.1
[线程池名称]:dynamic-thread-pool
[告警原因]:activeCount/maximumPoolSize值为(100),触达阈值(80)
[线程池参数]:
threadPoolName:dynamic-thread-pool
corePoolSize:5
maximumPoolSize:10
queueCapacity:10
keepAliveTime:0
unit:MILLISECONDS
rejectedExecutionType:AbortPolicy
queueType:DynamicLinkedBlockingQueue
fair:false
queueCapacityThreshold:10
activeRateCapacityThreshold:80
queueSize:10
activeCount:10
activeRate:100
completeTaskCount:0
largestPoolSize:10
rejectCount:0
[业务负责人]:admin
[告警间隔]:2分钟
参数说明
参数说明
threadPoolName线程池名称
corePoolSize常驻线程数
maximumPoolSize最大线程数
queueCapacity任务队列最大大小
keepAliveTime线程存活时间
unit时间单位
rejectedExecutionType任务拒绝策略
queueType队列类型
fair是否公平策略
queueCapacityThreshold队列任务堆积阈值
activeRateCapacityThreshold活跃度预警阈值
queueSize队列长度
activeCount活跃线程数
activeRate活跃度
completeTaskCount完成任务数
largestPoolSize任务队列历史最大数
rejectCount任务拒绝数

5、使用

public class DynamicThreadPoolDemo{

    @Autowired
    private DynamicThreadPoolManager dynamicThreadPoolManager;
    /**
     * 
     * @author admin
     * @date 2021/04/10 17:37
     * @param threadPoolName 在配置中心配置过的线程池名称
     */
    public void submitTask(String threadPoolName){
        dynamicThreadPoolManager.getThreadPoolExecutor(threadPoolName).execute(() -> {
            // TODO 处理任务
        }, "我是一个无敌牛逼的任务");
    }
    
    /**
     * 实时获取线程池的运行信息
     * @author admin
     * @date 2021/04/10 17:37
     * @param threadPoolName 在配置中心配置过的线程池名称
     */
    public ThreadPoolIndicatorInfo getThreadPoolIndicatorInfo(String threadPoolName){
        return dynamicThreadPoolManager.getThreadPoolIndicatorInfo(threadPoolName);
    }
    
}

// 自定义告警信息处理类
@ThreadPoolAlarmListener
public class MyThreadPoolAlarmNotify implements ThreadPoolAlarmNotify{

    @Override
    public void alarmNotify(AlarmMessage alarmMessage){
        // TODO 自己对告警信息进行处理
       log.info("message:", alarmMessage.getMessage());
    }
    
}

// 自定义线程池运行信息监听类,默认每隔5秒会收到信息
@ThreadPoolIndicatorListener
public class MyThreadPoolIndicatorNotify implements ThreadPoolIndicatorNotify {
    @Override
    public void indicatorNotify(List<ThreadPoolIndicatorInfo> indicatorInfoList) {
        // TODO 对指标信息进行处理
        System.out.println(JSON.toJSONString(indicatorInfoList));
    }
}

源码传送门

六、说明

本项目参考了美团的一篇线程池的文章实现的,源码是从公司早期版本拉取出来的,可以正常使用,但是尚有不完善的地方!