不改代码,不重启,我把线上线程池的核心数从 10 改成了 100
你的团队是否在为线程池参数的设置而烦恼?
• 大促前: “流量要暴增了,赶紧把所有核心应用的线程池参数调大三倍,发版!”
• 大促后: “流量降下来了,为了节省资源,再把参数调回去,又得发一次版...”
• 日常运维: “这个服务的线程池好像有点小,队列都满了,但改配置重启影响太大,先忍忍吧...”
线程池的静态配置,让我们的应用变得僵化,无法灵活应对流量的变化。理想的系统应该具备在运行时动态调整线程池参数的能力。
本文将带你从 0 到 1,构建一个基于动态配置中心、对业务代码零侵入的动态线程池 Starter。只需引入依赖并在配置中心动动手指,就能实时调整线上应用的线程池配置,无需重启。
1. 项目设计与核心思路
我们的 dynamic-threadpool-starter
目标如下:
1. 动态调整: 允许在运行时,通过外部配置中心(以 Nacos 为例)修改
ThreadPoolTaskExecutor
的核心参数,如corePoolSize
,maxPoolSize
,queueCapacity
。2. 自动发现: 自动发现 Spring 容器中所有需要被动态管理的线程池 Bean。
3. 实时监控: (可选增强)自动将动态线程池的核心指标(如活跃线程数、队列大小)暴露给 Micrometer,以便在 Prometheus/Grafana 中监控。
4. 无侵入: 业务开发者只需像平常一样定义和使用
ThreadPoolTaskExecutor
Bean,无需关心动态调整的逻辑。
核心实现机制:动态配置中心 + 配置监听器
1. 配置中心: 我们将线程池的配置参数存储在 Nacos 中。
2. 监听器: Starter 的核心是一个 Nacos 的配置监听器 (
Listener
)。3. 运行时变更: 当 Nacos 中的配置发生变更时,监听器会被触发。它会解析新的配置,并从 Spring 的
ApplicationContext
中找到对应名称的ThreadPoolTaskExecutor
Bean,然后调用其setCorePoolSize()
等方法,将新的参数实时应用上去。
2. 创建 Starter 项目与核心组件
我们采用 autoconfigure
+ starter
的双模块结构。
步骤 2.1: 依赖 (autoconfigure
模块)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</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-configuration-processor</artifactId><optional>true</optional></dependency>
</dependencies>
步骤 2.2: 实现核心服务 (ThreadPoolDynamicAdjuster
)
这是整个 Starter 的技术核心,负责监听配置并动态调整 Bean。
package com.example.threadpool.autoconfigure;import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;public class ThreadPoolDynamicAdjuster {private static final Logger log = LoggerFactory.getLogger(ThreadPoolDynamicAdjuster.class);@Autowiredprivate ConfigService configService; // Nacos Config Service@Autowiredprivate ConfigurableApplicationContext applicationContext;@Autowiredprivate DynamicThreadPoolProperties properties;private final ObjectMapper objectMapper = new ObjectMapper();@PostConstructpublic void init() throws Exception {// 项目启动时,注册一个监听器configService.addListener(properties.getDataId(), properties.getGroup(), new Listener() {@Overridepublic Executor getExecutor() {return null; // 使用默认}@Overridepublic void receiveConfigInfo(String configInfo) {log.info("接收到线程池配置变更:\n{}", configInfo);try {// 解析收到的 JSON/YAML 配置Map<String, ThreadPoolProperties> poolConfigs = parseConfig(configInfo);poolConfigs.forEach((beanName, props) -> {updateThreadPool(beanName, props);});} catch (Exception e) {log.error("动态更新线程池配置失败", e);}}});}private void updateThreadPool(String beanName, ThreadPoolProperties props) {try {ThreadPoolTaskExecutor executor = applicationContext.getBean(beanName, ThreadPoolTaskExecutor.class);log.info("更新线程池 '{}' -> coreSize: {}, maxSize: {}", beanName, props.getCorePoolSize(), props.getMaxPoolSize());executor.setCorePoolSize(props.getCorePoolSize());executor.setMaximumPoolSize(props.getMaxPoolSize());// 注意:队列容量 (queueCapacity) 通常不能在运行时动态修改,因为它涉及到队列的重新创建。// 但核心和最大线程数是可以的。} catch (Exception e) {log.error("找不到或更新线程池Bean '{}' 失败", beanName, e);}}// ... parseConfig and ThreadPoolProperties model class ...private static class ThreadPoolProperties {private int corePoolSize;private int maxPoolSize;// Getters and Setters...}
}
3. 自动装配的魔法 (DynamicThreadPoolAutoConfiguration
)
步骤 3.1: 配置属性类
@ConfigurationProperties(prefix = "dynamic.thread-pool")
public class DynamicThreadPoolProperties {private boolean enabled = false;private String dataId = "dynamic-threadpool.json";private String group = "DEFAULT_GROUP";// Getters and Setters...
}
步骤 3.2: 自动配置主类
@Configuration
@EnableConfigurationProperties(DynamicThreadPoolProperties.class)
@ConditionalOnProperty(prefix = "dynamic.thread-pool", name = "enabled", havingValue = "true")
@ConditionalOnClass(ConfigService.class) // 依赖 Nacos
public class DynamicThreadPoolAutoConfiguration {@Beanpublic ThreadPoolDynamicAdjuster threadPoolDynamicAdjuster() {return new ThreadPoolDynamicAdjuster();}
}
步骤 3.3: 注册自动配置
在 autoconfigure
模块的 resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中添加:
com.example.threadpool.autoconfigure.DynamicThreadPoolAutoConfiguration
4. 如何使用我们的 Starter
步骤 4.1: 引入依赖
在业务项目中引入我们的 Starter 和 Nacos Config Starter。
<dependency><groupId>com.example</groupId><artifactId>dynamic-threadpool-spring-boot-starter</artifactId><version>1.0.0</version>
</dependency>
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
步骤 4.2: 在业务应用中定义线程池 Bean
业务开发者像往常一样定义他们需要的线程池。关键是 Bean 的名称要清晰、唯一。
@Configuration
public class MyAsyncConfig {@Bean("myOrderExecutor") // <-- Bean 名称将用于动态配置public ThreadPoolTaskExecutor myOrderExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("OrderAsync-");executor.initialize();return executor;}
}
步骤 4.3: 配置 bootstrap.yml
和 application.yml
# bootstrap.yml - 用于引导 Nacos 连接
spring:cloud:nacos:config:server-addr: 127.0.0.1:8848# application.yml - 启用我们的 Starter
dynamic:thread-pool:enabled: truedata-id: "myapp-threadpool-config" # Nacos中的配置IDgroup: "PROD"
步骤 4.4: 在 Nacos 中配置
在 Nacos 控制台,创建一个 Data ID 为 myapp-threadpool-config
,Group 为 PROD
的配置,内容为 JSON 格式:
{"myOrderExecutor": {"corePoolSize": 10,"maxPoolSize": 20},"anotherExecutorBeanName": {"corePoolSize": 5,"maxPoolSize": 5}
}
Key (myOrderExecutor
) 必须与 Spring 中定义的 Bean 名称完全一致。
验证:
1. 启动应用,
myOrderExecutor
的核心线程数是5
(代码中定义的初始值)。2. 在 Nacos 控制台,将
corePoolSize
修改为15
并发布。3. 稍等片刻,观察应用日志,你会看到“接收到线程池配置变更”和“更新线程池 'myOrderExecutor'”的日志。
4. 此时,线程池的
corePoolSize
已经在不重启应用的情况下,被动态修改为了15
。
总结
通过自定义一个 Spring Boot Starter,我们利用动态配置中心和 Spring Bean 的可变性,成功地将一个静态的、固化在代码中的线程池,变成了一个可实时观测、动态调整的“弹性”资源。