聊聊動態線程池的 9 個場景(改進版)

大家好,我是小馬哥。

線程池是一種基於 池化思想管理線程 的工具,使用線程池可以減少 創建銷燬線程的開銷,避免線程過多導致 系統資源耗盡。在 高併發以及大批量 的任務處理場景,線程池的使用是必不可少的。

如果有在項目中實際使用線程池,相信你可能會遇到以下痛點:

  1. 線程池隨便定義,線程資源過多,造成服務器高負載。

  2. 線程池參數不易評估,隨着業務的併發提升,業務面臨出現故障的風險。

  3. 線程池任務執行時間超過平均執行週期,開發人員無法感知。

  4. 線程池任務堆積,觸發拒絕策略,影響既有業務正常運行。

  5. 當業務出現超時、熔斷等問題時,因爲沒有監控,無法確定是不是線程池引起。

  6. 原生線程池不支持運行時變量的傳遞,比如 MDC 上下文遇到線程池就 GG。

  7. 當項目關閉時,無法做到優雅退出,大量正在運行的線程池任務被丟棄。

  8. 線程池運行中,任務執行停止,懷疑發生死鎖或執行耗時操作,但是無從下手。

基於以上諸多痛點,小馬哥着手 hippo4j 的開發,致力於打造標準線程池 動態變更監控 的中間件框架。

GitHub:https://github.com/opengoofy/hippo4j

Gitee:https://gitee.com/agentart/hippo4j

什麼是 hippo4j

hippo4j 通過對 JDK ThreadPoolExecutor 線程池增強,以及擴展三方框架底層線程池等功能,爲業務系統提高線上運行保障能力。

小貼士:hippo4j 不止於 Java ThreadPoolExecutor 的增強,DubboRabbitMQRocketMQHystrixTomcatJettyUndertow 等框架線程池也都有進行監控和動態變更。

什麼場景適合用 hippo4j

1. 線程池隨意定義,造成服務器高負載

在系統開發的過程中,因爲涉及到多人協作,難免會出現信息不互通的情況。在同一個系統,對於線程池來說,常見的是線程池隨意定義。

隨着系統不斷開發,相同或不同語義的線程池被定義得越來越多,間接導致服務器資源嚴重耗損。

而如果系統中使用 hippo4j,能夠在控制檯查看當前應用已有線程池,是否存在相同語義且業務可複用線程池實例,避免線程資源過度浪費。

2. 線程池參數不易評估

業務中使用了線程池,十個程序員可能有九個都在犯嘀咕,這線程池的配置應該如何選擇?

我覺得犯糾結的點主要有兩個,無外乎設置的數多了或者少了。

  1. 如果預設的線程數或阻塞隊列數量少了,當業務量上來,會遇到兩種情況,不管哪一種對業務來說都是不能接受的。

  2. 預估 200ms 執行完的任務,可能會 2s 執行完,因爲任務都在排隊。

  3. 任務滿了後,開始執行拒絕策略,影響正常業務。

  4. 如果超量設置線程池的參數,無疑會造成資源浪費,同樣會造成兩種情況。

  5. 線程資源也是佔用服務器資源的,開啓的多了對服務器有一定壓力。

  6. 如果過多得創建線程,當和其它線程池一起執行時,服務器 CPU 上下文切換也是個問題。

大家都知道,如果要修改運行中應用線程池參數,需要停止線上應用,調整成功後再發布,而這個過程異常的繁瑣,如果能在運行中動態調整線程池的參數多好。

美團技術團隊基於這些痛點,推出了動態線程池的概念,催生了一批動態線程池框架,hippo4j 也是其一。

如果應用是集羣部署,hippo4j 可以選擇修改線程池 某一實例,或者修改 集羣全部實例,運行時生效,不需要再重啓服務。

再比如,壓測時使用 hippo4j 動態調整線程池參數,對於開發測試來說,也是個不錯的選擇。

3. 線程池運行時報警策略

從線程池運行時監控的角度出發,hippo4j 內置 4 種報警策略,線程池活躍度、阻塞隊列容量、拒絕策略觸發以及任務運行超時報警。

hippo4j 支持釘釘、企業微信和飛書軟件通知,線程池任務運行超時報警示例:

4. 線程池運行時狀態對開發者黑盒

線程池在服務運行過程中,對開發者來說是一個完全的黑盒。開發者無法得知線程池的參數變化,比如阻塞隊列數量或者完成任務數等核心參數,這對於排查問題來說並不友好。

hippo4j 支持線程池運行時狀態實時查看,並在核心參數的基礎上擴展了 負載、內存以及拒絕次數 等關鍵指標,每次查詢返回線程池當前運行信息。

5. 線程池監控

hippo4j 提供了兩種線程池運行時數據監控方式,分別是:

