当前位置: 欣欣网 > 码农

SpringBoot + Nacos 实现了一个动态化线程池,实用!

2024-02-24码农

架构师(JiaGouX)

我们都是架构师!
架构未来,你来不来?


1.背景

在后台开发中,会经常用到线程池技术,对于线程池核心参数的配置很大程度上依靠经验。然而,由于系统运行过程中存在的不确定性,我们很难一劳永逸地规划一个合理的线程池参数。

在对线程池配置参数进行调整时,一般需要对服务进行重启,这样修改的成本就会偏高。一种解决办法就是,将线程池的配置放到平台侧,运行开发同学根据系统运行情况对核心参数进行动态配置。

本文以Nacos作为服务配置中心,以修改线程池核心线程数、最大线程数为例,实现一个简单的动态化线程池。


2.code

1.依赖

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

2.配置yml文件

bootstrap.yml

server:
port:8010
# 应用名称(nacos会将该名称当做服务名称)
spring:
application:
name:order-service
cloud:
nacos:
discovery:
namespace:public
server-addr:192.168.174.129:8848
config:
server-addr:192.168.174.129:8848
file-extension:yml

application.yml

spring:
profiles:
active:dev

为什么要配置两个yml文件?

springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application。

nacos在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后才能保证项目的正常启动。

3.nacos配置

登录到nacos管理页面,新建配置,如下图所示

注意Data ID的命名格式为, ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ,在本文中,Data ID的名字就是 order-service-dev.yml

配置详情

这里我们只配置了两个参数,核心线程数量和最大线程数。

4.线程池配置和nacos配置变更监听

@RefreshScope
@Configuration
public classDynamicThreadPoolimplementsInitializingBean{
@Value("${core.size}")
private String coreSize;
@Value("${max.size}")
private String maxSize;
privatestatic ThreadPoolExecutor threadPoolExecutor;
@Autowired
private NacosConfigManager nacosConfigManager;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
publicvoidafterPropertiesSet()throws Exception {
//按照nacos配置初始化线程池
threadPoolExecutor = new ThreadPoolExecutor(Integer.parseInt(coreSize), Integer.parseInt(maxSize), 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10),
new ThreadFactoryBuilder().setNameFormat("c_t_%d").build(),
new RejectedExecutionHandler() {
@Override
publicvoidrejectedExecution(Runnable r, ThreadPoolExecutor executor){
System.out.println("rejected!");
}
});
//nacos配置变更监听
nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
new Listener() {
@Override
public Executor getExecutor(){
returnnull;
}
@Override
publicvoidreceiveConfigInfo(String configInfo){
//配置变更,修改线程池配置
System.out.println(configInfo);
changeThreadPoolConfig(Integer.parseInt(coreSize), Integer.parseInt(maxSize));
}
});
}
/**
* 打印当前线程池的状态
*/

public String printThreadPoolStatus(){
return String.format("core_size:%s,thread_current_size:%s;" +
"thread_max_size:%s;queue_current_size:%s,total_task_count:%s", threadPoolExecutor.getCorePoolSize(),
threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize(), threadPoolExecutor.getQueue().size(),
threadPoolExecutor.getTaskCount());
}
/**
* 给线程池增加任务
*
@param count
*/

publicvoiddynamicThreadPoolAddTask(int count){
for (int i = 0; i < count; i++) {
int finalI = i;
threadPoolExecutor.execute(new Runnable() {
@Override
publicvoidrun(){
try {
System.out.println(finalI);
Thread.sleep(10000);
catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
/**
* 修改线程池核心参数
*
@param coreSize
@param maxSize
*/

privatevoidchangeThreadPoolConfig(int coreSize, int maxSize){
threadPoolExecutor.setCorePoolSize(coreSize);
threadPoolExecutor.setMaximumPoolSize(maxSize);
}
}









这个代码就是实现动态线程池和核心了,需要说明的是:

  • @RefreshScope ,这个注解用来支持nacos的动态刷新功能;

  • @Value("${max.size}") @Value("${core.size}") ,这两个注解用来读取我们上一步在nacos配置的具体信息;同时,nacos配置变更时,能够实时读取到变更后的内容;

  • nacosConfigManager.getConfigService().addListener ,配置监听,nacos配置变更时实时修改线程池的配置。

  • 5.controller

    为了观察线程池动态变更的效果,增加Controller类。

    @RestController
    @RequestMapping("/threadpool")
    public classThreadPoolController{
    @Autowired
    private DynamicThreadPool dynamicThreadPool;
    /**
    * 打印当前线程池的状态
    */

    @GetMapping("/print")
    public String printThreadPoolStatus(){
    return dynamicThreadPool.printThreadPoolStatus();
    }
    /**
    * 给线程池增加任务
    *
    @param count
    */

    @GetMapping("/add")
    public String dynamicThreadPoolAddTask(int count){
    dynamicThreadPool.dynamicThreadPoolAddTask(count);
    return String.valueOf(count);
    }
    }

    6.测试

    启动项目,访问 http://localhost:8010/threadpool/print 打印当前线程池的配置。

    可以看到,这个就是我们之前在nacos配置的线程数。

    访问 http://localhost:8010/threadpool/add?count=20 增加20个任务。

    重新打印线程池配置

    可以看到已经有线程在排队了。

    为了能够看到效果,我们多访问几次/add接口,增加任务数,在控制台出现拒绝信息时调整nacos配置。

    此时,执行/add命令时,所有的线程都会提示rejected。

    调整nacos配置,将核心线程数调整为50,最大线程数调整为100.

    重新多次访问/add接口增加任务,发现没有拒绝信息了。这时,打印具体的线程状态,发现线程池参数修改成功。

    3.总结

    这里,只是简单实现了一个可以调整核心线程数和最大线程数的动态线程池。具体的线程池实现原理可以参考美团的这篇文章Java线程池实现原理及其在美团业务中的实践 - 美团技术团队,结合监控告警等实现一个完善的动态线程池产品。

    传送门:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

    如喜欢本文,请点击右上角,把文章分享到朋友圈
    如有想了解学习的技术点,请留言给若飞安排分享

    因公众号更改推送规则,请点「在看」并加「星标」第一时间获取精彩技术分享

    ·END·

    相关阅读:

    作者:新村10

    来源:blog.csdn.net/m0_37558251/article/details/126542359

    版权申明:内容来源网络,仅供学习研究,版权归原创者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

    架构师

    我们都是架构师!

    关注 架构师(JiaGouX),添加「星标」

    获取每天技术干货,一起成为牛逼架构师

    技术群请 加若飞: 1321113940 进架构师群

    投稿、合作、版权等邮箱: [email protected]