一次閘道器事故的總結
本文是記錄2015年的一次線上事故,引發的對系統架構和效能的思考,以及如果考慮系統容災和降級的問題。在大流量高併發下的網際網路環境下,如何做到系統穩定,不被異常流量擊垮,是我等軟體人需要思考和不斷優化改造的目標。
問題來了
記得是一個週六的早上,手機就開始不停的震動,拿起一開,是洪水般的報警簡訊,大概印象記錄如下:
-
網關係統報出效能報警,各種介面超時
-
網關係統報出方法可用率報警,統計閘道器呼叫量峰值 190w+/min
-
Jimdb(redis)出現 failover 主從切換報警,而且相當頻繁
-
Jimdb 出現 Connection Lost 報警
-
聯絡 dba 對 Jimidb 進行擴容,並調大連線數
-
Jimdb 恢復
-
網關係統 CPU 使用率飆升,伺服器出現宕機
-
對 Jstack 進行分析,發現 youngGc 異常頻繁
-
通過對 Jmap 進行分析,發現有 sql 會對 mysql 進行全表掃庫
-
...
網關係統是對外提供 API 服務呼叫的系統,對使用資源進行監控發現:
-
閘道器日誌 JMQ 積壓 8億+
-
閘道器日誌 ES 容量 2T
-
閘道器日呼叫量 8億+
-
閘道器資料庫 CPU 使用率 85%
通過 UMP 統一監控平臺,可以看到閘道器呼叫量,按小時維度統計如下圖:
按日維度統計如下圖:
當日和時段的呼叫量已經遠高於平時呼叫量,這種異常的呼叫量對網關係統造成了極大的壓力,對資料庫 CPU 使用率情況,按小時維度監控發現(如下圖),CPU 使用率隨著系統問題惡化,逐步趨近 100%。
去找原因
因為網關係統主要是對外提供 API 服務呼叫的系統,所以問題極有可能是某個介面的異常呼叫量引發整個網關出現的問題,通過對報警的排查,最終定位是訂單查詢介面呼叫量異常猛增,引發的整體網關係統的問題,介面呼叫量資料也對得上,見下圖:
但這種連帶問題是如何發生的?
下圖是對網關係統出現問題的分析:
網關係統分為閘道器接入層、閘道器應用層和閘道器監控共三個部分,閘道器接入層又包括閘道器防禦校驗和閘道器接入分發兩塊。分析問題原因,大致過程如下:
問題一、閘道器呼叫需要對呼叫進行實時監控,監控包括閘道器效能和閘道器錯誤,而這些資料都是通過 jimdb 進行儲存的。而當閘道器呼叫量異常猛增時,造成了 jimdb 的分片熱點,由於 jimdb 的資源不足,造成 jimdb 不停 failover,很快惡化服務不可用。
問題二、閘道器呼叫日誌是閘道器監控的重要部分,日誌是通過 jmq 傳送到 es 中儲存。當閘道器呼叫量異常猛增時,請求 jmq 的連線不足,而傳送給 jmq 的訊息也開始擠壓,最終傳送日誌的執行緒造成伺服器執行緒數高,執行緒切換,以及記憶體回收等,形成 jvm 頻繁的 young gc,並且 jmq 也近乎不可用。
問題三、閘道器接入層中防禦校驗邏輯中校驗 token 和 pin 的業務依賴於 jimdb,而當 jimdb 不可用時,程式碼會穿透 jimdb 進行查詢 mysql,進而對大量請求訪問 msyql,造成 mysql 巨大壓力,最終 mysql 惡化成服務宕機。
問題四、jimdb 穿透造成 mysql 宕機,閘道器應用層查詢資料庫中的 sql 大量超時,再次對網關係統形成壓力,而閘道器防禦層的一些程式碼 bug,造成引數丟失,形成查詢資料庫進行全庫掃描,系統最後一顆稻草斷了,網關係統崩塌了。
快速搶救
通過問題看到網關係統的架構存在著不合理設計,異常流量下,重啟伺服器已經無濟於事。需要迅速進行止損,既然分析出了問題點,就針對問題點進行補丁式的搶救,大概思路如下:
解決問題一、對閘道器效能監控功能進行降級,畢竟不是核心主流程,沒有該功能閘道器還是可以呼叫,降級之後不再進行 jimdb 資料儲存。後期也需要考慮改造其他方案。
解決問題二、對閘道器日誌功能也可以進行降級,同樣不是核心主流程,由於是單個介面的引發的災害,可以針對閘道器介面的日誌進行黑名單處理,對呼叫量大的介面呼叫日誌不儲存離線日誌。
解決問題三、由於不可理設計形成了 jimdb 熱 key,暫時無法解決,所以改造在呼叫 jimdb 之前增加 jvm cache 防禦熱點,同時修復程式碼 bug,以及對 jimdb 進行拆離,將閘道器日誌的 jimdb 和閘道器校驗使用的 jimdb 進行隔離部署,實現互不影響。
解決問題四、網關係統是需要對異常流量可以進行自動降級和熔斷的,設計時不能因為個別介面把整個網關係統打掛,所以在閘道器接入層,增加了流量控制邏輯,對併發數進行限流,通過令牌桶實現,進而讓閘道器擁有自我保護能力。
解決問題五、閘道器應用層檢查 sql,對可能全面掃庫的程式碼修改,有效保證資料庫安全。後期將閘道器應用層和接入層進行系統隔離部署,真正有效解耦閘道器接入和處理業務邏輯,完整方案見下圖。
想一想
異常流量是怎麼造成的?
因為閘道器是面向客戶端提供 API 服務的,客戶端發新版,有個邏輯是 5 秒查詢一次訂單介面,想要做到實時,而客戶端升級數量上萬後,這種呼叫量就直接將閘道器打死了。
這些異常又是怎麼消退的?
因為當時客戶端與服務端還是 http 無狀態請求,客戶端登入也是基於該閘道器,當時嘗試通過 http 長連結通知客戶端下線,但是效果甚微,網路頻寬基本被打滿了,是否有效觸達客戶端都是未知,最終是通過重啟伺服器,迫使客戶端超時重新登入,在登入邏輯中禁止該版本客戶端進行訪問閘道器,逐漸實現洩洪的。
這一過程是艱難而漫長的。
而也是這一次事故,促成了對網關係統的改造,實現了客戶端 TCP 長連結閘道器,以及業務服務 HTTP 閘道器的剝離和構建,也因此深入理解了 Netty、IO/">NIO 等技術,併成功應用在業務中。現在 2018年,客戶端 TCP 長連線同時線上量進幾十萬,感觸頗多。
想來,每一次問題都可能是一次技術飛躍和成功的機會。