概要
线程池使⽤⾯临的核⼼的问题在于:线程池的参数并不好配置。配置合理需要强依赖开发⼈员的个⼈经验和知识;另⼀⽅⾯,线程池执⾏的情况和任务类型相关性较⼤,IO密集型和CPU密 集型的任务运⾏起来的情况差异⾮常⼤,这导致业界并没有⼀些成熟的经验策略帮助开发⼈员参考。既然不能保证参数配置最合理,那么是否可以通过将修改线程池参数的成本降下来,这样⾄少可以发⽣故障的时候可以快速调整从⽽缩短故障恢复的时间?基于这个思考,我们可以将线程池的参数从代码中迁移到注册中心上,实现线程池参数可动态配置和即时⽣效。
整体架构流程
开发动态线程池SpringBoot Starter组件,通过该组件实现线程池参数信息可动态配置和即时生效
中间件层结构
在中间件实现工程分为四块,config、domain、registry,trigger它们的作用如下:
- config,读取自定义配置信息,以及负责把注册中心redis相关的Bean、spring相关的bean 加载启动 (包括动态注册线程池信息到注册中心的自定义bean,以及操作线程池的自定义bean)。
- domain,这部分是动态线程池服务,定义了线程池配置实体对象、注册中心枚举值对象,对外提供IDynamicThreadPoolService进行线程池数据查询、线程池参数查询、更新线程池参数的服务。
- registry,这一部分是注册线程池配置信息、线程池参数到注册中心,线程池配置信息使用list,线程池参数使用缓存,存成Bucket.
- trigger,这部分主要实现线程池数据的定时上报job 、以及线程池配置参数变更的redis消息触发。
实现步骤
- 创建DynamicThreadPoolAutoConfig类,这是Spring Boot的自动配置类,用于初始化和配置动态线程池。在DynamicThreadPoolAutoConfig类中,定义了多个Bean,包括redissonClient、redisRegistry、dynamicThreadPoolService、threadPoolDataReportJob、threadPoolConfigAdjustListener和dynamicThreadPoolRedisTopic。这些Bean分别用于创建Redis客户端,注册中心,动态线程池服务,线程池数据报告任务,线程池配置调整监听器和Redis主题。
@Configuration
@EnableConfigurationProperties(DynamicThreadPoolAutoProperties.class)
@EnableScheduling
public class DynamicThreadPoolAutoConfig {
private final Logger logger = LoggerFactory.getLogger(DynamicThreadPoolAutoConfig.class);
private String applicationName;
@Bean("redissonClient")
public RedissonClient redissonClient(DynamicThreadPoolAutoProperties properties){
Config config = new Config();
config.setCodec(JsonJacksonCodec.INSTANCE);
config.useSingleServer()
.setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
.setPassword(properties.getPassword())
.setConnectionPoolSize(properties.getPoolSize())
.setConnectionMinimumIdleSize(properties.getMinIdleSize())
.setIdleConnectionTimeout(properties.getConnectTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setRetryAttempts(properties.getRetryAttempts())
.setRetryInterval(properties.getRetryInterval())
.setPingConnectionInterval(properties.getPingInterval())
.setKeepAlive(properties.isKeepAlive())
;
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
// 初始化注册中心
@Bean
public IRegistry redisRegistry(RedissonClient redissonClient){
return new RedisRegistry(redissonClient);
}
@Bean("dynamicThreadPoolService")
public DynamicThreadPoolService dynamicThreadPoolService(ApplicationContext applicationContext, Map<String,ThreadPoolExecutor> threadPoolExecutorMap,RedissonClient redissonClient){
applicationName = applicationContext.getEnvironment().getProperty("spring.application.name");
if (StringUtils.isBlank(applicationName)) {
applicationName = "缺省的";
logger.warn("动态线程池,启动提示。SpringBoot 应用未配置 spring.application.name 无法获取到应用名称!");
}
// 防止应用启动时 已经配置了线程池的信息
Set<String> keySet = threadPoolExecutorMap.keySet();
for (String threadPoolKey : keySet) {
ThreadPoolConfigEntity threadPoolConfig = redissonClient.<ThreadPoolConfigEntity>getBucket(RegistryEnumVO.THREAD_POOL_CONFIG_PARAMETER_LIST_KEY.getKey() + "_" + applicationName + "_" + threadPoolKey).get();
if(null == threadPoolConfig) continue;
ThreadPoolExecutor executor = threadPoolExecutorMap.get(threadPoolKey);
executor.setCorePoolSize(threadPoolConfig.getCorePoolSize(