1. 程式人生 > 其它 >美團點評酒店後臺故障演練系統

美團點評酒店後臺故障演練系統

本文由曾鋆、海智、亞輝、孟瑩四位作者共同創作完成。

背景介紹

隨著海量請求、節假日峰值流量和與日俱增的系統複雜度出現的,很有可能是各種故障。在分析以往案例時我們發現,如果預案充分,即使出現故障,也能及時應對。它能最大程度降低故障的平均恢復時間(MTTR),進而讓系統可用程度(SLA)維持在相對較高的水平,將故障損失保持在可控範圍內。但是,經過對2016全年酒店後臺研發組所有面向C端系統的線上事故分析後發現,在許多情況下,由於事故處理預案的缺失或者預案本身的不可靠,以及開發人員故障處理經驗的缺失,造成大家在各種報警之中自亂了陣腳,從而貽誤了最佳戰機。

正如上面所講,由“上游流量”和“依賴”導致的故障數量,佔了全年故障的45%。

一個經典的case:

2016年3月10日,Tair叢集因流量過大掛掉,導致酒店後臺某組一個ID生成器的功能失效,無法獲取ID,插入資料庫失敗。 值班同學找到相應的開發同學,執行之前的預案(切換到基於資料庫的ID生成器),發現不能解決當前問題(有主鍵衝突)。 值班同學經過分析,臨時修改資料庫中的欄位值,修復問題。

從上面的例子可以看出,業務方針對系統可能出現的異常情況,雖然一般設有預案,但是缺乏在大流量、有故障情況下的演練,所以往往在故障來臨時,需要用一些臨時手段來彌補預案的不足。

整體方案

綜上所述,我們要有一套常態化的“故障演練”機制與工具來反覆驗證,從而確保我們的服務能在正常情形下表現出正常的行為,在異常狀況下,也要有正確、可控的表現。

這個服務或是工具能執行:

  • 容量與效能評估。
  • 故障演練,進而進行依賴梳理、預案驗證,保證服務柔性可用。

這樣才能夠做到在節假日與大促時心中有數,在提高系統服務能力的同時增加開發人員應對與處理故障的經驗。

下面,以酒店後臺switch研發組開發的“Faultdrill”系統為例,向大家介紹一下我們在這方面的經驗。

業界案例和實際業務比對

在壓力測試(以下簡稱“壓測”)和故障演練方面,業界已有很多種實踐。

壓測

壓測有單模組壓測和全鏈路壓測兩種模式。

阿里雙11、京東618和美團外賣都有過線上全鏈路壓測的分享(美團外賣的分享參見美團點評技術沙龍第6期回顧)。

全鏈路壓測有幾點明顯的優勢:

  • 基於線上環境,測出的效能資料準確;
  • 相較於線下,測試環境完備,不存在單點、低配置等問題;
  • 線上環境有完備的監控報警系統。

但與此同時,全鏈路壓測也有較高的實踐成本:

  • 需要有明顯的波谷期;
  • 需要清理壓測資料,或者申請資源構建影子儲存;
  • 真實流量難構造,需要準備虛擬商家和虛擬使用者;
  • 需要有完備的監控報警系統。

酒店業務模式和外賣/購物類的業務模式不太一樣。首先,沒有明顯的波峰波谷(夜裡也是訂房高峰期,你懂的),因為沒有明顯的波峰波谷,所以清理資料/影子表也會帶來額外的影響。真實流量的構造也是一個老大難問題,需要準備N多的虛擬商家和虛擬使用者。

所以酒店最早推的是單業務模組級別的壓力測試和故障演練,大家先自掃門前雪。

美團點評內部的通訊協議以Thrift為主,業界的相關壓力測試工具也有很多:

  • JMeter作為老牌的壓力測試工具,通常作為HTTP協議的測試,也可以通過自定義外掛的方式實現Thrift協議的測試。
  • TCPCopy的方式主要是關注“真實流量”。
  • loading_test是美團點評內部的壓力測試工具。

這幾種方式都不滿足我們的要求,我們的要求是:真實流量、method級別控制、操作簡單。

所以我們準備自己造個輪子 :)

需求:業務方低成本接入,流量在叢集級別(AppKey級別,AppKey相當於同樣功能叢集的唯一標識,比如訂單搜尋叢集的AppKey為xx.xx.xx.order.search)以最低成本進行復制、分發,以及最重要的在這個過程中的安全可控等等都是對測試工具、框架的潛在要求。

基於以上,我們開發了流量複製分發服務。它的核心功能是對線上真實流量進行實時複製並按配置分發到指定的機器,來實現像異構資料遷移一樣進行流量定製化的實時複製與遷移。

藉助流量複製分發服務進行功能和系統級別的測試,以達到:

  • 容量規劃。在穩定與效能保證的基礎上儘可能的節約資源。
  • 核心鏈路梳理,強弱依賴區分,並做到服務之間鬆耦合。
  • 系統瓶頸。在真實請求流量加倍下暴露服務瓶頸點。
  • 故障獨立,容災降級等等。

故障演練

如果要演練故障,首先要模擬故障(我們不可能真跑去機房把伺服器炸了)。自動化的故障模擬系統業界已有實踐,如Netflix的SimianAmy,阿里的MonkeyKing等。

美團點評內部也有類似的工具,casekiller等等。

SimianAmy和casekiller設計思路相仿,都是通過Linux的一些“tc”、“iptables”等工具,模擬製造網路延時、中斷等故障。這些工具都是需要root許可權才可以執行。美團點評的伺服器都需要使用非root使用者來啟動程序,所以這種思路暫不可行。

這些工具都有一定要求,比如root許可權,比如需要用Hystrix來包裝一下外部依賴。比如我想製造一個表的慢查詢、想製造Redis的某個操作網路異常,就有些麻煩。

所以我們準備自己造個輪子 :)

需求:業務方低成本接入,流量以最低成本進行故障的“製造”和“恢復”,無需釋出、對程式碼無侵入就可以在後臺介面上進行故障的場景配置、開啟與停止。

基於以上,我們開發了故障演練系統。它是一個可以針對叢集級別(AppKey級別)的所有機器,隨意啟停“故障”的故障演練平臺。可以在無需root許可權的前提下,構造任意method級別的延時或者異常類故障。

詳細設計

我們的設計思路是:

  1. 複製線上流量到影子叢集。
  2. 通過對同樣配置影子叢集的壓測,獲得系統抗壓極值。
  3. 製造針對外部介面/DB/Cache/MQ等方面的故障,在影子叢集上測試降級方案、進行演練。

流量複製系統

架構設計中參考了DubboCopy的系統設計,增加了一個SDK,解除了對TCPCopy的依賴。

形成以下的流程:

① 需要壓測方先依賴我們的SDK包,在需要壓測的具體實現方法上打上註解@Copy,並註明取樣率simplingRate(預設取樣率為100%)。

@Copy(attribute = CopyMethodAttribute.READ_METHOD, simplingRate = 1.0f)
public Result toCopiedMethod() {
}

② 正式流量來時,非同步將流量發往copy-server。

③ copy-server根據流量中的資訊(interface、method、serverAppKey)來獲取壓測配置(影子叢集的AppKey,需要放大幾倍)。

④ 根據壓測配置,對影子叢集按照放大倍數開始發包。

協議分析

Thrift原生協議情況下,如果你沒有IDL(或者註解式的定義),你根本無法知道這條訊息的長度是多少,自然做不到在沒有IDL的情況下,對報文進行解析轉發。感謝基礎元件同學做的統一協議方面的努力,讓ThriftCopy這個事情有了可行性 :)

除了公網RPC介面使用HTTP協議以外,美團點評內部RPC協議都統一為一種相容原生Thrift協議的“統一協議”。

total length指定其後訊息的總長度,包含2B的header length+訊息頭的長度+訊息體的長度+可能的4B的校驗碼的長度。header length指定其後訊息頭的長度。

header裡的內容有TraceInfo和RequestInfo等資訊,裡面有clientAppKey、interfaceName、methodName等我們需要的資訊。

client功能

應用啟動時

  1. 客戶端啟動時,首先獲取copyServer的IP list(非同步起定時任務不斷重新整理這些IP列表)。
  2. 建立相應的連線池。
  3. 初始化對@Copy做切面的AOP類。

RPC請求到來時

  1. 命中切面,先同步處理業務邏輯。
  2. 非同步處理下面的邏輯:
  • 通過取樣率判定本次請求引數是否需要上報到copyServer。
  • 通過當前的JoinPoint找到method和args,再通過method找到相應的Thrift生成程式碼中的send_xx方法,對連線池中的一個TSocket傳送資料。

以上,便可進行流量的複製與分發,在服務設計上,Client端儘量做到輕量高效,對接入方的影響最小,接入成本低,並且在整個流量複製的過程中安全可控。另外,在Client,當前針對美團點評使用的Thrift協議,進行:

  • 流量染色。對原請求在協議層重寫染色其中的clientAppKey和requestMethodName,分別重寫為""和"${rawMethodName}_copy"在請求接收方可以呼叫特定方法即可判斷請求是否是由“流量複製分發服務”的轉發請求。
  • 讀寫標記。通過在註解上attribute屬性標記轉發介面為讀還是寫介面,為後續的流量分發做好準備。
  • 負載均衡。支援服務端的橫向擴充套件。
  • 取樣控制。對流量複製/取樣進行控制,最大限度的定製複製行為。

server功能

應用啟動時

  1. 讀取資料中存住的壓測配置(fromAppKey、targetAppKey、放大倍數)。
  2. 根據targetAppKeys去分別獲取IP list(非同步起定時任務不斷重新整理這些IP列表)。
  3. 建立相應的連線池。

流量到來時

  1. 根據“統一通訊協議”解包,獲取fromAppKey、interfaceName、methodName等我們要的資訊。
  2. 非同步處理下面的邏輯:
  • 根據流量中的資訊(interface、method、serverAppKey)來獲取壓測配置(影子叢集的AppKey,需要放大幾倍)。
  • 尋找相應的連線池。
  • 根據放大倍數n,迴圈n次傳送收到的ByteBuf。

故障演練系統

我們的需求是,可以叢集級別(AppKey級別)而不是單機級別輕鬆的模擬故障。

模擬什麼樣的故障呢?

我們調研了很多種實現方式:

經過調研對比,選定了基於javaagent進行位元組碼注入,來實現對個目標物件的攔截並注入演練邏輯。

client功能

應用啟動時

需要修改啟動時的JVM引數-javaagent:WEB-INF/lib/hotel-switch-faultdrill-agent-1.0.2.jar

  1. 載入client包,對RedisDefaultClient、MapperRegistry、DefaultConsumerProcessor、DefaultProducerProcessor、MTThriftMethodInterceptor、ThriftClientProxy等類進行改造。
  2. 從遠端獲取設定好的相應的script,比如“java.lang.Thread.sleep(2000L);”比如“throw new org.apache.thrift.TException("rpc error");”(會有非同步任務定時更新script列表)。
  3. 根據script的AppKey和faultType,生成一個md值,做為key。
  4. 根據script的文字內容,動態生成一個類,再動態生成一個method(固定名稱為invoke),把script的內容insertBefore進來。然後例項化一個物件,做為value。
  5. 將上面的key和value放入一個Map。
  6. 目標類(比如RedisDefaultClient)的特定method執行之前,先執行map裡對應的object中的invoke方法。

方法執行時

  1. 執行之前查詢當前的策略(map中的對應object),如果沒有就跳過。
  2. 如果有就先執行object中的invoke方法。起到“java.lang.Thread.sleep(2000L);”比如“throw new org.apache.thrift.TException("rpc error");”等作用。

server的功能

server的功能就比較簡單了,主要是儲存使用者的設定,以及提供給使用者操作故障啟停的介面。

案例

舉個例子

本機單測呼叫beta03叢集上的服務介面distributeGoodsService.queryPrepayList 5000次。

在目標叢集beta04上收到此介面的25000次轉發過來的請求。

多次請求,觀察CAT(美團點評開發的開源監控系統,參考之前的部落格)報表,其中Receive為接收到的需要轉發的次數,Dispatch為實際轉發數量。

開始模擬故障:Redis故障、MTThrift故障。

請求介面:控貨的filter介面(訪問快取),故障前、後響應時間對比圖:

客戶端設定超時1s,接收到請求都超。

開啟Thrift介面故障演練,介面:com.meituan.hotel.goods.service.IGoodsService.queryGoodsStrategyModel,延時3s,設定介面超時6s。

故障前後響應時間對比:

這樣就完成了一次加壓情況下的故障演練過程,隨後就可以讓團隊成員按照既定預案,針對故障進行降級、切換等操作,觀察效果。定期演練,縮短操作時間,降低系統不可用時間。

總結

“故障演練系統”目前具備了流量複製和故障演練兩方面的功能。希望能通過這個系統,對酒店後臺的幾個關節模組進行壓測和演練,提高整體的可用性,為消費者、商家做好服務。

後續“故障演練系統”還會繼續迭代,比如把忙時流量存起來,等閒時再回放;還有如何收集response流量,進而把抽樣的request和response和每天的daily build結合起來;如何在故障演練系統中,模擬更多更復雜的故障等等。還會有更多的課題等待我們去攻克,希望感興趣的同學可以一起參與進來,和我們共同把系統做得更好。

參考資料

  1. 分散式會話跟蹤系統架構設計與實踐,美團點評技術部落格.
  2. 基於TCPCopy的Dubbo服務引流工具-DubboCopy.
  3. 從0到1構建美團壓測工具,美團點評技術部落格.
  4. javassit.