記一次生產環境tomcat線程數打滿情況分析


  • 前言

    旨在分享工作中遇到的各種問題及解決思路與方案,與大家一起學習. -- 學無止境, 加油 ! 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技術寶典 ,及時獲取最新分享.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM