在后臺開發中,會經常用到線程池技術,對于線程池核心參數的配置很大程度上依靠經驗。然而,由于系統運行過程中存在的不確定性,我們很難一勞永逸地規劃一個合理的線程池參數。在對線程池配置參數進行調整時,一般需要對服務進行重啟,這樣修改的成本就會偏高。一種解決辦法就是,將線程池的配置放到平臺側,運行開發同學根據系統運行情況對核心參數進行動態配置。
本文以Nacos作為服務配置中心,以修改線程池核心線程數、最大線程數為例,實現一個簡單的動態化線程池。
代碼實現
1.依賴
???? com.alibaba.cloud ????spring-cloud-starter-alibaba-nacos-discovery ????2021.1 ???? com.alibaba.cloud ????spring-cloud-starter-alibaba-nacos-config ????2021.1 ???? org.springframework.boot ????spring-boot-starter-web ???? org.springframework.boot ????spring-boot-starter
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?這個代碼就是實現動態線程池和核心了,需要說明的是:
@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接口增加任務,發現沒有拒絕信息了。這時,打印具體的線程狀態,發現線程池參數修改成功。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
總結
這里,只是簡單實現了一個可以調整核心線程數和最大線程數的動態線程池。具體的線程池實現原理可以參考美團的這篇文章:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html,結合監控告警等實現一個完善的動態線程池產品。
優秀的輪子還有好多,比如Hippo4J ,使用起來和dynamic-tp差不多。Hippo4J 有無依賴中間件實現動靜線程池,也有默認實現Nacos和Apollo的版本,而dynamic-tp 默認實現依賴Nacos或Apollo。
編輯:黃飛
?
評論