1、內置數據池數據採集 + 監控,無需依賴任何中間件,由 hippo4j 內部集成的方式運行。

2、使用三方中間件 Prometheus + Grafana 或 ElasticSearch + Grafana 採集和監控。

6. 整合 Spring ThreadPoolTaskExecutor

Spring ThreadPoolTaskExecutor 對原生線程池擴展了一部分功能,我認爲比較實用有兩個,並且 hippo4j 也已經支持。

  1. 當服務停止時,通知線程池處理剩餘任務,並在等待指定時間後強制停止。

  2. 傳遞線程上下文到線程池執行上下文中。

第一個是實際使用中很核心的功能,減少了線程池丟棄任務的可能,這裏重點說明下。

我們平時在停止應用時,有沒有這樣一個考慮,線程池中的任務真的都執行完成了嗎?

可能執行完了,可能沒有

Spring 基於以上考慮,註冊了線程池銷燬方法。在應用關閉時,如果發現線程池存在任務沒有執行完,需要等待一個指定時間。指定時間內任務執行如果執行完畢,皆大歡喜;如果還存在沒有結束的任務,則丟棄。

爲什麼會丟棄任務而不是再等等?

因爲如果線程池任務長時間執行,會影響整個應用的停止,進行了折中處理。

7. 三方框架中間件線程池適配

hippo4j 的目標是兼容所有框架的線程池,並可以提供監控和動態修改的能力。

目前已支持的三方框架線程池列表:

支持上述框架線程池的動態變更參數和監控功能,比如:

未來 hippo4j 會支持更多三方框架線程池,如果你有好的想法也可以和我溝通。

8. 線程池運行堆棧查看

線程池運行中,任務運行停止,懷疑發生死鎖或執行耗時操作。大多數程序員會選擇使用命令或者 arthas 查看線程池運行中線程的堆棧,看看其中的 Worker 都在哪個方法卡住了。

hippo4j 基於以上痛點,推出了線程池運行堆棧實時查看功能。

9. 動態線程池對性能有無影響

這可能是很多開發者擔心的一個點,在這裏統一回復下。

hippo4j 僅對線程池做部分核心功能增強,沒有修改任務執行源代碼流程,可以保證絕對的安全。

其次,hippo4j 上述的功能,都是與線程池執行任務主流程外擴展的,不會影響線程池正常的執行性能。

hippo4j 支持的兩種運行模式

hippo4j 爲用戶提供了兩種運行模式,分別是輕量級的配置中心接入,和功能更齊全的服務端接入,下面都來說說各自的優缺點。

1. hippo4j config

依賴配置中心完成線程池的動態變更,已支持的配置中心有:Nacos、Apollo、Zookeeper、Etcd,未來還會接入 Consul 等。

另外,hippo4j 已支持用戶自定義配置中心實現,如果使用公司自研或其它配置中心,也可以極小工作量進行引入。

使用 hippo4j config 模式的優點和不足:

  1. 優點:輕量級引入,可以根據項目中已有配置中心進行 hippo4j 的集成,無需引入其它服務,即可使用線程池參數動態化、運行時監控、報警等核心功能。

  2. 不足:缺少可視化控制檯頁面,上文中描述的諸多功能不能使用。

2. hippo4j server

需要部署 hippo4j Jar 包,涵蓋上文中描述的所有功能。

因爲服務端內部實現了配置中心和註冊中心(參考 nacos 和 eureka 實現),所以它不依賴任何三方中間件。

  1. 優點:功能齊備,可以享受更多的服務和便利。如果應用啓動的是集羣,可以指定其中某一個實例的線程池修改,而 config 則是整個集羣變更。

  2. 不足:相比較 hippo4j config,需要額外部署一個 jar 包,增加了部署工作量。

如果最初使用 hippo4j config,想要切換到 server,兩者在進行替換的時候,無需修改業務代碼

使用建議:根據公司情況選擇,如果基本功能可以滿足使用,選擇 hippo4j config 使用即可;如果希望更多的功能,可以選擇 hippo4j server。

項目發展近況

開源項目發展離不開用戶和貢獻者的支持,小馬哥梳理出最近 hippo4j 發展近況:

文末結語

最後總結下,作者在每天下班和週六日的時間做開源項目,妥妥的爲愛發電。如果各位大佬覺得有用,麻煩在以下平臺 star 支持,非常感謝~

GitHub:https://github.com/opengoofy/hippo4j

Gitee:https://gitee.com/agentart/hippo4j

長按添加小馬哥微信,備註 hippo4j,拉你進 hippo4j 官方交流羣,羣裏還有阿里、字節、京東、美團等大廠程序員,2022 一起抱團取暖

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/3GCGlC5cvcrFIQTqW4MdRQ