記一次生產環境tomcat執行緒數打滿情況分析

Java技術寶典發表於2020-08-12
  • 前言

    旨在分享工作中遇到的各種問題及解決思路與方案,與大家一起學習. -- 學無止境, 加油 ! Just do it !

  • 問題描述

    • 執行環境描述

      • tomcat-8.5

      • 單節點(該應用叢集20個節點) avg-tps 250,max-tps 350

      • tomcat max-threads:200 (下圖藍色線)

      • tomcat busy-threads 正常(下圖綠色線)

      • tomcat cur-threads飛昇(下圖黃色線)

      • 每次黃色線上升時可以發現原本平均響應時間100ms內的介面響應時間均在3-10s 在這裡插入圖片描述

    • 提出問題

      使用grafana監控發現服務某個節點的cur執行緒數會暴漲直至Max-threads數且一直無法回收

    • 期望

      解決cur-threads回收問題,讓執行緒正常回收

  • 原因分析

    • 執行緒問題首先來一波jstack 在這裡插入圖片描述 上圖是當時某個節點執行緒飆升時dump下來的執行緒日誌,在這個時間點的執行緒中有大量的TIMED_WAITING 狀態,可以先複習一波執行緒狀態了,走起.

    • Java執行緒的5種狀態

      • 新建狀態(New): 執行緒物件被建立後,就進入了新建狀態。例如,Thread thread = new Thread()。

      • 就緒狀態(Runnable): 也被稱為“可執行狀態”。執行緒物件被建立後,其它執行緒呼叫了該物件的start()方法,從而來啟動該執行緒。例如,thread.start()。處於就緒狀態的執行緒,隨時可能被CPU排程執行。

      • 執行狀態(Running): 執行緒獲取CPU許可權進行執行。需要注意的是,執行緒只能從就緒狀態進入到執行狀態。

      • 阻塞狀態(Blocked): 阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:

        • 等待阻塞 -- 通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。

        • 同步阻塞 -- 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態。

        • 其他阻塞 -- 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

      • 死亡狀態(Dead): 執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。 在這裡插入圖片描述

    • Jstack中常見的執行緒狀態

      • RUNNABLE 執行緒執行中或I/O等待

      • BLOCKED 執行緒在等待monitor鎖(synchronized關鍵字)

      • TIMED_WAITING 執行緒在等待喚醒,但設定了時限(lock.wait(10))

      • WAITING 執行緒在無限等待喚醒(lock.wait(10))

    複習完了,結合上面的執行緒日誌以及服務中高併發的介面,找到有用到lock鎖的介面,分析程式碼,到這一步基本算是找到解題思路了,如此多的執行緒等待是因為併發的查詢介面快取穿透了 接下來還要dump下這個節點的堆記憶體來具體分析,準確定位,下圖是堆記憶體日誌: 在這裡插入圖片描述 很明顯可以看到堆中的大物件內容,結合實際業務可以準確定位需要優化的介面了,那麼cur-threads執行緒數為什麼一直增長呢?為什麼不回收呢?帶著這兩個疑問,我們先去找下tomcat官網針對這兩個引數的描述; 在這裡插入圖片描述

  • 上圖可以看到最大執行緒數預設是200,初始化空閒執行緒數10,與我們線上環境一致(附上圖中tomcat資料連結) 在這裡插入圖片描述

  • 上圖也是找的tomcat官網(附上圖中tomcat資料),第三個引數 maxIdleTime 執行緒閒置一分鐘後會被回收

  • 總結

    • cur-threads一直增長的原因

      • 介面併發且發生了大量快取穿透(執行緒日誌中大量time_wait執行緒是專案中防快取穿透使用的鎖),造成鎖等待,進而造成tomcat當前執行緒不夠用,所以cur執行緒資料增加,每次線上程數增加的時候介面響應均達到秒級別,可能建立Thread比較消耗資源,這塊有待驗證!

    • tomcat執行緒一直不回收的原因

      • Tomcat執行緒池每次從佇列頭部取執行緒去處理請求,請求完結束後再放到佇列尾部,在高併發下,每個執行緒都會在短時間內被使用,達不到1分鐘空閒被回收的條件

  • 解決方案與建議

    • 需要優化響應慢的介面(治本)

    • 如果可以,降低介面併發(治標)

    • 適當增加tomcat的maxThreads值可以提升應用效能(不是越大越好,最優配置數值需要模擬pro環境經過大量壓測對比得出)

  • 優化後

    在這裡插入圖片描述

  • 本次改造有兩個點

    • 降低併發(比如serv A->serv-B,查詢併發比較高,可以根據實際業務考慮在A系統做快取,降低B系統併發)

    • 優化響應慢的介面 (如果業務複雜可以先考慮設計是否合理再考慮技術改造(多執行緒,快取中介軟體))

  • 上圖是在改造後的第二天可以明顯看到cur執行緒數有一個下降,基本驗證思路正確.

 

歡迎關注個人訂閱號:Java技術寶典 ,及時獲取最新分享. 記一次生產環境tomcat執行緒數打滿情況分析

相關文章