背景
在日常项目开发中,我们通常会使用线程池来处理一些并发场景,来提高任务处理的效率。但是在使用过程中,无法准确地设置线程池参数,只能在运行过程中,不断去调整参数,然后重启服务。
那如何实现在不重启服务的前提下,动态调整线程池参数呢?
简版实现
先自己造个简单的轮子~
实现方案
整体方案:基于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
参考链接
下面了解一个开源项目dynamic-tp,既然有现成的轮子,就不用我们自己造轮子啦....
DynamicTp
DynamicTp 是一个基于配置中心实现的轻量级动态线程池管理工具,主要功能可以总结为动态调参、通知报警、运行监控、三方包线程池管理等几大类。
官网地址:dynamictp.cn/
代码示例
- 引入相应配置中心的依赖,具体见下述 maven 依赖
- 配置中心配置线程池实例,配置见下述(给出的是全配置项,不用的可以删除)
- 启动类加 @EnableDynamicTp 注解
- 使用 @Resource 或 @Autowired 进行依赖注入,或通过 DtpRegistry.getDtpExecutor("name") 获取
- 通过以上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/…