乐趣区

关于java:轻量级动态线程池才是王道

大家好,我是龙台。

一、前言

最后设计 Hippo4j 的初衷是尽可能进步以及保障线程池对于线上利用的作用,所以加了很多个性化性能,这也间接导致强依赖 Hippo4j Server 我的项目。

自 Hippo4j 1.0.0 版本公布之后,一直有社区小伙伴提出雷同的一个问题, 如何可能轻量级应用动静线程池?

这不,它来了。

GitHub:https://github.com/acmenlt/dy…

Gitee:https://gitee.com/acmenlt/dyn…

随着 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 公布!

退出移动版