轻量级动态线程池才是“王道”?

大家好,我是龙台。

一、前言

最初设计 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 已支持容器线程池的核心参数变更:corePoolSizemaximumPoolSizekeepAliveTime

为什么要加 Web 线程池的动态更新?两个原因:

  • 压测应用时,需要针对不同的压测流量来调整 Web 容器线程池的线程数。正常流程,调整后需要重新发布项目,无疑是比较费时费力;
  • 当 SpringBoot Java 应用响应时间变慢,并且服务器整体负载不高时,我们可以通过修改 Web 容器线程池来提高并行处理能力,以此提高响应时间。

当然,正常来说,线上的容器线程池配置是通过压测后得出的最优值。所以,这个功能在线上应该谨慎使用,或者说尽量不在线上使用。

3. 动态线程池报警策略

为了让线程池运行出现问题,及时通知到相关负责人,Hippo4j 针对线程池做了四种定制化报警策略:

  • 活跃度报警:假设设置线程池活跃度报警阈值为 80%,最大线程数 10。当线程数达到 8 发起报警;
  • 阻塞队列容量报警:假设设置容量报警阈值为 80%,阻塞队列容量 100。当容量达到 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 项目快速接入动态线程池。

总结下接入步骤:

  1. Pom 中引入 Hippo4j Core 包依赖;
  2. 启动类上添加动态线程池启用注解;
  3. 配置中心(Nacos 或 Apollo)添加动态线程池配置;
  4. 项目中以 Spring Bean 的形式创建动态线程池 。

四、常见问题

1. 项目关闭时,如何保障线程池中任务全部完成

答:借鉴了 Spring 封装的线程池框架。构建动态线程池时,指定 waitForTasksToCompleteOnShutdownawaitTerminationMillis

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 发布!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容