分布式系統(tǒng)的挑戰(zhàn):八個(gè)關(guān)鍵故障的解讀
常見故障原因
我們所謂系統(tǒng)發(fā)生故障是指當(dāng)它無(wú)法再按照規(guī)格要求向用戶提供服務(wù)時(shí)。這種故障是由故障所引發(fā)的,即內(nèi)部組件或系統(tǒng)所依賴的外部組件發(fā)生故障。有一些故障是可以被容忍的,對(duì)用戶沒有明顯的影響,而另一些則會(huì)導(dǎo)致系統(tǒng)故障。
為了構(gòu)建具備容錯(cuò)能力的應(yīng)用程序,首先需要了解可能出現(xiàn)哪些問題。在接下來(lái)的內(nèi)容中,我們將探討一些最常見的故障根本原因。到最后,您可能會(huì)思考如何應(yīng)對(duì)各種不同類型的故障。
1、硬件故障
在計(jì)算機(jī)系統(tǒng)中,任何物理組件都可能發(fā)生故障。硬盤驅(qū)動(dòng)器、內(nèi)存模塊、電源供應(yīng)器、主板、固態(tài)硬盤、網(wǎng)絡(luò)接口卡以及中央處理器等,均可能因各種原因停止正常運(yùn)作。有時(shí),硬件故障甚至可能導(dǎo)致數(shù)據(jù)損壞。更甚者,整個(gè)數(shù)據(jù)中心可能會(huì)因電力中斷或自然災(zāi)害而發(fā)生故障。不過(guò),正如我們將在后文討論的那樣,通過(guò)引入冗余措施,許多這些基礎(chǔ)設(shè)施故障是可以應(yīng)對(duì)的。雖然你可能認(rèn)為這些硬件故障是導(dǎo)致分布式應(yīng)用程序失敗的主要原因,但實(shí)際上,它們往往因?yàn)橐恍┓浅F胀ǖ脑蚨霈F(xiàn)問題。
2、錯(cuò)誤處理
在最近對(duì)五個(gè)流行的分布式數(shù)據(jù)存儲(chǔ)系統(tǒng)進(jìn)行的用戶報(bào)告研究中,發(fā)現(xiàn)了多數(shù)災(zāi)難性故障的根本原因是對(duì)非致命錯(cuò)誤處理的不當(dāng)。在大多數(shù)情況下,這些錯(cuò)誤處理中的問題本可以通過(guò)簡(jiǎn)單的測(cè)試來(lái)檢測(cè)出來(lái)。例如,有些錯(cuò)誤處理程序完全忽略了錯(cuò)誤。其他則捕捉了過(guò)于通用的異常,比如Java中的Exception,然后出于毫無(wú)充分理由的原因終止整個(gè)進(jìn)程。還有一些錯(cuò)誤處理程序只實(shí)施了部分功能,甚至包含了"FIXME"和"TODO"這樣的注釋?;剡^(guò)頭來(lái)看,這或許并不太令人意外,因?yàn)殄e(cuò)誤處理往往被視為次要問題。這也是Go語(yǔ)言如此重視錯(cuò)誤處理的原因。在后續(xù),我們將更加深入地探討測(cè)試大型分布式應(yīng)用程序的最佳實(shí)踐。
3、配置更改
配置更改是導(dǎo)致災(zāi)難性故障的主要根本原因之一。問題不僅僅在于錯(cuò)誤的配置可能會(huì)引發(fā)問題,還有一種情況是對(duì)于啟用了很少被使用的功能的有效配置更改不再按預(yù)期工作,或者從來(lái)沒有按預(yù)期工作。配置更改之所以特別危險(xiǎn),是因?yàn)樗鼈兊挠绊懣赡軙?huì)拖延。如果一個(gè)應(yīng)用程序僅在真正需要配置值的時(shí)候才讀取它們,那么無(wú)效的配置更改可能會(huì)在幾小時(shí)甚至幾天后才顯現(xiàn)出問題,從而無(wú)法及早被發(fā)現(xiàn)。因此,配置更改應(yīng)該像代碼更改一樣受到版本控制,經(jīng)過(guò)測(cè)試,并在變更時(shí)采取預(yù)防性驗(yàn)證措施。在持續(xù)部署的背景下,我們將探討代碼和配置更改的安全發(fā)布實(shí)踐。
4、單點(diǎn)故障
單點(diǎn)故障(SPOF)是指當(dāng)某個(gè)組件故障時(shí),整個(gè)系統(tǒng)都會(huì)崩潰的情況。實(shí)際上,系統(tǒng)可能存在多個(gè)單點(diǎn)故障。
通常情況下,人員往往成為關(guān)鍵的單點(diǎn)故障(SPOF),如果將他們置于可能獨(dú)立引發(fā)災(zāi)難性故障的位置,幾乎可以確定他們最終會(huì)引發(fā)這種故障。例如,人類故障經(jīng)常發(fā)生在某人需要按照特定順序手動(dòng)執(zhí)行一系列操作步驟而不能犯任何錯(cuò)誤的情況下。相比之下,計(jì)算機(jī)在執(zhí)行指令方面表現(xiàn)出色,因此自動(dòng)化應(yīng)盡可能得到充分利用。
另一個(gè)常見的SPOF是DNS。如果客戶端無(wú)法解析應(yīng)用程序的域名,它們將無(wú)法連接到該應(yīng)用程序。造成這種情況的原因多種多樣,從域名到期到整個(gè)根級(jí)域名崩潰都有可能。
類似地,應(yīng)用程序用于其HTTP端點(diǎn)的TLS證書也是一個(gè)SPOF。如果證書過(guò)期,客戶端將無(wú)法與應(yīng)用程序建立安全連接。
理想情況下,在系統(tǒng)設(shè)計(jì)階段就應(yīng)該識(shí)別這些SPOF。檢測(cè)它們的最佳方法是審查每個(gè)系統(tǒng)組件,然后詢問如果其中任何一個(gè)出現(xiàn)故障會(huì)發(fā)生什么。有些SPOF可以通過(guò)引入冗余來(lái)解決,而其他一些則無(wú)法。在這種情況下,唯一的選擇就是減小SPOF的影響范圍,也就是在它們發(fā)生故障時(shí)對(duì)系統(tǒng)造成的損害。我們將在后續(xù)討論的許多彈性模式都旨在減小故障的影響范圍。
5、網(wǎng)絡(luò)故障
當(dāng)客戶端向服務(wù)器發(fā)送請(qǐng)求時(shí),期望在不久后收到響應(yīng)。在最佳情況下,請(qǐng)求后很快就會(huì)收到響應(yīng)。如果出現(xiàn)了不同尋常的情況,客戶端有兩種選擇:要么繼續(xù)等待,要么因?yàn)槌瑫r(shí)異?;蝈e(cuò)誤而取消請(qǐng)求。慢速的網(wǎng)絡(luò)調(diào)用是分布式系統(tǒng)的潛在隱患,因?yàn)榭蛻舳藷o(wú)法確定響應(yīng)是否最終會(huì)到達(dá),因此它可能會(huì)長(zhǎng)時(shí)間等待,或者干脆不取消請(qǐng)求,從而導(dǎo)致性能下降,而這種問題很難調(diào)試。這種故障也被稱為"灰色故障",它非常微妙,很難迅速或準(zhǔn)確地檢測(cè)到。由于其特性,灰色故障可能會(huì)輕松使整個(gè)系統(tǒng)崩潰。
當(dāng)引入故障檢測(cè)和超時(shí)的概念時(shí),會(huì)有很多原因?qū)е挛茨塬@得及時(shí)響應(yīng)。舉例而言,服務(wù)器可能因在處理請(qǐng)求時(shí)速度極慢或崩潰而未能及時(shí)響應(yīng);或者可能是網(wǎng)絡(luò)丟失了少量數(shù)據(jù)包,從而引發(fā)大量的重傳和延遲。
慢速網(wǎng)絡(luò)調(diào)用 是分布式系統(tǒng)的潛在殺手。由于客戶端不清楚響應(yīng)是否最終會(huì)抵達(dá),因此它可能花費(fèi)大量時(shí)間等待,甚至可能不會(huì)放棄,這將導(dǎo)致性能下降,而問題難以調(diào)試。這種故障也被稱為 灰色故障,這是一種如此微妙以至于不能迅速或準(zhǔn)確檢測(cè)到的故障。由于其特性,灰色故障很容易將整個(gè)系統(tǒng)帶入崩潰的邊緣。
6. 資源泄漏
從觀察者的角度來(lái)看,一個(gè)非常慢的進(jìn)程與根本不運(yùn)行的進(jìn)程幾乎沒有什么不同,兩者都無(wú)法執(zhí)行有用的工作。資源泄漏是導(dǎo)致進(jìn)程變慢的最常見原因之一。
內(nèi)存可能是受泄漏影響最廣泛的資源之一。內(nèi)存泄漏會(huì)導(dǎo)致內(nèi)存消耗逐漸增加。即使是帶有垃圾回收功能的編程語(yǔ)言也容易受到泄漏的影響:如果對(duì)不再需要的對(duì)象保留了引用,垃圾回收器將無(wú)法刪除它。當(dāng)泄漏消耗了大量?jī)?nèi)存以至于剩下很少時(shí),操作系統(tǒng)將開始積極將內(nèi)存頁(yè)面交換到磁盤。此外,垃圾回收器將更頻繁地啟動(dòng),試圖釋放內(nèi)存。所有這些都會(huì)消耗CPU周期并使進(jìn)程變得更慢。最終,當(dāng)物理內(nèi)存不再可用,且交換文件空間耗盡時(shí),進(jìn)程將無(wú)法分配內(nèi)存,導(dǎo)致大多數(shù)操作失敗。
內(nèi)存只是眾多可能泄漏的資源之一。以線程池為例:如果從線程池獲取的線程進(jìn)行同步阻塞的HTTP調(diào)用而沒有設(shè)置超時(shí),并且該調(diào)用從未返回,線程將不會(huì)返回到線程池。由于線程池具有有限的最大大小,如果持續(xù)喪失線程,最終線程將耗盡。
或許你認(rèn)為在前述情況下,采用異步調(diào)用而不是同步調(diào)用會(huì)有所幫助。然而,現(xiàn)代HTTP客戶端使用套接字池以避免重復(fù)創(chuàng)建TCP連接并支付性能代價(jià)。如果請(qǐng)求未設(shè)置超時(shí),連接將永遠(yuǎn)不會(huì)返回到池中。由于池的最大大小是有限的,最終將不再有可用的連接。
此外,你的代碼并不是唯一訪問內(nèi)存、線程和套接字的代碼。你的應(yīng)用程序所依賴的庫(kù)也會(huì)使用相同的資源,它們可能會(huì)遇到我們剛剛討論的相同問題。
7. 負(fù)載壓力
每個(gè)系統(tǒng)都有其負(fù)載容量,也就是它可以承受的負(fù)載極限。因此,當(dāng)導(dǎo)向系統(tǒng)的負(fù)載持續(xù)增加時(shí),它遲早會(huì)觸及到這個(gè)極限。
然而,有機(jī)會(huì)的負(fù)載增長(zhǎng)為系統(tǒng)提供了逐漸擴(kuò)展和增加容量的時(shí)間,這是一種情況;而突然和意外的洪水是另一種情況。
例如,考慮應(yīng)用程序在一段時(shí)間內(nèi)收到的請(qǐng)求數(shù)量。傳入請(qǐng)求的速率和類型可能會(huì)隨時(shí)間變化,有時(shí)甚至?xí)蚋鞣N原因突然改變:
- 請(qǐng)求可能具有季節(jié)性。因此,例如,根據(jù)一天中的時(shí)間,應(yīng)用程序可能會(huì)受到來(lái)自不同國(guó)家用戶的訪問。
- 有些請(qǐng)求比其他請(qǐng)求昂貴得多,以意外的方式濫用系統(tǒng),例如高速爬蟲抓取數(shù)據(jù)。
- 有些請(qǐng)求是惡意的,例如試圖通過(guò)DDoS攻擊來(lái)飽和應(yīng)用程序的帶寬,從而拒絕合法用戶的訪問。盡管一些負(fù)載激增可以通過(guò)自動(dòng)增加容量(例如,自動(dòng)擴(kuò)展)來(lái)處理,但其他情況下,系統(tǒng)需要拒絕請(qǐng)求以保護(hù)自己免受過(guò)載。
8. 級(jí)聯(lián)故障
或許你認(rèn)為,如果一個(gè)系統(tǒng)有數(shù)百個(gè)進(jìn)程,那么如果其中一小部分進(jìn)程變得緩慢或無(wú)法訪問,這應(yīng)該不會(huì)有太大的影響。
故障的問題在于它們具有蔓延傳播的潛力,會(huì)從一個(gè)進(jìn)程傳播到另一個(gè)進(jìn)程,直到整個(gè)系統(tǒng)崩潰。這種情況發(fā)生在系統(tǒng)組件相互依賴的情況下,其中一個(gè)組件的故障會(huì)增加其他組件故障的概率。
通常需要采取足夠大的糾正措施來(lái)打破這個(gè)循環(huán),比如暫時(shí)阻止流量到首次復(fù)制品。不幸的是,一旦這些故障開始,它們非常難以減輕,而預(yù)防故障從一個(gè)組件傳播到另一個(gè)組件是最佳方法。