故障描述html
做爲一個老牌OTA公司,公司早些年訂單主要來源是PC網站和呼叫中心。我在入職公司大約半年後,遇到一次很是詭異的故障。有一天早上,大概也是這個季節,陽光明媚,程序猿剛起牀,洗洗涮涮,準備去迎接初戀般的工做日,卻忽然收到一大堆報警,線上消息隊列大量積壓;固然,我仍是一如既往的很是勤奮地在9點以前就到公司的;可是做爲一名新員工,環視四周,組內其餘員工都還沒到公司,運維也都在路上,故障就這樣忽然降臨了。我趕忙開機登陸堡壘機,鏈接線上機器,tail 錯誤日誌。可是線上10幾個系統,我看了好幾個系統,都沒有發現有什麼錯誤,這就尷尬了。可是統計消息隊列,超過好幾千的消息待消費。我當時就在想,這些消息都是什麼鬼。截圖以下:mysql
圖一spring
看到這裏,你必定會問數量爲604和881個的消息是作什麼?知道這些消息的邏輯不就解決問題了麼?話說當時我也是這麼想的,但是當時我做爲一名新人,纔開始接觸業務不到3個月,還徹底沒有這麼深的業務積累(這個時候知道業務是多麼重要)。sql
既然系統看不到任何錯誤,我也沒有什麼辦法了,當時由於剛入職沒多久,還有點寄但願於領導來解決。轉眼間半個小時已通過去,故障仍然沒有恢復,從業務反饋來看,微信支付寶等支付方式不受影響。受影響的只是信用卡支付(其實當時信用卡量佔比挺高)和分銷支付(後來瞭解到,其實這兩種模式都是信用卡支付模式)。領導還在堵車,運維也只是到了幾個小兵,我找運維把幾個機器的stack打印了一下,也沒有發現什麼問題;運維也陸續到崗,運維準備出大招,重啓系統。可是就在此時,忽然系統自動恢復了。全部積壓的消息自動被消費,信用卡支付也能夠了。好,系統居然有自我修復功能,佩服;數據庫
故障緣由分析編程
後來,通過一番努力,仍是找到一點蛛絲馬跡,我發現系統的一個消費消息的定時任務,在故障期間一直在報錯,由於是高可用的job機制,4臺機器,只有搶佔到鎖的服務器才能獲取到訪問數據庫消息權利,因此報錯信息比較分散,4臺機器都有。服務器
圖二微信
能夠斷定,這個sql一直異常致使job根本沒法獲取到消息,而另外的生產者又不斷的往隊列放消息,進而致使消息積壓。兩個系統關係以下:網絡
圖三框架
雖然故障總結了,可是咱們內心也不踏實,如何找到系統故障的根本緣由,以防止之後再次出現這種故障呢?
方法有兩種:
一、去查代碼,全部跟這個表相關的sql,都須要仔細review一下,可是你也不必定能查到緣由,由於這個場景確定是很差復現的,要否則早就發現這個問題了。
二、藉助外力,從DB層面查致使這個sql沒法執行成功的緣由;
方法1看似簡單,其實很是不可行。首先,雖然跟這個表相關的sql,只有幾十個,可是都是正常的sql,沒有使用for update鎖死表的sql。也沒有存在未關閉的事務,由於事務是經過AOP配置的;
因此只能寄但願於方法2了,讓DBA去查;
好歹咱們的DBA足夠給力,只用了1天多的時間就查出來了。
DBA回覆以下:
一、有事務沒有及時提交,且鏈接也沒有關閉,致使該事務一直處於開啓狀態並持有鎖,後續update操做是全表掃描,所以會有鎖等待。
二、最後該鏈接後續一直沒有操做,達到空閒超時3600秒(咱們的故障時間正好也是1小時)後被mysql server斷開,鎖才被釋放。(mysql設置:wait_timeout = 3600)
最牛B的是DBA貼出了沒有提交事務的SQL;sql我就不貼出來了,咱們根據DBA提供的線索,找到了代碼的問題;
故障根本緣由
後來咱們查看代碼,如上面DBA所說,消息沒有被消費處理,是由於有一個mysql客戶端,即咱們的支付應用程序,在進行快捷支付的時候,向隊列插入一條記錄,而後在事務中向第三方發起了調用。使用的是httpclient工具發起的調用,可是設置超時時,只設置了鏈接超時時間(connectionTimeout)爲30秒,沒有設置響應超時時間(soTimeout),這樣當出現網絡問題時,程序就會一直等第三方響應,而後事務也一直沒有提交。而在job程序中,須要將這個queue的全部記錄給更新,可是又取不到表鎖(見圖三),就不斷的報lock wait timeout的錯誤;其實對使用spring AOP框架的研發,很容易犯這種錯誤。咱們從 https://tech.meituan.com/2018/04/19/trade-high-availability-in-action.html 這篇總結裏面的1.5段也能看出,美團支付也在這塊也栽過坑;
圖四
到這裏,其實故障緣由已經很清楚了,咱們在代碼層面也確實查到了問題。由於DBA提供的sql中,連insert sql的主機名也列了出來,而且現場沒有被破壞,咱們使用jstack應該還能找到正在等待的線程纔對;因而在時隔故障2天后,咱們又讓運維把那臺機器的jvm stack給打印了一下,果真發現等待的線程仍然存在。
堆棧以下:
圖五
與之對應的代碼,我就不貼了;
解決方法
一、臨時解決方法,將響應超時時間設置上,但這沒法根除問題,只是下降再次出現問題的機率;
二、長久解決方案,修改框架,使用編程式事務,將全部遠程調用從事務中剝離出來。
知識點
一、事務,spring AOP
二、httpclient,超時設置
求關注