當前位置: 妍妍網 > 碼農

基於SpringBoot+Nacos構建的動態執行緒池!

2024-03-15碼農

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 class DynamicThreadPool implements InitializingBean {
@Value("${core.size}")
private String coreSize;
@Value("${max.size}")
private String maxSize;
private static ThreadPoolExecutor threadPoolExecutor;
@Autowired
private NacosConfigManager nacosConfigManager;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Override
public void afterPropertiesSet() 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
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("rejected!");
}
});
//nacos配置變更監聽
nacosConfigManager.getConfigService().addListener("order-service-dev.yml", nacosConfigProperties.getGroup(),
new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(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
*/
public void dynamicThreadPoolAddTask(int count) {
for (int i = 0; i < count; i++) {
int finalI = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(finalI);
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
/**
* 修改執行緒池核心參數
*
* @param coreSize
* @param maxSize
*/
private void changeThreadPoolConfig(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 class ThreadPoolController {
    @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.總結

    這裏,只是簡單實作了一個可以調整核心執行緒數和最大執行緒數的動態執行緒池,感興趣的同學可以在此基礎上擴充套件成為一個結合監控告警的動態執行緒池產品。

    - EOF -

    推薦閱讀點選標題可跳轉

    ·················END·················

    看完本文有收獲?請轉發分享給更多人

    關註「哪咤編程」,提升Java技能

    點贊和在看就是最大的支持❤️