基于Nacos实现动态线程池(简版+DynamicTp)

2,635 阅读6分钟

背景

在日常项目开发中,我们通常会使用线程池来处理一些并发场景,来提高任务处理的效率。但是在使用过程中,无法准确地设置线程池参数,只能在运行过程中,不断去调整参数,然后重启服务。

那如何实现在不重启服务的前提下,动态调整线程池参数呢?

简版实现

先自己造个简单的轮子~

实现方案

整体方案:基于Nacos配置中心动态调整线程池参数。

  • 线程池 ThreadPoolExecutor 提供了修改线程池参数的方法
public void setCorePoolSize(int corePoolSize)
public void setKeepAliveTime(long time, TimeUnit unit)
public void setMaximumPoolSize(int maximumPoolSize)
public void setRejectedExecutionHandler(RejectedExecutionHandler handler)
public void setThreadFactory(ThreadFactory threadFactory)
  • 直接使用Nacos动态更新策略是不可行,所以并没有这么简单,因为在Spring容器启动时,ThreadPoolTaskExecutor 已经初始化了,所以我们可以借助Nacos的Listener,在Bean初始化的时候,开启Nacos配置变更的监听,在监听逻辑里面更新线程池参数。
nacosConfigManager.getConfigService().addListener("thread-pool.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        
                    }
                });

实现代码

pom.xml

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR5</spring-cloud.version>
        <spring-cloud-alibaba.version>2.1.2.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
    </dependencies>

application.yml

server:
  port: 9001
  servlet:
    context-path: /pool
spring:
  application:
    name: dynamic-thread-pool
#线程池配置
thread:
  pool:
    corePoolSize: 50
    maxPoolSize: 100
    keepAliveSeconds: 1000
    queueCapacity: 200

bootstrap.yml

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: 7e98b650-0c03-4663-b747-b3d4848630aa
        group: DEFAULT_GROUP
      config:
        server-addr: localhost:8848
        namespace: 7e98b650-0c03-4663-b747-b3d4848630aa
        group: DEFAULT_GROUP
        file-extension: yml
        refresh-enabled: true
        prefix: thread-pool

