線程池優化與監控

圖片

作者:明義、光線

部門:業務技術

一、前言

隨着應用不斷的迭代更新,零售工程內異步任務逐漸增多,包括網絡請求、本地 DB 操作、輪詢等任務使用的都是同一個線程池,線程池極易打滿(比如網絡一慢就容易阻塞線程池),導致任務堆積,造成「操作卡頓」的現象。用戶看到操作沒反應,可能會更頻繁的嘗試操作,再加上不斷輪詢的任務,應用的線程池隊列會快速積壓更多的任務,「卡頓」現象加劇,單一線的線程池已經無法支撐業務需求:

  1. 任務沒有隔離,異步任務相互影響主要是長耗時影響短耗時任務

  2. 輪迴任務開銷大:沒有統一的輪詢處理方式,業務方需要自己創建線程或線程池(有些乾脆就在默認的 I/O 線程池進行輪詢),應用中存在大量的自建線程池,增加無謂的資源消耗

  3. 缺少監控:缺乏線程池的監控和日誌,線程池的運行狀況和健康度無法衡量,一旦出現「卡頓」問題,排查非常困難,完全不知道是哪些任務造成的問題,傷害用戶體驗的同時,也極大的消耗開發人員的精力

圖注:短時間任務暴漲的情況,在幾毫秒內觸發多次任務

二、整體設計

目標:

  1. 任務隔離,避免耗時任務影響交互

  2. 統一輪詢,減少資源開銷

  3. 任務監控,防止業務方使用錯誤

  4. 信息採集與監控,快速定位排查問題

優化的核心在於分離和監控

分離:

 對原有的 I/O 線程池進行拆分,分離出不適合放在這個線程池的任務,保證 I/O 線程池能對大量、快速的本地任務給予更好的支持,而分離的關鍵就在於分離出「慢」和「多」的任務:

   具體的策略包括:

監控:

除了已知的網絡任務 & 輪詢任務需要分離出 I/O 線程池之外,還需要增加對線程池的監控,進一步分離出不適合放在 I/O 線程池的任務(同樣是「慢」和「多」)

  1. 通過監控每個任務執行的時長,分離出長耗時(即「慢」)的任務,分析出是因爲邏輯 bug 還是本身的複雜度正常佔用,如果是正常佔用就考慮是否合適繼續放在默認的 I/O 線程池,最終目標是達成對「慢」的優化

  2. 另外零售工程中還存在短時間大量重複任務堆積的現象,當線程池接近或達到負荷狀態時,監控線程池中的任務,找到批量的任務,然後進行優化,最終目標是達成對「多」的優化

  3. 通過上面的方式將「慢」的任務分離出去之後,並不代表這樣就完事了,這些任務可能會消耗大量的系統資源,所以同樣需要統一對這個線程池進行監控(主要是網絡任務的監控),把慢網絡請求找出來,達到對「慢」的進一步優化

  4. 通過 API 監控業務方使用合理性,比如:使用多線程輪詢的合理性

三、技術實現

2.1 線程管理庫

目標:

  1. 管理工程內所有線程池與線程創建

  2. 子線程任務監控

  3. 線程池隔離任務分開執行,使用不同線程池執行不同任務

  4. 輪詢任務的統一與異常任務的過濾

  5. 線程池的自動最優設置

    線程庫結構圖

** 線程池管理,目前會提供三種線程池:**

  1. 網絡線程池:針對網絡任務。更改方式:直接在網絡庫替換業務方無感知

  2. IO 線程池:針對本地異步任務。更改方式:App 啓動時 Hook RxJava 線程池替換業務方無感知

  3. 輪詢線程池:針對輪詢任務,需要業務方的接入

線程池 API 定義

3.2 輪詢任務統一

  1. 禁止時間間隔小於 1 秒輪詢任務,防止高頻輪詢消耗資源

  2. 所有輪詢任務巡檢間隔 1 秒檢測一次

  3. 輪詢檢測是獨立的線程,只做輪詢檢測,有任務時死循環間隔檢測,沒有任務時則睡眠等待下次註冊執行,根據結果輪詢執行或睡眠

  4. 線程任務模式有兩種:
    單線程回調: 表示最多佔用一個線程。例:1 秒輪詢回調 1 次,再回調前會判斷上個任務是執行完成,如果沒有執行完成則回調 pollTaskExceptionCallback(),如果已經執行完成則回調 pollTaskCallback()
    多線程回調:表示最多佔用 30 個線程。例:1 秒輪詢回調 1 次,再回調前會判斷當前任務佔用線程總量,如果沒有執行完成則回 pollTaskExceptionCallback(),如果已經執行完成則回調。pollTaskCallback() 默認是單線程回調

  5. 業務方可以指定輪詢次數,或設置無限輪詢

  6. 輪詢線程池動態擴容:初始線程爲 30 個,如果使用線程等於核心線程則動態擴容 2 倍,最大限制 120 個核心線程

輪詢任務流程圖

輪詢 API 定義

任務過濾:

輪詢正常回調 pollTaskCallback() 方法,如果異常這時將回調到 pollTaskExceptionCallback() 方法。

異常任務過濾的實現:

  1. 記錄所有任務。

  2. 每次執行任務時會進行校驗,校驗監聽者是否已經把自己申請的線程全部使用,如果已經全部佔用將回調異常方法,直接到任務有一個釋放後再繼續回調。

  3. 任務執行完後重設記錄數據。

輪詢異常回調有三種觸發場景:

  1. 單線程任務沒有釋放超時。

  2. 多線程任務佔用線程超過最大數量。

  3. 輪詢線程池被全部佔滿超時。

    任務過濾 - 流程圖

四、工程更改

工程更改主要分爲 5 塊:

  1. 網關請求線程池的替換,在構建 RxJava observable 替換成自定義網絡線程池。

  2. RxJava 默認線程池的替換,App 啓動時把 RxJava Hook 設置成自定義 IO 線程池。

  3. 輪詢業務接入統一輪詢。

  4. 網線線程池使用錯誤攔截,目的防止線程使用錯誤,禁止 IO 線程網絡請求,通過攔截器方式實現。

  5. 線程任務監聽設置給 APM ,通過 APM 分析上報。

備註:只有輪詢需要業務方逐個替代其它都正常使用不感知。

五、信息採集與監控

  1. 任務提交之後記錄每個任務的調用棧信息以及任務提交的時間,之後在任務真正開始執行時會記錄任務開始的時間,任務執行完成後即可算出一個任務完整的執行時間、任務等待時間等,這樣就可以抓取出「慢」的任務

  2. 在任務執行完成時,如果線程池已滿並且任務的等待時間超過閾值,則會拉出線程池任務棧的信息,用於查找出異常的任務

    線程池監聽 API


    任務監聽實現流程

改善效果:

卡頓定義:包含主線程超過 300ms 慢方法、ANR、本地子線程操作超過 1s、線程池阻塞、頁面渲染時間超過 200ms。

卡頓次數趨勢圖


    圖注:卡頓次數下降 76%

六、未來規劃

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