作者| 阿裡文娛進階開發工程師 雲琰
一、淺談面向失敗的設計
1. 什麼是面向失敗的設計?
面向失敗的設計,就是以“失敗”為對象,天然為了失敗而存在的設計思想,在一開始的系 統設計階段就考慮到各種失敗場景,把面向失敗當成是系統設計的一部分,準備好從失敗中恢 複的政策。
2. 為什麼要面向失敗設計?
因為無所不在的失敗場景,比如硬體問題,軟體 bug,配置變更錯誤,系統惡化,超預期流量,外部攻擊,依賴庫問題,依賴服務問題。
并且,這些一旦失敗,輕則出現業務不可用,影響使用者體驗和企業聲譽;重則導緻資料永 久丢失、業務再無恢複可能。例如,911 事故發生前,約有 350 家企業在世貿大廈工作。事故 後,有 200 家企業由于重要系統被破壞,關鍵資料丢失而倒閉。

3. 怎麼面向失敗設計?
在軟體的整個生命周期中,不同的階段面對失敗場景有不同的應對規則。在設計階段将系 統的架構簡單化,結構層次分明;在釋出階段盡量做到最小變更原則,小規模,多次疊代;在運維階段做好周期性壓測,最小依賴。
二、播控是如何實作面向失敗設計的
1. 播控架構設計
優酷播放控制系統從層級結構上将系統分成 3 層,對外服務層、基礎服務層、資料存儲層。
第一層:對外服務層包含播控 SDK,播控查詢服務,播控變更服務,播控篩選服務;
第二層:基礎服務層包含播控任務排程,播控資料庫服務;
第三層:資料存儲層包含分布式緩存,資料庫,開放搜尋。
從功能上,播控系統共 3 大核心線路讀核心、寫核心、篩選服務。
線路一:讀核心是整個系統的核心服務,負責優酷、洋芋、天貓視訊展示、透出能力;
線路二:寫核心負責整個系統政策變更核心服務;
線路三:篩選服務提供管理背景多資料源篩選服務。
這樣設計的特性如下:
1)線路上讀、寫分離,核心與篩選分離(主次分離),單獨鍊路變更失敗之後不會影響核心鍊路的功能;
2)核心高并發查詢服務讀取資料庫方式從資料庫服務隔離開,當壓力增加後,無單點瓶頸, 可以無限擴充;
3)任一鍊路故障,其他鍊路正常運作,穩定提供服務;
4)資料庫服務遵從容量設計原則,針對查詢服務、變更服務相同接口有不同限流政策。
2. 資料庫不可用兜底設計
資料庫兜底設計的目的在于在資料庫不可用的極端場景下,保證生産鍊路不挂,且不會影 響到業務方。實作方式通過全局統一開關,切換核心讀鍊路和寫鍊路使用緩存作為臨時的資料 持久層,并把資料變更堆積在消息隊列中,待資料庫可用後再切換回資料庫,并消費消息隊列。
讀鍊路以查詢是否資源是否可播為例,兜底開關打開,則隻查詢緩存中是否存在該資源。 若緩存中存在資源,則傳回查詢結果。若不存在,則判斷資源是否在禁播黑名單,根據判斷結 果傳回是否可播。可見兜底開關打開後,雖然傳回的是降級之後的結果,但至少擺脫了對資料 庫的依賴,整個流程仍能正常進行下去。
寫鍊路以新增資源政策為例,兜底開關打開,則先查詢緩存中是否存在該資源政策。若緩 存中存在該資源政策,則流程結束。若不存在,則更新緩存,并發送消息,用于延時消費,更 新資料庫。可以看到思路仍然是以緩存作為臨時資料源,降低對資料庫的依賴,同時借助了消 息隊列的異步特性,來解決資料庫恢複後,資料庫資料與緩存資料的一緻性問題。
3. 緩存備援更新和緩存一緻性檢測設計
高并發的業務場景下,資料庫是十分寶貴的資源,也是非常薄弱的環節。系統設計時不可 避免的需要使用緩存作為緩沖。上一小節針對資料庫不可用的極端場景設計了方案,本小節将 會提出兩種解決緩存與資料不一緻問題的方案。
1)緩存備援更新 緩存備援更新是在資料發生變更時,通過同步與異步備援更新的方式,保證不會出現髒數
據,并且保證最終的資料一緻性。
1-2-6-7 即為一個常見的旁路緩存更新結構。但由于業務的複雜性,在播控變更服務執行完
資料變更後,需要通過步驟 3 發消息通知外部系統,外部系統接收到消息後通過步驟 4-5 進行
反查,但由于步驟 3 和步驟 6-7 都是異步過程,可能在 6-7 執行完成之前,3-4-5 已經執行,則 查詢到的是更新之前的髒資料。
緩存備援更新的思路是,變更服務除了通過消息異步更新緩存之外,同步的調用播控任務 排程,去更新緩存。同步更新保證緩存資料的準确性,不會出現髒資料,異步更新保證更新鍊 路的高可用,不會因為接口調用失敗而導緻緩存更新失敗,達到最終資料的一緻性。
2)緩存一緻性檢測 緩存備援更新是用來確定寫入緩存時資料的正确性。除此之外,我們還提供一種寫入緩存
後的緩存一緻性檢測機制,專門檢查近期寫入緩存的資料是否符合預期,檢測到不一緻後進行
上報和清理。
檢測流程如下,利用 guava cache 設定 5s 過期機制,在資料變更寫入資料庫和緩存的 5s 之 後,通過監聽器監聽 guava cache 的過期,來觸發資料對比的檢測,并根據檢測結果做出相應的處理。
4. 遠端/近端調用動态切換設計
播控除了通過 RPC 調用方式為上遊業務提供基礎服務,還提供了播控 SDK 可以供業務方 進行近端調用。近端調用對于業務方來說在降低延時,提高成功率方面都有很好的效果,對于 播控來說也能減輕伺服器壓力,節省資源。但為了防止出現穩定性問題,也必須面向失敗設計, 提供降級方案,其中一種降級方案是流量動态切換方案。當業務方機器能夠承受的 SDK 流量上 限超過門檻值時,多餘的流量會動态分散到播控中心系統;這在實際中是很有效的,在一些單機 QPS 達到 1000 甚至更高的場景下,確定 SDK 不會壓垮業務方機器。極端情況下,所有流量都 可以回切到播控中心系統。
三、總結
一個優秀的架構師往往都是悲觀主義者,除了設計好能夠支撐業務持續發展的優雅架構, 另一個容易被忽略的重要能力在于充分考慮失敗場景。面向失敗設計是一種十分重要的設計思 路,需要能夠防患于未然,在設計階段考慮到各種失敗場景,提前準備好預案,并且做好充分 的演練和驗證。隻有這樣才能夠在失敗來臨時從容應對。