MyDynamicThreadPool.java

    ```
@Configuration
@Data
public class MyDynamicThreadPool implements InitializingBean {

    @Value("${thread.pool.corePoolSize}")
    private int corePoolSize;
    @Value("${thread.pool.maxPoolSize}")
    private int maxPoolSize;
    @Value("${thread.pool.queueCapacity}")
    private int queueCapacity;
    @Value("${thread.pool.keepAliveSeconds}")
    private int keepAliveSeconds;

    private static ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource
    private NacosConfigManager nacosConfigManager;

    @Resource
    private NacosConfigProperties nacosConfigProperties;


    @Override
    public void afterPropertiesSet() throws Exception {
        threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
        threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
        threadPoolTaskExecutor.setThreadNamePrefix(System.currentTimeMillis() + "_");
        threadPoolTaskExecutor.setRejectedExecutionHandler(
                new RejectedExecutionHandler() {
                      @Override
                      public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                             System.out.println("队列已满,抛弃任务");
                       }
                });
        threadPoolTaskExecutor.initialize();
        nacosConfigManager.getConfigService().addListener("thread-pool.yml", nacosConfigProperties.getGroup(),
                new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        System.out.println("动态修改前-->");
                        print();
                        Yaml yaml = new Yaml(new Constructor(JSONObject.class));
                        InputStream inputStream = new ByteArrayInputStream(configInfo.getBytes());
                        JSONObject data = yaml.load(inputStream);
                        JSONObject pool = data.getJSONObject("thread").getJSONObject("pool");
                        threadPoolTaskExecutor.setCorePoolSize(pool.getInteger("corePoolSize"));
                        threadPoolTaskExecutor.setMaxPoolSize(pool.getInteger("maxPoolSize"));
                        System.out.println("动态修改后-->");
                        print();
                    }
                });

    }
    //执行任务
    public void execute(Runnable runnable){
        threadPoolTaskExecutor.execute(runnable);
    }

    public void print(){
        System.out.println("核心线程数:" + threadPoolTaskExecutor.getThreadPoolExecutor().getCorePoolSize() + " " +"最大线程数:" + threadPoolTaskExecutor.getThreadPoolExecutor().getMaximumPoolSize()
                +" " + "阻塞队列数:" + threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().size() + "/" + queueCapacity +" " + "活跃线程数:" + threadPoolTaskExecutor.getThreadPoolExecutor().getActiveCount());
    }
}

TestController.java

@RestController
public class TestController {

    @Resource
    private MyDynamicThreadPool dynamicThreadPool;

    @RequestMapping("/execute")
    public void execute(){
        dynamicThreadPool.execute(new MyRunnable());
        dynamicThreadPool.print();
    }
}

Nacos配置

测试

核心线程数:1 最大线程数:2 阻塞队列数:0/2 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:1/2 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:2
队列已满,抛弃任务
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:2
队列已满,抛弃任务
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:2
队列已满,抛弃任务
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:2

动态修改前-->
核心线程数:1 最大线程数:2 阻塞队列数:2/2 活跃线程数:2
动态修改后-->
核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:2

核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:3
核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:4
核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:5
队列已满,抛弃任务
核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:5
队列已满,抛弃任务
核心线程数:1 最大线程数:5 阻塞队列数:2/2 活跃线程数:5

参考链接

blog.csdn.net/m0_37558251…

tech.meituan.com/2020/04/02/…


下面了解一个开源项目dynamic-tp,既然有现成的轮子,就不用我们自己造轮子啦....

DynamicTp

DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为动态调参、通知报警、运行监控、三方包线程池管理等几大类。

官网地址:dynamictp.cn/

代码示例

  1. 引入相应配置中心的依赖,具体见下述 maven 依赖
  2. 配置中心配置线程池实例,配置见下述(给出的是全配置项,不用的可以删除)
  3. 启动类加 @EnableDynamicTp 注解
  4. 使用 @Resource 或 @Autowired 进行依赖注入,或通过 DtpRegistry.getDtpExecutor("name") 获取
  5. 通过以上4步就可以使用了,是不是感觉很简单啊

引入依赖

spring-cloud 场景下的 nacos 应用接入用此依赖

    <dependency>
        <groupId>cn.dynamictp</groupId>
        <artifactId>dynamic-tp-spring-cloud-starter-nacos</artifactId>
        <version>1.0.9</version>
    </dependency>

非 spring-cloud 场景下的 nacos 应用接入用此依赖

    <dependency>
        <groupId>cn.dynamictp</groupId>
        <artifactId>dynamic-tp-spring-boot-starter-nacos</artifactId>
        <version>1.0.9</version>
    </dependency>

配置文件

spring:
  application:
    name: dynamic-thread-pool
  dynamic:
    tp:
      enabled: true
      enabledBanner: true
      nacos:
        dataId: dynamic-tp.yml
        group: DEFAULT_GROUP
      executors:
        - threadPoolName: dptExecutor
          corePoolSize: 1
          maximumPoolSize: 2
          queueCapacity: 5
          keepAliveTime: 200
          allowCoreThreadTimeOut: false

我这是简化版,具体参数含义可以看官网案例:dynamictp.cn/guide/use/c…

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: 7e98b650-0c03-4663-b747-b3d4848630aa
        group: DEFAULT_GROUP
      config:
        server-addr: localhost:8848
        namespace: 7e98b650-0c03-4663-b747-b3d4848630aa
        extension-configs:
          - group: DEFAULT_GROUP
            dataId: thread-pool.yml #可忽略
            refresh: true
          - group: DEFAULT_GROUP
            dataId: dynamic-tp.yml
            refresh: true

nacos新填 dynamic-tp.yml 配置文件。

启动类

启动类添加 @EnableDynamicTp 注解

@SpringBootApplication
@EnableDiscoveryClient
@EnableDynamicTp
public class DynamicThreadPoolApplication {
    public static void main(String[] args) {
        SpringApplication.run(DynamicThreadPoolApplication.class, args);
    }
}

测试接口

@RestController
public class TestController {

    @Resource
    private ThreadPoolExecutor dtpExecutor;

    @RequestMapping("/dtp")
    public void dtp(){
        dtpExecutor.execute(new MyRunnable());
        System.out.println("核心线程数:" + dtpExecutor.getCorePoolSize() + " " +"最大线程数:" + dtpExecutor.getMaximumPoolSize()
                +" " + "阻塞队列数:" + dtpExecutor.getQueue().size()  +" " + "活跃线程数:" + dtpExecutor.getActiveCount());
    }
}

测试

动态修改nacos配置文件,

corePoolSize 1 -> 2

maximumPoolSize 2 -> 3

queueCapacity 5 -> 6

核心线程数:1 最大线程数:2 阻塞队列数:0 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:1 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:2 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:3 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:4 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:5 活跃线程数:1
核心线程数:1 最大线程数:2 阻塞队列数:5 活跃线程数:2
核心线程数:2 最大线程数:3 阻塞队列数:6 活跃线程数:2
核心线程数:2 最大线程数:3 阻塞队列数:6 活跃线程数:3

查看日志,核心线程数、最大线程数和阻塞队列大小都动态更新了。

优秀的轮子还有好多,比如Hippo4J ,使用起来和dynamic-tp差不多。Hippo4J 有无依赖中间件实现动静线程池,也有默认实现Nacos和Apollo的版本,而dynamic-tp 默认实现依赖Nacos或Apollo。


gitee 地址:gitee.com/renxiaoshi/…