在后臺開發中,會經常用到線程池技術,對于線程池核心參數的配置很大程度上依靠經驗。然而,由于系統運行過程中存在的不確定性,我們很難一勞永逸地規劃一個合理的線程池參數。在對線程池配置參數進行調整時,一般需要對服務進行重啟,這樣修改的成本就會偏高。一種解決辦法就是,將線程池的配置放到平臺側,運行開發同學根據系統運行情況對核心參數進行動態配置。
本文以Nacos作為服務配置中心,以修改線程池核心線程數、最大線程數為例,實現一個簡單的動態化線程池。
<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>
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在項目初始化時,要保證先從配置中心進行配置拉取,拉取配置之后才能保證項目的正常啟動。
登錄到nacos管理頁面,新建配置,如下圖所示:
圖片
注意Data ID的命名格式為,${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension} ,在本文中,Data ID的名字就是order-service-dev.yml。
圖片
這里我們只配置了兩個參數,核心線程數量和最大線程數。
@RefreshScope@Configurationpublic 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); }}
這個代碼就是實現動態線程池和核心了,需要說明的是:
為了觀察線程池動態變更的效果,增加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); }}
啟動項目,訪問http://localhost:8010/threadpool/print打印當前線程池的配置。
圖片
可以看到,這個就是我們之前在nacos配置的線程數。
訪問http://localhost:8010/threadpool/add?count=20增加20個任務,重新打印線程池配置
圖片
可以看到已經有線程在排隊了。
為了能夠看到效果,我們多訪問幾次/add接口,增加任務數,在控制臺出現拒絕信息時調整nacos配置。
圖片
此時,執行/add命令時,所有的線程都會提示rejected。
調整nacos配置,將核心線程數調整為50,最大線程數調整為100.
圖片
重新多次訪問/add接口增加任務,發現沒有拒絕信息了。這時,打印具體的線程狀態,發現線程池參數修改成功。
這里,只是簡單實現了一個可以調整核心線程數和最大線程數的動態線程池。具體的線程池實現原理可以參考美團的這篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,結合監控告警等實現一個完善的動態線程池產品。
優秀的輪子還有好多,比如Hippo4J ,使用起來和dynamic-tp差不多。Hippo4J 有無依賴中間件實現動靜線程池,也有默認實現Nacos和Apollo的版本,而dynamic-tp 默認實現依賴Nacos或Apollo。
本文鏈接:http://www.www897cc.com/showinfo-26-72425-0.htmlSpring Boot + Nacos 實現了一個動態化線程池,非常實用!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com