一、Nacos Config 事件监听
在实际项目中一般都会使用线程池解决一些异步并发问题,不过线程池核心参数很大程度上一次性进行设置,但系统运行起来总有可能出现各种各样的问题,如果修改线程池的参数则一般需要重启项目,对于生产环境而言有时无法进行重启,这种情况下就需要我们做成可动态调节的线程池。以应对突发情况。
在微服务项目中,目前使用 Nacos
作为配置中心已经是非常普遍的现象,得益于 Nacos
的灵活性以及稳定性,本文我们基于 Nacos Config
配置能力,将线程池的核心参数配置到 Nacos
中,并通过Nacos Config
的事件监听机制,触发动态参数调节。
实验开始前,需要安装好 Nacos
服务:
线程池这里使用 springframework
提供的 ThreadPoolTaskExecutor
,并通过其自带的方法进行动态调节:
二、准备测试项目并创建配置文件
首先创建一个 SpringBoot
项目,并引入 nacos config
的依赖,这里为了方便操作属性 ,同时引入 lombok
,Spring Cloud Alibaba
版本使用的 2.2.6.RELEASE
:
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建 bootstrap.yml
文件增加 nacos config
的配置,注意这里 application.name
为 demo-service
,下面创建配置文件命名时需要注意。
spring:
application:
name: demo-service
profiles:
active: thread # 引入thread配置
cloud:
nacos:
discovery:
server-addr: 192.168.244.1:8848
config:
server-addr: 192.168.244.1:8848
file-extension: yaml
这里单独引入一个 thread
配置,关于线程的配置都放在这里,文件名应该为 demo-service-thread.yaml
在 Nacos
创建 demo-service.yml
主配置文件,将服务的端口放在这里 :
server:
port: 8082
接着创建 demo-service-thread.yaml
文件,声明出核心线程数、最大线程数、线程保持时间、以及队列的存储个数
:
thread:
corePoolSize: 25
maxPoolSize: 30
keepAliveSeconds: 4
queueCapacity: 200
二、声明线程池
首先对上面的配置文件,声明一个 Properties
接收参数:
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "thread")
public class ThreadPoolProperties {
/**
* 核心线程数
*/
private Integer corePoolSize;
/**
* 最大线程数
*/
private Integer maxPoolSize;
/**
* 线程保持时间 秒
*/
private Integer keepAliveSeconds;
/**
* 队列的存储个数
*/
private Integer queueCapacity;
}
使用参数声明线程池,其中提供出调整线程池参数,和打印当前参数方法:
@Data
@Configuration
public class ThreadPoolConfig {
@Resource(name = "dynamic_thread_pool")
ThreadPoolTaskExecutor executor;
@Bean(value = "dynamic_thread_pool")
public ThreadPoolTaskExecutor dynamicThreadPool(ThreadPoolProperties properties) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(properties.getCorePoolSize());
executor.setMaxPoolSize(properties.getMaxPoolSize());
executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
executor.setQueueCapacity(properties.getQueueCapacity());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 重新设置参数
*/
public void restThreadPool(ThreadPoolProperties properties) {
executor.setCorePoolSize(properties.getCorePoolSize());
executor.setMaxPoolSize(properties.getMaxPoolSize());
executor.setKeepAliveSeconds(properties.getKeepAliveSeconds());
executor.setQueueCapacity(properties.getQueueCapacity());
}
/**
* 打印线程池参数
*/
public void printParam() {
System.out.println("当前核心线程数:" + executor.getCorePoolSize());
System.out.println("当前最大线程数:" + executor.getMaxPoolSize());
System.out.println("当前线程保持时间:" + executor.getKeepAliveSeconds());
}
}
三、添加 demo-service-thread.yaml
文件的监听
添加监听,这里我用的 PropertiesListener
,可以将修改后的配置,映射为 Properties
对象,如果想要拿到修改后配置内容的字符串可以使用 AbstractListener
或 Listener
。
@Component
public class NacosListener implements ApplicationRunner {
@Resource
NacosConfigManager nacosConfigManager;
@Resource
NacosConfigProperties nacosConfigProperties;
@Resource
ThreadPoolConfig threadPoolConfig;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("初始线程状态:");
threadPoolConfig.printParam();
//配置修改监听
nacosConfigManager.getConfigService().addListener("demo-service-thread.yaml", nacosConfigProperties.getGroup(),
new PropertiesListener(){
@Override
public void innerReceive(Properties properties) {
System.out.println("配置被修改,重新调整线程池!");
ThreadPoolProperties poolProperties = new ThreadPoolProperties();
poolProperties.setCorePoolSize(Integer.parseInt(properties.getProperty("corePoolSize")));
poolProperties.setMaxPoolSize(Integer.parseInt(properties.getProperty("maxPoolSize")));
poolProperties.setKeepAliveSeconds(Integer.parseInt(properties.getProperty("keepAliveSeconds")));
poolProperties.setQueueCapacity(Integer.parseInt(properties.getProperty("queueCapacity")));
System.out.println("开始重新调整线程池...");
threadPoolConfig.restThreadPool(poolProperties);
System.out.println("调整之后线程池的参数:");
threadPoolConfig.printParam();
}
});
}
}
这里需要注意下,我在触发监听后,手动获取的参数,上面细心地会发现,其实ThreadPoolProperties
已经加了 @RefreshScope
会自动刷新的,那这里为啥还要手动解析呢,答案就是配置更新后,会首先触发这里的监听,如果在监听中直接调用调整方法,使用 ThreadPoolProperties
的话有可能还是原来的参数,保险起见这里获取一下。
四、测试
启动项目,可以看到初始的参数:
下面修改最大线程数为 50
:
发布后,观察日志:
线程池的最大线程已经被调整为 50
。