當前位置: 妍妍網 > 碼農

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]