分析tpserver啟動時偶發CPU飈升,甚至hsf執行緒池滿

yudenghu發表於2016-01-04

該文章來自阿里巴巴技術協會(ATA)精選集

序言

aliexpress(中文:速賣通)是個快速成長的國際化電商平臺,肩負開拓海外市場的重任。
隨著業務飛速膨脹(多語言、多幣種、海外倉、分站、無線、區域化),網站程式碼也在不停增長,同時也在不停湧現新應用。
而本文的主角tpserver擔當aliexpress的交易流程核心服務這一職責,雖然它最近才經歷了一次拆分(逆向流程被剝離了出來),但還揹負了各種歷史包袱。
簡單說就是tpserver提供33個介面的hsf服務,監聽16種notify訊息,1種metaq訊息。
通俗一點就是從下單開始,後續所有交易流程都由這一個系統提供服務,維護每一個訂單的生命週期。

背景

一般應用都存在重啟時cpu偏高的現象,因為應用需要做初始化,虛擬機器會裝載很多類,二期很多各種各樣的資源需要快取起來,tpserver也不例外,以前最多就佔40%的cpu。
隨著為了保障2015雙十一大促,tpserver做了叢集拆分,拆出了下單專用的核心叢集tpservercore,然後原來普通叢集tpserver的機器就減少了,應用容量一般維持在30%的水位。
隨著統一升級tddl、hsf等等,重啟時的CPU也一路走高到100%,偶爾會出現執行緒池滿。這個時候外圍應用呼叫tpserver普通叢集會出現較多超時,一般經過1分鐘內的消化,應用漸漸穩定,各項指標也都會恢復正常。
期間聯絡tddl、集團內部JVM、hsf的相關同學一塊從tddl,jvm預編譯、hsf優雅上下線都排查實施了一遍,結果還是沒有徹底解決這個問題。
由於是偶發、會自動恢復,隨著大促最後衝刺,這個事情就放了下來。多次釋出tpserver的同學形成了默契,釋出tpserver的時候每批都暫停5分鐘,這樣異常也受控。

就這樣一直推到了2015-12-09下午,突然接到tpserver很多執行緒池滿的報警,一問了解到有同學正在連續釋出tpserver,於是再次排查。
_1_cpu

排查

一般HSF執行緒池滿的快速排查方法

1,確認是不是由於消費的外部服務或DB的rt變長或網路抖動引起

——這種就是要翻看各個監控或/home/admin/logs/hsf/hsf.log來確認,比較繁瑣,但也是比較容易處理:通知相關服務同學跟進

2,本機存在效能較差的程式碼

——這種得靠分析jstack匯出的執行緒堆疊了,連結文章介紹得非常詳細,建議有興趣的同學仔細閱讀
但像tpserver這種隨機爆發+一會就自動恢復,就要拼手快了!之前很多同學分析過很多次就是沒有拿到有價值的堆疊,因為登上去執行jstack的時候,應用已經恢復了。
這個時候要吐槽一下hsf,其提供了線上程池滿的時候會自動生成一份/home/admin/logs/hsf/HSF_JStack.log檔案,但是每次都沒拿到問題發生現場。

快速排查結論

經過檢視各類監控與日誌檔案,確認外部服務與資料庫的rt與網路都沒有明顯變化,看來很可能“本機存在效能較差的程式碼”。

深入單臺機器排查通

如何確認load最高的機器,請使用神器xflush連結
http://xflush.alibaba-inc.com/optimus/#/10/app/detail/tpserver/ecs
(由於xflush暫時無法檢視幾天前的資料,所以圖就不貼了)
挑選一臺正在報警的機器,第一次用sudo -u admin jstack -p pid > ~/cpu.tdump,後續就用sudo -u admin jstack -p pid >> ~/cpu.tdump來附加到先前的檔案,這樣能快速匯出多份執行緒堆疊到一個檔案裡,利用最近發現的一個工具來分析執行緒趨勢。

利用工具review thread dump

嘗試了多臺機器後終於發現一點線索,附圖
阻塞的執行緒很多
tpserver010176210054_ot7_HSF_JStack_log_blocked
感覺問題就出在

at com.ibatis.common.beans.ClassInfo.getInstance(ClassInfo.java:494)

分析ibatis快取核心程式碼

拉程式碼,直接看方法
tpserver010176210054_ot7_HSF_JStack_log_ibatis0

再看CLASS_INFO_MAP的實現
tpserver010176210054_ot7_HSF_JStack_log_ibatis1

