大家好,我是龙台。
一、前言
最初设计 Hippo4j 其初衷是尽可能提高和保证线程池在线应用中的作用,因此增加了许多个性化功能,间接导致强烈依赖 Hippo4j Server 项目。
自 Hippo4j 1.0.0 版本发布后,社区朋友不断提出同样的问题,动态线程池如何轻量级使用?
不,它来了。
GitHub:https://github.com/acmenlt/dynamic-threadpool
Gitee:https://gitee.com/acmenlt/dynamic-threadpool
随着 Hippo4j 1.1.0 除了迭代输出原始功能外,版本的发布还添加了额外的使用模式:轻量级动态线程池依赖于配置中心
,将 Hippo4j 源代码从一种使用模式分为两种。
两种模式共享一套核心源代码,保留了基本功能和大家关注的功能。模块命名为:Hippo4j Core。
二、Hippo4j Core
所谓一图胜千言
只要您的项目中有配置中心,请参考 hippo4j-core-spring-boot-starter
之后,您可以使用上述功能。
1. 更新动态线程池参数
当客户端项目启动时,请求配置中心进行动态线程池配置,并在获得配置后创建 DynamicThreadPool
线程池。
并向配置中心发起监听事件,当配置中心中配置发生变更时,监听事件实时修改项目中的线程池参数。
如果动态线程池配置在配置中心发生变化,则在日志中打印变更信息:
[MESSAGE-CONSUME] Changed thread pool. coreSize :: [1 => 10] maxSize :: [1 => 20] queueType :: [ResizableCapacityLinkedBlockIngQueue => ResizableCapacityLinkedBlockIngQueue] capacity :: [1024 => 2048] keepAliveTime :: [1000 => 1000] executeTimeOut :: [600 => 600] rejectedType :: [DiscardOldestPolicy => DiscardOldestPolicy] allowCoreThreadTimeOut :: [false => false]
同时,通过消息推送通知相关负责人。目前,通知平台已支持钉钉、企业微信、飞书三种常用办公软件,以企业微信群聊机器人为例:
2. Web 更新线程池参数
SpringBoot 内置三种 Web 容器:Tomcat、Jetty、Undertow。
Hippo4j Core 变更已支持容器线程池的核心参数:corePoolSize
、maximumPoolSize
、keepAliveTime
。
为什么要加 Web 动态更新线程池?有两个原因:
- 压测应用时,需要调整不同的压测流量 Web 容器线程池的线程数。正常流程,调整后需要重新发布项目,无疑费时费力;
- 当 SpringBoot Java 当应用响应时间变慢,服务器整体负载不高时,我们可以修改 Web 提高并行处理能力,从而提高响应时间。
当然,正常情况下,在线容器线程池配置是压力测量后获得的最佳值。因此,此功能应在线小心使用,或尽量不在线使用。
3. 动态线程池报警策略
及时通知相关负责人,使线程池运行出现问题,Hippo4j 为线程池制定了四种定制报警策略:
活跃度报警
:假设线程池活动报警阈值为 最大线程数为80% 10.当线程数达到时 8 发起报警;阻塞队列容量报警
:假设容量报警阈值为 80%阻塞队列容量 当容量达到时 80 发起报警;拒绝任务报警
:当线程池无法执行任务并开始执行拒绝策略时,报警;报警执行时间
:假设线程池超时设置 1000ms,任务执行时间超过 1000ms 发起报警。
有很多问题的朋友会问,如果线程池 频繁拒绝任务或频繁超时
,不是要被信息轰炸吗?
当设置报警间隔时,优化报警策略,线程池 报警类型
两个维度只会发出报警信息。
例如,有一个线程池 ID:message-consum 线程池设有报警间隔 5 分钟。
换句话说,几个报警纬度,活动,阻塞队列容量,拒绝任务,执行时间,message-consum 线程池在 每种类型最多5分钟内发出报警通知。
已支持钉钉、企业微信、飞书群机器人报警。企业微信机器人示例如下:
上图中的链路信息只存在于加班报警中,通过链路信息更容易定位到线程池任务执行缓慢的原因。
三、代码示例
Nacos 或 Apollo 任选配置中心之一。
SpringBoot Pom 文件引入 Hippo4j Core Maven 坐标。
<dependency> <groupId>cn.hippo4j</groupId> <artifactId>hippo4j-core-spring-boot-starter</artifactId> <version>1.1.0</version> </dependency>
添加启动类 @EnableDynamicThreadPool
注解。
@SpringBootApplication @EnableDynamicThreadPool public class ExampleApplication { public static void main(String[] args) { SpringApplication.run(ExampleApplication.class, args); } }
添加到配置中心 spring.dynamic.thread-pool
前缀的配置如下:
server: port: 8090 servlet: context-path: /example spring: profiles: active: dev dynamic: thread-pool: enable: true # 动态线程池是否打开 banner: true # 是否打印 banner collect: true # 是否开启线程池数据采集,对接 Prometheus check-state-interval: 3 # 检查线程池状态,是否符合报警条件,单位秒 notify-platforms: # 通知报警平台,支持多个,或者选择一个 - platform: 'WECHAT' # 企业微信 secret-key: 1d307bfa-815f-4662-a2e5-99415e947bb8 - platform: 'DING' # 钉钉 secret-key: 56417ebba6a27ca352f0de77a2ae9da66d01f39610b5ee8a6033c60ef9071c55 - platform: 'LARK' # 飞书 secret-key: 2cbf2808-3839-4c26-a04d-fd201dd51f9e nacos: # nacos apollo 任选其一 data-id: xxx group: xxx apollo: namespace: xxxx config-file-type: yml # 配置中心文件格式 executors: - thread-pool-id: message-consume' # 线程池标识
core-pool-size: 1 # 核心线程数
maximum-pool-size: 1 # 最大线程数
queue-capacity: 1 # 阻塞队列大小
execute-time-out: 1000 # 执行超时时间,执行任务时间超过此时间发起报警
blocking-queue: 'LinkedBlockingQueue' # 阻塞队列名称,参考 QueueTypeEnum,支持 SPI
rejected-handler: 'AbortPolicy' # 拒绝策略名称,参考 RejectedPolicies,支持 SPI
keep-alive-time: 1024 # 线程存活时间,单位秒
allow-core-thread-time-out: true # 是否允许核心线程超时
thread-name-prefix: 'message-consume' # 线程名称前缀
notify: # 通知配置
is-alarm: true # 是否报警
active-alarm: 80 # 活跃度报警阈值;假设线程池最大线程数 10,当线程数达到 8 发起报警
capacity-alarm: 80 # 容量报警阈值;假设阻塞队列容量 100,当容量达到 80 发起报警
interval: 8 # 报警间隔,同一线程池下同一报警纬度,在 interval 时间内只会报警一次,单位分钟
receives: # 任选其一
DING: 'xxx' # 手机号
WECHAT: 'xxx' # 填写企业微信用户 ID(填写其它将无法达到 @ 效果)
LARK: 'xxx' # 填写 ou_开头的用户唯一标识,否则只能普通 @
使用 Hippo4j ThreadPoolBuilder 构建动态线程池。
import cn.hippo4j.core.executor.DynamicThreadPool;
import cn.hippo4j.core.executor.support.ThreadPoolBuilder;
@Bean
@DynamicThreadPool
public ThreadPoolExecutor dynamicThreadPoolExecutor() {
String consumeThreadPoolId = "message-consume";
return ThreadPoolBuilder.builder()
.threadFactory(consumeThreadPoolId)
.dynamicPool()
.build();
}
按照 Spring Bean 注入的方式使用动态线程池即可。
@Resource
private ThreadPoolExecutor dynamicThreadPoolExecutor;
dynamicThreadPoolExecutor.execute(() -> xxx);
没了,是不是很 easy?我大致试了下,不到两分钟的时间
,就能让你的 SpringBoot 项目快速接入动态线程池。
总结下接入步骤:
- Pom 中引入 Hippo4j Core 包依赖;
- 启动类上添加动态线程池启用注解;
- 配置中心(Nacos 或 Apollo)添加动态线程池配置;
- 项目中以 Spring Bean 的形式创建动态线程池 。
四、常见问题
1. 项目关闭时,如何保障线程池中任务全部完成
答:借鉴了 Spring 封装的线程池框架。构建动态线程池时,指定 waitForTasksToCompleteOnShutdown
和 awaitTerminationMillis
import cn.hippo4j.core.executor.DynamicThreadPool;
import cn.hippo4j.core.executor.support.ThreadPoolBuilder;
@Bean
@DynamicThreadPool
public ThreadPoolExecutor dynamicThreadPoolExecutor() {
String consumeThreadPoolId = "message-consume";
return ThreadPoolBuilder.builder()
.threadFactory(consumeThreadPoolId)
.waitForTasksToCompleteOnShutdown(true)
.awaitTerminationMillis(5000L)
.dynamicPool()
.build();
}
这两个参数什么意思呢?
waitForTasksToCompleteOnShutdown
:是否在关闭线程池时等待任务完成,这里我们设置 true;awaitTerminationMillis
:等待任务完成的时间,单位毫秒。
问题很多的小伙伴可能就问了:为啥要有 awaitTerminationMillis
这个参数?直接等待全部任务完成不就行了。
线程池中都是执行很快的任务可能是没问题。但是,如果线程池里面都是耗时的任务呢?
停止项目时等个几分钟甚至更长时间是无法忍受的。这个需要根据大家项目的实际情况评估。
2. 动态线程池是否可以传递上下文参数
可以的,同样是借鉴 Spring 线程池框架。实现 TaskDecorator
接口,并在构建动态线程池时指定。
import cn.hippo4j.core.executor.DynamicThreadPool;
import cn.hippo4j.core.executor.support.ThreadPoolBuilder;
@Bean
@DynamicThreadPool
public ThreadPoolExecutor dynamicThreadPoolExecutor() {
String consumeThreadPoolId = "message-consume";
return ThreadPoolBuilder.builder()
.threadFactory(consumeThreadPoolId)
.waitForTasksToCompleteOnShutdown(true)
.awaitTerminationMillis(5000L)
.taskDecorator(new TaskDecoratorTest.ContextCopyingDecorator())
.dynamicPool()
.build();
}
3. Hippo4j Core 和 Hippo4j Server 转换麻烦么
有些小伙伴最初用的 Hippo4j Core,觉得功能并不满足使用,想要使用 Hippo4j Server,问我如何转换?
其实这里非常简单。从项目上来说:代码无需任何改变
,把 Pom 文件中的依赖坐标改下就可以。
其次就是将配置中心里的配置迁移到 Hippo4j 的控制台,将线程池记录创建才出来即可。
五、文末总结
文章介绍了 Hippo4j 新增的一种使用模式:依赖配置中心的轻量动态线程池的实现
。
不太好评价 Hippo4j Server 和 Hippo4j Core 的好坏。一个是功能更强大,一个是引入更加轻量,使用上具体如何,交给使用者来评价。
如果项目中使用了 配置中心以及线程池
的话,强烈推荐大家引入到项目中试一试,为项目线上的稳定性多了一份保障。
因为个人能力有限,项目中难免会有考虑不到或待优化的地方,各位小伙伴有兴趣提交 PR 修复。
如果文章对您有所帮助,。
本文由博客一文多发平台 OpenWrite 发布!