一、引言
一個優秀的架構師通常都是一個悲觀主義者,除了設計好能夠支撐業務持續發展的優雅架構,另一個容易被忽略的重要能力在于充分考慮失敗場景。如果對失敗場景考慮不夠充分,輕則出現業務不可用,影響使用者體驗和企業聲譽;重則導緻資料永久丢失、業務再無恢複可能。2001 年 9 月 11 日,美國世貿中心雙子大廈遭受了誰也無法預料的恐怖打擊,災難發生前約有 350 家企業在世貿大廈中工作,事故發生一年後,重返世貿大廈的企業變成了 150 家,有200 家企業由于重要資訊系統的破壞,關鍵資料的丢失而永遠的關閉、消失了,其中的一家公司聲稱自己要恢複到災難前的狀态需要 50 年的時間。“Everything fails, all the time”,無論是在傳統軟體時代還是在網際網路、雲時代,系統終究會在某個時間點失敗,面向失敗的設計理念數十年來并沒有多大的變化,不同的是在分布式、雲架構的網際網路時代:失敗将由小機率偶發事件變成常态,同時應對和處理失敗的具體實作方式也大相徑庭。
二、無所不在的失敗場景
單個技術點在絕大部分時間都能按照設想正常工作,但是當規模和複雜度到達一定程度,失敗其實無所不在。當你的業務場景從服務企業内部的幾百号員工變成面向上億的外部使用者,你不确定你的使用者群會有些什麼樣的角色,也不知道他們會在你的系統平台上創造出什麼樣的業務行為;當你的技術架構從單機、一體機演進到分布式的多層、多元件架構,原本5個以内的技術元件可能變成了今天的500個,并且為了用較低的成本保持服務能力的擴充能力,你可能放棄了穩定性更好但也昂貴的商業技術、轉而用開源自建來替代。網際網路業務快速發展不僅直接帶來了流量、安全等不确定性,同時促使了技術架構的快速演進,使架構變得越來越複雜,這些因素都将導緻失敗發生的機率大幅提升。當人類的工作、生活越來越依賴網際網路,一旦出現失敗,造成的影響和損失将是空前巨大的。在遠古時代,人類沒有自來水也沒有電,一切都很好;今天如果停電停水一段時間,相信很多人都會無法适應。而網際網路正在逐漸演變成跟水和電一樣的基礎設施。
失敗的原因多種多樣,抽象來看可以分為以下幾類:
硬體問題
首先硬體是有生命周期的,它一定會老化,并且你不知道它會在什麼時候壞;其次硬體是一個實體,它存在于客觀環境當中,它的狀态會受外部環境幹擾,比如火災、地震等外力因素都可能導緻硬體損壞;最後所有硬體都會存在殘次品,你很可能就是那個不幸者。通常情況下單個硬體出問題的機率不高,但是當有幾十萬的硬體裝置,硬體的失敗問題每天都會發生。
軟體bug
即便是最優秀程式員寫出來的程式,經過最優秀測試同學的嚴格測試後的代碼,上線依然無法做到完全沒有bug。網際網路業務疊代往往講究一個“快”字,以往幾個月或者幾年更新一次的軟體程式,現在一周就需要更新一次或者多次,這大幅提升了軟體出錯的可能性。
配置變更錯誤
系統運作态的日常運維過程當中,難免會因為疏忽或者考慮不周全導緻災難。當上萬名技術同學跟上百個變更系統做笛卡爾積,哪怕是6個9的可靠性,依舊無法做到萬無一失。全局的流量入口、權限與安全驗證體系、統一網關與接口平台等技術環節是可能促發全站不可用的重要風險點,對于影響面大的配置的變更需要尤為謹慎。
系統惡化
原本工作地很好的程式随着時間的推移可能有一天不再正常工作,舉幾個常見的例子:自增變量運作了很長一段時間後出現越界、緩存随着資料量的逐漸變大而出現空間不足、資料庫連接配接池随着機器的擴容而不夠用等等。千萬不要認為運作良好的系統是不會出問題的,它的代碼裡面可能藏了定時炸彈,隻是你不知道會在什麼時間點爆炸。
超預期流量
某一天你的系統可能突然會承受遠超過預期的每秒請求數,特别是在“中國特色”的網際網路場景之下,你很難精确預估系統各個時間點的業務通路量。
外部攻擊
你需要考慮各種攻擊行為,包含流量攻擊和安全攻擊。你的系統可能随時會面臨着DDOS和CC類攻擊,你傳輸的資料可能會被盜取或者篡改。
依賴庫問題
你的系統很可能會用大量的二方庫或者三方庫,它們對你來說是黑盒子,你不了解它們存在哪些風險,并且你無法掌控。這些庫可能會存在漏洞、可能會有bug,可能會大量消耗你的系統資源,總之不要太信任它們。
依賴服務問題
你依賴的服務也一定不會100%可用,它們可能會逾時,可能會失敗。當依賴服務逾時的時候,如果你沒有很好的處理,可能會導緻你自己的系統無法工作,在分布式場景下,這種失敗狀态會持續輻射,最終導緻大面積的不可用。
三、如何面向失敗設計
作為一個悲觀主義者,你需要在一開始的系統設計階段就考慮到以上各種失敗場景,把面向失敗當成系統設計的一部分,并且準備好從失敗中恢複的政策,這有助于更好地提升整個系統的可用性。隻有你意識到事情會随着時間的推移而失敗,并将這種思想融入到體系結構中,那麼在失敗發生的時候你才能完全不受影響或者将失敗損失降到最低。面向失敗的設計理念數十年來并沒有多大的變化,一些好的經典原則在今天依舊被廣泛運用。
備援設計避免單點故障
硬體和軟體都不可靠,環境和人都存在極大的不确定性,雖然無法避免失敗場景的發生,但是可以通過備援設計來規避局部失敗對系統的影響。備援設計避免單點故障這一政策在網際網路技術架構中處處可見,比如重要的服務通常都會部署多個、資料庫的主備結構、服務調用的重試機制、存儲的多副本等概念都屬于這一範疇。
面向失敗的宏觀多活架構
除了局部失敗場景,你的系統可能還面臨着大範圍的失敗場景。大範圍的原因有兩個:1、天災,比如火災、地震、台風、雷電等大的自然災害可能導緻大面積的基礎裝置被毀壞;2、人禍:人的失誤或者刻意破壞行為有時候也會釀成大禍,如操作錯誤、 破壞、植入有害代碼和恐怖襲擊。“面向失敗的宏觀多活架構”從宏觀架構的高可用層面來解決系統的整體可用性問題,随着技術的演進,冷備、熱備、兩地三中心、異地多活等應對大範圍失敗場景的技術體系這些年頻頻被提起。
服務能力與依賴調用自我保護
如何來衡量一個軟體系統的設計是否優良?一條很重要的衡量标準——在任何情況之下你的軟體系統都應該工作在目前環境的最優狀态。每個人都知道機翼是飛機的重要部件,一旦機翼出現問題,飛機很可能就會墜落。然而在二戰當中,許多戰鬥機即便機翼千瘡百孔了,依然保持着最佳戰鬥能力;甚至還有更誇張的情況:1983年的一次戰鬥機演習當中,一架飛機由于事故損失了一個機翼,這架缺少一個機翼的飛機依然保持了飛行能力、最終完成安全着陸。軟體系統由兩部分構成:系統自身的代碼和依賴的庫以及服務。“服務能力與依賴調用自我保護”需要從這兩塊分别切入建構系統在任意情況都始終工作在最佳狀态的能力。服務限流、系統負載保護、給依賴的服務設定逾時或者資源限制等都是相應的應對政策。
為一切不可預料的情況備好預案
能夠抵抗失敗和從失敗中快速恢複是面向失敗設計的核心思想,然而即便已經做了萬全的設計,也并非所有的失敗場景都是系統能夠自動抵禦的。你需要考慮到所有的失敗場景,并準備好相應的應對預案。為一切不可預料的情況備好預案才能在失敗場景真正發生時做到有條不紊。做好預案需要對失敗場景有全面的考慮:會發生哪些失敗?失敗會帶來什麼問題?應對政策是什麼?預期的恢複時間多久?恢複後的影響面有多大?需要通知到哪些角色?等這一系列的因子構成了一個完整的預案體系。
自動化運維管控
大量的系統故障是因為人的失誤造成的,即便讓一個優秀的運維工程師進行一萬次同樣的運維操作也難免不出錯。唯一的解決辦法便是在運維過程當中盡可能降低人為操作的比重。系統化、白屏化是第一個階段——将人為的操作步驟固化成系統程式,避免操作失誤;自動化是第二個階段——将正确的決策過程也固化成智能程式,避免決策失誤。同時所有的運維動作都需要遵循灰階原則,即便出現了失誤也能控制好爆炸半徑。
精細化的監控體系
面向失敗設計不僅要求你的系統足夠健壯,同時要求你能夠在第一時間感覺到失敗的發生。無論是自動化的系統恢複,還是人為介入,如果你壓根都不知道是哪裡出問題了,一切都将束手無策。精細化的監控體系一方面能夠在出現問題的時候以最快的速度将最準确的資訊傳遞到人或者運維系統,同時它還能夠展現趨勢、進行提前預警。AI技術的結合使得監控領域在近幾年得到了新的發展驅動力:智能監控報警、根因定位、智能預測、智能決策等能力都是學術界和工程界非常熱衷的課題。
故障與攻防演練錘煉容災應急能力
最後,即便以上工作都做好了,你也不能高枕無憂去等待失敗的到來。你的設計、系統、流程、技術人員等需要通過不斷的演練來保障能力和進化更新。對于代價非常巨大的事件,做好前期的充分演練是非常有必要的,比如軍事演練、消防演練等都屬于這一範疇。而系統不可用的代價對于企業來講很可能是無法承受的,是以需要在平時做好充分的演練:通過故障與攻防演練錘煉容災應急能力,對面向失敗的設計做好充分驗證。隻有當所有的失敗場景都被提前演練過,當失敗真正來臨時才能做到胸有成竹。
What is More
這篇文章僅僅是面向失敗設計的序言,後面會有更多在對應領域深耕多年同學的精彩文章,敬請期待。。。。
文章來源:AlibabaTechQA
開發者社群整理