Collections.synchronizedMap(new HashMap())是一個通用的多執行緒安全方案,但是效能只能說一般,問什麼呢,見原始碼
tpserver010176210054_ot7_HSF_JStack_log_Collections0
tpserver010176210054_ot7_HSF_JStack_log_Collections1_words
看來這麼多執行緒被BLOCKED就是阻塞在要獲取上面兩個鎖上面。

簡單介紹一下幾個資料集合類的差異


他們是HashMapHashTableCollectionsSynchronizedMapConcurrentHashMap。其中HashMap執行緒不安全,HashTableCollectionsSynchronizedMapConcurrentHashMap都執行緒安全。
|
對比項類別 | Hashtable |
Collections$SynchronizedMap | ConcurrentHashMap |

| —- | —- | —- | —- |

| 讀需要鎖 | 是 | 是 | 不需要 |
| 讀鎖物件 | this,也就是整個資料集 | 預設是this,也可以是定製物件,不管怎樣還是相當於鎖整個資料集 | 片段 |
| 寫需要鎖 | 是 | 是 | 不需要 |
| 加鎖範圍 | this,也就是整個資料集 | 預設是this,也可以是定製物件,不管怎樣還是相當於鎖整個資料集 | 片段 |
| 讀寫互斥 | 是 | 是 | 否 |
| 寫同時併發讀的影響 | 阻塞讀,喚醒後拿到最新的整個表格 | 同Hashtable | 無阻塞,讀取到當時最新的資料 |

驗證

1,分析鎖情況

再到tdump檔案裡驗證,隨便找一個被阻塞的執行緒
tpserver010176210054_ot7_HSF_JStack_log_waite1_words
查詢獲得這個鎖的執行緒,被阻塞在第二個鎖
tpserver010176210054_ot7_HSF_JStack_log_locked1waite2_words
再查詢獲得這個第二個鎖的執行緒
tpserver010176210054_ot7_HSF_JStack_log_locked1locked2_words
果然正在執行!

2,利用工具檢視執行緒趨勢

這裡給大家介紹一個簡單的tdump查閱工具samuraism
http://samuraism.jp/samurai/en/index.html

圖例:samurai_tuli
可以看到在第一次tdump裡就是由於執行緒”HSFBizProcessor-8-thread-123“阻塞了其他執行緒
samurai1_words
第二次tdump裡是由於執行緒”HSFBizProcessor-8-thread-457“阻塞了其他執行緒
samurai2_words

3,檢視四次tdump的趨勢

拆分之前連續四次的tdump檔案為四個檔案,統計趨勢
tpserver010176210054_ot7_HSF_JStack_log_locked_number_words
被阻塞的執行緒逐漸減少

結論

由於ibatis的ClassInfo存在效能瓶頸,而tpserver暴露的相關依賴資料庫的服務又很多,而且外部對tpserver的服務訪問量又不斷在增長,當超過這個閥值後,就會觸發ibatis的ClassInfo兩個鎖衝突,降低應用的qps,提升相關請求的rt,使得消費方出現服務超時異常,影響使用者。

措施

既然知道了原因,那解決起來就比較順手了,主要從幾個方面入手:提升效能、降低併發、繞過耗時,

提升效能的方法可以有:

這其實是ibatis的一個效能bug連結
修改方式連結

tpserver010176210054_ot7_HSF_JStack_log_ibatis_fix_words

由於ibatis已經沒有維護,無bug的版本已經遷移為mybatis,所以就有兩個策略

1,遷移到mybatis二方包

遷移需要做一定的修改,見mybatis官方文件,存在較大工作量。

2,path現有ibatis二方包

在現有ibatis的版本下,直接path修改,然後釋出一個全新path版本的二方包。

降低併發的方法可以有:

3,擴容tpserver普通叢集

這個簡單有效,但是明顯不符合優秀工程師的原則!!!

4,拆分tpserver

依照領域模型將tpserver內各個業務拆分開來,這樣壓力會分攤到各個子系統,ClassInfo的併發得到有效降低。

繞過耗時的方法可以有:

5,應用預熱

在應用內部spring容器初始化之後,提前訪問併發量大相關表的查詢服務,確保ibatis建立起需要快取的相關class資訊,最後再註冊hsf服務。
以上方法不是依賴各種流程,就是需要投多資源,所以先走起方法1的流程,流程通過後升級應用依賴。然後方法5可以立即實施併發布到線上。
剩下的方法可以藉著其它機會在後續再逐步推進。

驗證

依照方法5的指導思想,新增預熱程式碼後上線釋出,如下圖,效果很好!!!當然也有副作用,tpserver的啟動時間又延長了30秒!!!
tpserver010176210054_ot7_HSF_JStack_log_warmup

後續展望

  • 各種第三方lib的升級速度得提上來了
  • 按照領域與服務分層拆分龐大的tpserver


相關文章