記幾次 [線上環境] Dubbo 線程池佔滿原因分析(第三次:GC STW)

轉載:https://blog.csdn.net/wsmalltiger/article/details/124236206

前言
  某天晚上正在開開心心寫代碼,忽然收到了線上告警:dubbo 線程池 活躍線程數告警、應用錯誤日誌告警、dubbo線程池隊列長度告警;瞬間意識到要出大事情了,得趕緊定位到原因並解決問題,不然時間長了肯定會影響商家功能使用。

 

一、問題分析
1、監控分析
  之前已經有過兩次處理dubbo線程池佔滿的經驗,心想:不會又是上次兩個問題類似的原因吧?既然這裏已經確認是dubbo線程池出現了告警,那麼我們先看看線上機器dubbo線程池監控信息,發現出現排隊情況的機器不多:

  繼續排查應用系統日誌情況,看看是否有能夠利於我們快速定位問題原因的日誌信息:

  通過上面日誌發現調用中臺的接口時間超過10秒,而我們的dubbo框架時有配置 fastfail 3秒快速失敗熔斷機制的(一個dubbo請求超過 3秒沒有返回結果不再繼續請求其他dubbo接口,直接拋出fastfail異常,不做任何重試),而這裏我們有一個請求超過了26秒,於是諮詢一下中間件團隊(公司對dubbo框架有過定製開發)看看是否能快速找到答案:

  好吧,中間件同學沒能提供其他有效的信息(提供的文檔已經看了好幾遍),只能靠自己繼續排查了。首先我們的相關應用dubbo都是開啓了 fastfail 並且超時時間設置的是 3秒,所以接口請求理論上不會超過26秒的(fastfail只有再次請求dubbo接口的時候纔會觸發,如果dubbo入口只進入一次,應用自己內部邏輯處理超過了3秒是不會觸發fastfail機制的);再根據traceId(調用鏈ID,上下游所有相關係統都會打印,方便查找整條請求鏈路的日誌)繼續排查26秒這個接口內部的請求過程:

  發現代碼執行過程很長,有間隔10+秒才走到下一個流程,由於這段代碼邏輯嵌套比較深且打印的日誌偏少,中間不確定發生了什麼?花了大半天時間review了所有相關代碼邏輯,修改代碼增加相關日誌,在 預發環境 使用相同的入參再次調用,速度很快,並沒有出現間隔10+秒的問題。而這段邏輯中也不存在前兩次的 httpClient、CompletableFuture 線程池之類的問題,到底是什麼原因呢?


   經過多次review代碼、查看系統日誌、告警信息,忽然在大量的告警信息中發現一條線索:


   推測:由於dubbo線程池佔滿之前有 GC 告警出來; 是否和 GC STW( stop the world 指的是Gc事件發生過程中,會產生應用程序的停頓。停頓產生時整個應用程序線程都會被暫停,沒有任何響應,有點像卡死的感覺,這個停頓稱爲STW)有關???


2、STW 原因排查
   既然是GC出現了問題,也考慮過修改 JVM參數配置,在FullGC前後分別dump出內存快照,這邊能夠快速定位問題。但是這個方案不可行,主要有兩個原因:
      1、FullGC前後分別dump內存快照勢必會造成服務器性能出現抖動,而出現問題的是我們核心應用,穩定性十分重要。明知道會造成服務器抖動的配置,線上環境是不允許添加上去的,除非“萬不得已”。
      2、線上總共有200多臺服務器,而只有少量個別機器短時間內有發生問題且存在隨機性,無法確定哪一臺會出現問題。如果抽樣配置自動dump內存快照,命中概率十分低下,但是這個問題又急需解決。

   先查看線上機器JVM GC配置(線上機器發佈腳本都是同一套,所有服務器jvm配置都是一樣的):-XX:+UseConcMarkSweepGC 使用CMS垃圾回收器;-XX:+UseParallelGC:並行收集器,僅對年輕代有效,可以同時並行多個垃圾收集線程,但此時用戶線程必須停止:


   準備去定位根因時,忽然遇到一個難題,今天有發佈,出現問題的線上機器docker容器都已經下掉了,沒有gc日誌了; 目前推測大概率是STW時間過長導致,但暫時無法實錘。準備後面再持續觀察,如果再出現GC時間過長再去定位根因。

   果然,第二天另一臺機器再次出現這個問題,這次先使用 jmap 把內存快照dump下來,因爲根據經驗推測STW時間長大概率是內存對象較大,導致GC回收時間過長。分別使用JProfiler、IBM的內存分析工具分析了內存dump文件結果如下:

   logback 佔用內存較多,推測可能是打印了大日誌對象,導致內存佔用過高,最終引起GC。接下來去證實一下,去服務器上查詢GC日誌: 16:59:37 觸發了一次FullGC,耗時 4.5s :

(可以使用 GCEasy 工具對 gc 日誌進行完整分析,支持上傳整個gc日誌文件或者gc日誌文本,並且有統計圖展示,非常好用 官網地址)

   繼續根據 FullGC發生的時間,查看逐行查看問題服務器上的日誌(非常考眼力,滴了好幾滴眼藥水),終於發現了“異常”日誌:

營銷中心發送了一個大MQ消息,482W字符,將近4.5M的消息體:

   憑藉這一個結果,真的可以確定是這個原因造成的嗎? 爲了實錘問題原因,這邊又根據歷史告警記錄,找到了其他幾臺機器的日誌,發現都時間都能夠對上,超過三次出現時間基本對上,是時候下結論了:由於營銷中心業務原因,存在發送單個大MQ消息體的場景,而我們這邊有接收這個MQ消息,存在多次JSON對象轉換、new新對象、log輸出日誌等操作導致堆內存會瞬間飆高觸發FullGC,由於邏輯嵌套較深,且創建對象較多引起 STW 造成線程停頓,而我們應用dubbo接口qps較高,在停頓期間不斷有請求進來,最終引起dubbo線程池佔滿,隊列堆積,dubbo服務出現不可用。

代碼案例:

 

二、解決方案
既然已經知道了原因,那麼解決起來也變得很容易了:

1、解析營銷中心MQ消息體的地方進行優化,消息體只解析我們關心的參數轉成對象;

2、營銷中心的消息體設計顯然有些不合理,發送的MQ消息過於龐大,在傳輸和業務處理上可能會造成性能瓶頸(傳輸速度慢、佔用網絡寬度多、數據量過大解析起來處理速度慢等)。因此需要向營銷中心提需求優化此消息發送策略,拆成小消息發送或者只發送ID讓業務方進行反查詳細數據,從源頭解決這個問題。

   與營銷中心同學溝通過程中出現了一個小插曲:由於這個MQ消息監聽的業務方比較多,改動影響較大,推動其他業務方一起改造比較困難。在中臺同學準備放棄的時候,這邊給他們提了個建議:創建一個新的消息通道,將拆分好的消息發送到新通道中,其他業務方依次遷移到新消息中,全部遷移完成之後再廢棄掉舊的大消息,我們業務可以第一個配合接入新消息通道。 好在中臺同學最終同意採取這個方案,與中臺同學溝通部分記錄:


三、總結
1、dubbo 線程池佔滿只是一個表象,可能造成的原因會有很多,可以根據系統日誌、監控、告警等信息分析具體原因;
2、學會使用工具分析很重要,比如文中提到的 GCEasy 、JProfiler等,能夠快速幫助我們定位問題根因;
3、發現問題之後,可以先有短期臨時方案,但是最終還是一定要從源頭解決,徹底根治問題;
————————————————
版權聲明:本文爲CSDN博主「smatiger」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/wsmalltiger/article/details/124236206

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章