阿里面試:系統的最佳線程數,怎麼確定?
線程使用的兩個核心規範
首先看編程規範中, 有兩個很重要的,與線程有關的需要強制執行的規範:
規範一:【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:Java 線程的創建非常昂貴,需要 JVM 和 OS(操作系統)配合完成大量的工作:
1)消耗內存資源:必須爲線程堆棧分配和初始化大量內存塊,其中包含至少 1MB 的棧內存。
2)消耗 CPU 資源:需要進行系統調用,以便在 OS(操作系統)中創建和註冊內核線程,大量內核線程調度會導致 CPU 上下文過度切換。
所以,Java 高併發應用頻繁創建和銷燬線程的操作將是非常低效的,而且是不被編程規範所允許的。
如何降低 Java 線程的創建成本?必須使用到線程池。使用線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者 “過度切換” 的問題。
以上的內容,在尼恩的 《Java 高併發核心編程 卷 2》 進行了詳細介紹。
規範二:【強制】線程池不允許使用 Executors 去創建快捷線程池 ,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端如下:
-
FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
-
CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
通過以上規範,說明我們應用中,需要用自定義線程池。然而,由於構造一個線程池竟然有 7 個參數
7 個重要參數中,最爲重要的三個是:核心,最大線程數量, BlockingQueue。前兩個參數和線程數量有關係, 後一個和內存資源消耗有關。
線程數設置太少或者阻塞隊列太小, 會導致大量任務被拒絕,拋出 RejectedExecutionException,觸發線上的接口降級,用戶體驗很差。
二線程數設置太多或者阻塞隊列太長,會導致資源消費高而有效負荷很小, 特別是阻塞隊列設置過長,會導致頻繁 FullGC,甚至 OOM。
如何確定系統的最佳線程數?來一個牛逼轟轟的答案
如何確定系統的最佳線程數,大體上分三步:
第一步,理論預估;
第二步,壓測驗證;
第三步,監控調整。
這也是尼恩給大家歸納的,最爲理想的:可監控 / 可彈性的 線程池模式
step1: 完成線程數的理論預估 (設計階段)
在尼恩的 《Java 高併發核心編程 卷 2》 進行了詳細介紹。
首先,按照任務類型對線程池進行分類, 分爲三類,具體如下圖:
具體,請參見在尼恩的 《Java 高併發核心編程 卷 2》 1.7.1 小節。
第一類:IO 密集型線程池線程數預估
線程數就是 CPU 的核數的 2 倍。
具體,請參見在尼恩的 《Java 高併發核心編程 卷 2》 1.7.2 小節。
第二類:CPU 密集線程池線程數預估
CPU 密集型任務並行執行的數量應當等於 CPU 的核心數, 線程數就是 CPU 的核數
具體,請參見在尼恩的 《Java 高併發核心編程 卷 2》 1.7.3 小節。
第三類:混合型線程池線程數預估
混合型線程池線程數預估, 參考下面的的公式:
最佳線程數 = ((線程等待時間 + 線程 CPU 時間) / 線程 CPU 時間 ) * CPU 核數
具體,請參見在尼恩的 《Java 高併發核心編程 卷 2》 1.7.4 小節。
step2: 完成線程數的壓測驗證 (測試階段)
過少的線程會造成任務拒絕,業務降級。
過多的線程會造成,額外的內存開銷 CPU 開銷,甚至會導致 OOM。
所以,合理的線程池線程數,纔是王道。
在設計階段完成了 step1 的線程數的理論預估之後, 那麼我們的理論值就出來了。
如何做驗證呢?這裏需要 壓測。
根據公式:
服務器端最佳線程數量=((線程等待時間+線程cpu時間)/線程cpu時間) * cpu數量
前面線程等待時間,線程 cpu 時間都是 預估的 ,都是要驗證的。
首先通過用戶慢慢遞增來進行性能壓測,觀察 QPS。持續大的增加用戶數, 壓測出最大的吞吐量。
然後再 收集 最大的吞吐量場景的 線程等待時間,線程 cpu 時間, 再計算出最佳線程數。
step3: 完成線程數的線上調整 (生產階段)
壓測的場景,是有限的。而線上的業務, 是複雜的,多樣的。
由於系統運行過程中存在的不確定性,很難一勞永逸地規劃一個合理的線程數。
所以,需要進行生產階段線程數的兩個目標:
-
可監控預警
-
可在線調整
第一個維度:可監控預警
第二個維度:可在線調整
參數的在線動態調整:結合 Nacos 實現動態化線程池
優秀的動態化線程池輪子,主要有:
-
Hippo4J
-
dynamic-tp
如果線上使用,可以使用這些輪子項目。
但是尼恩的是 [技術自由圈] 一個實戰社羣,必須自己從 0 到 1,去擼一把代碼,提升自己的水平。
1. 結合 Nacos 實現動態化線程池架構
結合 Nacos 實現動態化線程池的參數在線調整,架構如下:
2.Nacos 上的配置如下:
3. 線程池配置和 nacos 配置變更監聽
4. 線程池配置的動態刷新
5.LinkedBlockingQueue 實現 resize
LinkedBlockingQueue 不支持 resize, 需要重新定製。自定義可以擴容的 LinkedBlockingQueue ,結構如下:
這裏採用的是讀寫鎖,對 capacity 的設置,進行線程安全 保護:
讀寫鎖的使用如下:
通過對 capacity 的安全修改,以達到動態擴展目的。
其他代碼和 LinkedBlockingQueue 代碼一致。
在線監控預警:結合 PGA 實現 Metric 採集和預警
先把架構圖畫出來,大致如下:
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/iSaykodOKeLvr_BE0lJ5eA