分布式系統(tǒng)中,級(jí)聯(lián)故障是最可怕的
互聯(lián)網(wǎng)服務(wù)提供商面臨快速增長挑戰(zhàn)的同時(shí)還要管理不斷增長的系統(tǒng)分布。盡管服務(wù)的可靠運(yùn)行對(duì)像 Google、Amazon 和 Co. 等這樣的大公司來說非常重要,但它們的系統(tǒng)還是會(huì)一次又一次地出現(xiàn)故障,導(dǎo)致大量中斷和帶來糟糕的客戶體驗(yàn)。
舉幾個(gè)例子,比如深受影響的 Gmail(2012)、AWS DynamoDB(2015)以及最近的 Facebook(2021)。在這種情況下,人們經(jīng)常會(huì)遇到所謂的級(jí)聯(lián)故障,導(dǎo)致超出普通系統(tǒng)故障的不良并發(fā)癥。但是,考慮到他們的預(yù)算和技術(shù)知識(shí),即使是在線業(yè)務(wù)的大玩家,為什么也不能完全避免這種故障呢?你可以為自己的系統(tǒng)使用哪些切實(shí)可行的風(fēng)險(xiǎn)緩解方法?
這篇文章是想帶你了解如何通過防止故障傳播來提高大型分布式系統(tǒng)的彈性。
1. 級(jí)聯(lián)故障
級(jí)聯(lián)故障是由于正反饋循環(huán)而隨時(shí)間增加的故障。典型行為最初由單個(gè)節(jié)點(diǎn)或子系統(tǒng)故障觸發(fā)。然后它會(huì)將負(fù)載分散到其他系統(tǒng)的節(jié)點(diǎn)上,這反過來又進(jìn)一步增加系統(tǒng)故障的可能性,從而導(dǎo)致惡性循環(huán)或滾雪球效應(yīng)。
級(jí)聯(lián)故障的重要性體現(xiàn)在三個(gè)方面:首先,它們可以在短時(shí)間內(nèi)導(dǎo)致整個(gè)服務(wù)停機(jī)。其次,受影響的系統(tǒng)不會(huì)像處理常見的問題那樣恢復(fù)正常,而是會(huì)逐漸惡化。最終要依賴人為干預(yù)才行。最后,在最壞的情況下,級(jí)聯(lián)故障可能會(huì)在沒有警告的情況下突然發(fā)生,因?yàn)樨?fù)載分布和故障會(huì)迅速發(fā)生。
這篇文章主要關(guān)注點(diǎn)是分布式計(jì)算環(huán)境中的級(jí)聯(lián)故障,但它們也可能發(fā)生在其他各種領(lǐng)域,例如,電力傳輸、金融、生物學(xué)以及生態(tài)系統(tǒng)。因此,它們是一種相當(dāng)普遍的現(xiàn)象,與自然界中發(fā)現(xiàn)的模式有些相似。為了更好地了解計(jì)算機(jī)科學(xué)中的級(jí)聯(lián)故障是什么樣的,讓我們看一個(gè)具體的案例。
2. 案例研究:2015 年的 AWS DynamoDB 中斷
AWS DynamoDB 是一種高可擴(kuò)展的非關(guān)系數(shù)據(jù)庫服務(wù),分布在多個(gè)數(shù)據(jù)中心,提供高度一致的讀取操作和 ACID 事務(wù)。它被 Netflix、Airbnb 和 IMDb 等多個(gè)知名的互聯(lián)網(wǎng)公司所使用。
我們要研究的級(jí)聯(lián)故障示例的事件發(fā)生在 2015 年 9 月 20 日,當(dāng)時(shí) DynamoDB 在美國東部地區(qū)超過四個(gè)小時(shí)不可用。涉及兩個(gè)子系統(tǒng):存儲(chǔ)服務(wù)器和元數(shù)據(jù)服務(wù),兩者都在多個(gè)數(shù)據(jù)中心有副本。存儲(chǔ)服務(wù)器向元數(shù)據(jù)服務(wù)請(qǐng)求其數(shù)據(jù)分區(qū)分配的所謂成員資格。如圖 1 所示。
圖 1:存儲(chǔ)服務(wù)器和元數(shù)據(jù)服務(wù)
對(duì)于成員資格(也用于數(shù)據(jù)分區(qū)的分配)的請(qǐng)求,存在相應(yīng)的超時(shí)時(shí)間。如果超時(shí)了,則相應(yīng)的存儲(chǔ)服務(wù)器會(huì)重試并將其自身排除在服務(wù)之外。
該事件的一個(gè)不幸的先決條件是 DynamoDB 引入的一個(gè)新特性,稱為全球二級(jí)索引(GSI)。這使客戶可以更好地訪問他們的數(shù)據(jù),但缺點(diǎn)是會(huì)顯著增加元數(shù)據(jù)表的大小。因此,導(dǎo)致處理時(shí)間變得很長。同時(shí)不幸的是元數(shù)據(jù)服務(wù)的容量和成員請(qǐng)求的超時(shí)時(shí)間沒有做出相應(yīng)的調(diào)整。
真正的問題是由于一個(gè)短暫的網(wǎng)絡(luò)問題導(dǎo)致一些存儲(chǔ)服務(wù)器(處理非常大的元數(shù)據(jù)表)成員資格請(qǐng)求超時(shí),這些服務(wù)器變得不可用并且還不斷的重試它們的請(qǐng)求。
這就導(dǎo)致元數(shù)據(jù)服務(wù)超負(fù)荷運(yùn)轉(zhuǎn),進(jìn)而減慢響應(yīng)速度并導(dǎo)致更多服務(wù)器重新提交其成員資格請(qǐng)求,因?yàn)樗鼈円策_(dá)到了超時(shí)時(shí)間。結(jié)果,元數(shù)據(jù)服務(wù)的狀態(tài)進(jìn)一步惡化。盡管多次嘗試增加資源,系統(tǒng)仍然陷入故障循環(huán)數(shù)小時(shí)。最終,問題只能通過中斷對(duì)元數(shù)據(jù)服務(wù)的請(qǐng)求來解決,即服務(wù)基本上離線。
結(jié)果是美國東部地區(qū)發(fā)生了廣泛的 DynamoDB 中斷,這是一個(gè)典型的級(jí)聯(lián)故障例子。但是,陷入這種錯(cuò)誤循環(huán)的系統(tǒng)的底層概念和模式是什么?
3. 級(jí)聯(lián)故障的原因
首先,要說的是級(jí)聯(lián)故障的觸發(fā)點(diǎn)看起來是多種多樣的。例如,可能是新推出的特性、維護(hù)、流量流失、cron 作業(yè)、分布式拒絕服務(wù)(DDoS)、限流等。它們的共同點(diǎn)是它們?cè)谝唤M有限資源的上下文中工作,這意味著可能會(huì)出現(xiàn)服務(wù)器過載、資源耗盡和服務(wù)不可用等影響 。讓我們?cè)敿?xì)看看這些:
服務(wù)器過載
最常見的原因是服務(wù)器過載。發(fā)生這種情況時(shí),系統(tǒng)性能下降通常會(huì)影響系統(tǒng)的其他區(qū)域。如圖 2 所示,在初始場景(左)中,來自兩個(gè)反向代理的負(fù)載分布在集群 A 和 B 之間,因此集群 A 以每秒 1000 個(gè)請(qǐng)求的假設(shè)最大容量運(yùn)行。
在第二種情況(右)中,集群 B 發(fā)生故障,整個(gè)負(fù)載都到達(dá)集群 A,這就會(huì)導(dǎo)致集群A過載。集群 A 現(xiàn)在必須每秒處理 1200 個(gè)請(qǐng)求并開始出現(xiàn)異常行為,導(dǎo)致性能遠(yuǎn)遠(yuǎn)低于所預(yù)期的每秒 1000 個(gè)請(qǐng)求。
圖 2:集群 A 和 B 根據(jù)容量(左)接收負(fù)載,如果集群 B 發(fā)生故障,集群 A 接收過載(右)
資源耗盡
服務(wù)器的資源是有限的。如果負(fù)載增加到某個(gè)閾值以上,服務(wù)器的性能指標(biāo)(例如,延遲或錯(cuò)誤率)就會(huì)惡化,這意味著更高的崩潰風(fēng)險(xiǎn)。隨后的影響取決于導(dǎo)致瓶頸的資源類型,例如:
如果 CPU 不足,可能會(huì)出現(xiàn)各種問題,包括請(qǐng)求速度較慢、排隊(duì)效應(yīng)過多或線程不足。
如果內(nèi)存/RAM 被過度使用,任務(wù)可能會(huì)崩潰,或者緩存命中率會(huì)降低。
此外,線程饑餓可能直接導(dǎo)致錯(cuò)誤或?qū)е陆】禉z查失敗。
在這種情況下對(duì)主要原因進(jìn)行故障排除通常很痛苦。這是因?yàn)樗婕暗慕M件是相互依賴的,并且根本原因可能隱藏在復(fù)雜的事件鏈之后。例如,假設(shè)可用于緩存的內(nèi)存較少,導(dǎo)致緩存命中次數(shù)減少,因此后端負(fù)載較高,以及此類組合。
服務(wù)不可用
當(dāng)資源耗盡導(dǎo)致服務(wù)器崩潰時(shí),流量會(huì)轉(zhuǎn)到其他服務(wù)器,從而增加這些服務(wù)器崩潰的可能性。這樣一個(gè)服務(wù)器的崩潰循環(huán)就建立了。更壞的情況是這些問題會(huì)一直保持在系統(tǒng)中,因?yàn)槟承C(jī)器仍然處于關(guān)閉狀態(tài)或正在重新啟動(dòng)的過程中,而持續(xù)增加的流量會(huì)阻止它們完全恢復(fù)。
一般來說,當(dāng)我們將流量從不健康節(jié)點(diǎn)重新分配到健康節(jié)點(diǎn)時(shí),總是存在級(jí)聯(lián)故障的風(fēng)險(xiǎn)。這可能是編排系統(tǒng)、負(fù)載均衡器或任務(wù)調(diào)度系統(tǒng)的情況。為了解決級(jí)聯(lián)故障,我們需要仔細(xì)研究所涉及的組件之間的關(guān)系。
4. 跳出循環(huán)——如何修復(fù)級(jí)聯(lián)故障
從 DynamoDB 的案例中可以看出,修復(fù)級(jí)聯(lián)故障非常棘手。尤其是從大型科技公司的角度來看,分布式系統(tǒng)增加了很多復(fù)雜性,這使得跟蹤各種互連變得更加困難。
我們這里使用一種被稱為因果循環(huán)圖(CLD)的方法來描述這些(級(jí)聯(lián))關(guān)系。CLD 是一種建模方法,有助于可視化復(fù)雜系統(tǒng)中的反饋回路。圖 3 可視化了 AWS DynamoDB 中斷的 CLD。
解釋如下:箭頭表示初始變量和后續(xù)變量之間的動(dòng)態(tài)。例如,如果元數(shù)據(jù)服務(wù)的延遲增加,超時(shí)次數(shù)就會(huì)增加,所需的重試次數(shù)也會(huì)增加。如果系統(tǒng)中的影響是高度不平衡的,即正負(fù)的數(shù)量在很大程度上不相等,則存在一個(gè)加強(qiáng)循環(huán)。這意味著系統(tǒng)可能對(duì)級(jí)聯(lián)故障很敏感。
圖 3:2015 年 AWS DynamoDB 中斷的因果循環(huán)圖
現(xiàn)在,針對(duì)級(jí)聯(lián)故障場景,我們有好多種措施可以采用。第一個(gè)也是最直觀的選擇是增加資源。在上圖中,可以看到在循環(huán)中元數(shù)據(jù)服務(wù)容量引入了減號(hào)。如果增加,它會(huì)減弱循環(huán)的增強(qiáng),不過,這可能沒有用,正如我們?cè)?AWS 中看到的那樣。除了增加資源外,還可以采用其他策略:
- 盡量避免健康檢查失敗,以防止系統(tǒng)因過度健康檢查而死亡。
- 如果出現(xiàn)線程阻塞請(qǐng)求或死鎖,請(qǐng)重新啟動(dòng)服務(wù)器。
- 顯著降低流量,然后慢慢增加負(fù)載,以便服務(wù)器可以逐漸恢復(fù)。
- 通過丟棄某些類型的流量切換到降級(jí)模式。
- 消除批處理/不良流量,通過減少非關(guān)鍵或錯(cuò)誤工作來減輕系統(tǒng)負(fù)載。
這個(gè)可能會(huì)讓系統(tǒng)的某些服務(wù)不可用并且客戶是能夠感知到的,因此最好首先避免級(jí)聯(lián)故障。
5. 避免級(jí)聯(lián)故障
有許多方法可以使分布式系統(tǒng)對(duì)級(jí)聯(lián)故障具有魯棒性。
一方面,大型互聯(lián)網(wǎng)公司已經(jīng)在思考如何防止系統(tǒng)陷入級(jí)聯(lián)錯(cuò)誤,比如通過對(duì)錯(cuò)誤進(jìn)行隔離,為此市面上已經(jīng)開發(fā)出來許多工具和框架。例如,Hystrix(來自 Netflix),一個(gè)延遲和容錯(cuò)庫,或者 Sentinel。對(duì)于前者,Netflix 已經(jīng)做出了進(jìn)一步的發(fā)展,即自適應(yīng)并發(fā)限制(可以在此處閱讀更多內(nèi)容[4])。但總的來說,這些工具都是將外部調(diào)用包裝成某種數(shù)據(jù)結(jié)構(gòu),試圖抽象出關(guān)鍵點(diǎn)。
另一方面,就是目前正在發(fā)展的技術(shù),有一些復(fù)雜的解決方案,例如,實(shí)現(xiàn)所謂的sidecar代理,諸如 Istio 這樣的服務(wù)網(wǎng)格。其他的一些示例比如 Envoy 或 Haproxy。
除了這些解決方案之外,我們還要牢記某些系統(tǒng)設(shè)計(jì)概念。例如,嘗試減少系統(tǒng)中同步調(diào)用的數(shù)量。通過應(yīng)用發(fā)布-訂閱模式設(shè)計(jì)(比如使用 Kafka)從編排(orchestration)模式轉(zhuǎn)變?yōu)閰f(xié)調(diào)(choreography)模式。面對(duì)不斷增加的流量,這種解決方案通常會(huì)更健壯。其他方法例如,執(zhí)行容量規(guī)劃(取決于用例)也可能有所幫助。這通常意味著實(shí)施自動(dòng)供應(yīng)和部署、自動(dòng)擴(kuò)展和自動(dòng)修復(fù)的解決方案。在這種情況下,對(duì) SLA 和 SLO 的密切監(jiān)控就顯得很重要。
現(xiàn)在,為了更好地理解底層解決方案的方法,我們可以看看分布式系統(tǒng)中的典型反模式,在級(jí)聯(lián)故障的情況下應(yīng)該避免這些反模式。Laura Nolan 提出了其中的六項(xiàng),我們會(huì)就風(fēng)險(xiǎn)緩解策略方面進(jìn)行討論。
反模式 1:接受數(shù)量不受限制的請(qǐng)求
隊(duì)列/線程池中的任務(wù)數(shù)量應(yīng)該是受限的。這可以在請(qǐng)求過多的情況下控制服務(wù)器何時(shí)以及如何慢下來(slow down)。該設(shè)置應(yīng)該在服務(wù)器可以達(dá)到峰值負(fù)載的范圍內(nèi),但不要太多從而導(dǎo)致它阻塞。在這種情況下,對(duì)于系統(tǒng)和用戶來說,快速失敗總比長時(shí)間掛起要好。在代理或負(fù)載均衡器方面,通常是通過速率限制策略來實(shí)現(xiàn),例如,用來避免 DDoS 和其他形式的服務(wù)器過載。
但是還有其他許多方面要考慮的,例如,在隊(duì)列管理的上下文中,因?yàn)榇蠖鄶?shù)服務(wù)器在線程池前面都有一個(gè)隊(duì)列來處理請(qǐng)求。如果數(shù)量增加超過隊(duì)列的容量,請(qǐng)求將被拒絕。隊(duì)列中等待的大量請(qǐng)求會(huì)占用更多內(nèi)存并增加延遲。如果請(qǐng)求的數(shù)量接近恒定,那么一個(gè)小隊(duì)列或不需要隊(duì)列就可以了。這意味著如果流量增加,請(qǐng)求會(huì)被立即拒絕。如果預(yù)期會(huì)有更大的偏差,則應(yīng)使用更長的隊(duì)列。
此外,為了保護(hù)服務(wù)器免受過度負(fù)載的影響,減載和優(yōu)雅降級(jí)的概念是可行的選擇。負(fù)載脫落用于在過載的情況下盡可能地保持服務(wù)器的性能。這是通過簡單地返回 HTTP 503(服務(wù)不可用)狀態(tài)碼來確定請(qǐng)求優(yōu)先級(jí)的方法丟棄流量來實(shí)現(xiàn)的。
一個(gè)更復(fù)雜的變體是優(yōu)雅降級(jí),它會(huì)逐漸切換到較低質(zhì)量的查詢響應(yīng)。這些可能會(huì)運(yùn)行得更快或更有效。但是,這一定是一個(gè)經(jīng)過深思熟慮的解決方案,因?yàn)樗鼤?huì)給系統(tǒng)增加很多復(fù)雜性。
反模式 2:危險(xiǎn)的(客戶端)重試行為
為了減少系統(tǒng)的工作量,確保避免過度的重試行為是很重要的。指數(shù)退避是一種合適的方法,它的做法是重試的時(shí)間間隔連續(xù)增加。還可以使用所謂的抖動(dòng)(jitter)機(jī)制,即在重試間隔中添加隨機(jī)噪聲。這可以防止系統(tǒng)被累積的“負(fù)載波”擊中,這也稱為重試放大(參見圖 4)。
圖 4:重試放大的典型模式
此外,還有一種稱為熔斷器的設(shè)計(jì)模式。熔斷器可以被認(rèn)為是一種開關(guān)。在初始狀態(tài)下,來自上游服務(wù)的命令被允許傳遞給下游服務(wù)。如果錯(cuò)誤增加,熔斷器會(huì)切換到打開狀態(tài),系統(tǒng)會(huì)快速出現(xiàn)故障。這意味著上游服務(wù)出錯(cuò),允許下游服務(wù)恢復(fù)。一段時(shí)間后,請(qǐng)求再次逐漸增加。例如,在 Hystrix(上面已經(jīng)提到)中,實(shí)現(xiàn)了某種熔斷器模式。
減輕危險(xiǎn)重試行為的另一種方法是設(shè)置服務(wù)器端重試預(yù)算,設(shè)置每分鐘可以重試請(qǐng)求的數(shù)量。超出預(yù)算的所有內(nèi)容都將被丟棄。但是,我們要綜合全局來看。一定要避免在軟件架構(gòu)的多個(gè)級(jí)別上執(zhí)行重試,因?yàn)檫@可能會(huì)呈指數(shù)級(jí)增長。
最后,需要注意的是,重試應(yīng)該是冪等的并且沒有副作用。無狀態(tài)調(diào)用 在系統(tǒng)復(fù)雜性方面也是有益的。
反模式 3:因輸入錯(cuò)誤而崩潰
系統(tǒng)應(yīng)確保服務(wù)器不會(huì)因輸入錯(cuò)誤而崩潰。此類崩潰與重試行為相結(jié)合,可能導(dǎo)致災(zāi)難性后果,例如,一臺(tái)服務(wù)器接著一臺(tái)相繼崩潰。在這方面,尤其應(yīng)仔細(xì)檢查來自外部的輸入。使用模糊測(cè)試是檢測(cè)這些類型問題的好方法。
反模式 4:基于鄰近的故障轉(zhuǎn)移
確保不要把所有流量都重定向到最近的數(shù)據(jù)中心,因?yàn)樗部赡軙?huì)過載。此處適用的邏輯與集群中單個(gè)服務(wù)器的故障相同,也就是一臺(tái)機(jī)器接著一臺(tái)發(fā)生故障。
因此,為了提高系統(tǒng)的彈性,必須在故障轉(zhuǎn)移期間以受控方式重定向負(fù)載,這意味著必須考慮每個(gè)數(shù)據(jù)中心的最大容量。基于 IP-Anycast 的 DNS 方式最終會(huì)將流量轉(zhuǎn)發(fā)到最近的數(shù)據(jù)中心,這可能會(huì)出現(xiàn)問題。
反模式 5:失敗引起的工作
故障通常給系統(tǒng)帶來額外的工作。特別是,故障發(fā)生在少數(shù)幾個(gè)節(jié)點(diǎn)上,最終可能會(huì)給剩余其他節(jié)點(diǎn)帶來大量的額外工作(例如,副本)。這可能會(huì)帶來有害的反饋循環(huán)。一種常見的緩解策略是延遲或限制副本數(shù)量。
反模式 6:啟動(dòng)時(shí)間長
一般而言,在開始時(shí)處理過程通常較慢。這是因?yàn)閷?shí)例需要做初始化過程和運(yùn)行時(shí)優(yōu)化。故障轉(zhuǎn)移后,服務(wù)和系統(tǒng)經(jīng)常由于負(fù)載過重而崩潰。為了防止這種情況,我們希望系統(tǒng)可以更快的啟動(dòng)。
此外,緩存在系統(tǒng)啟動(dòng)時(shí)通常是空的。這使得查詢變得更加昂貴,因?yàn)樗鼈儽仨毴ピ嫉胤侥脭?shù)據(jù)。因此,崩潰的風(fēng)險(xiǎn)高于系統(tǒng)在穩(wěn)定模式下運(yùn)行時(shí)的風(fēng)險(xiǎn),因此請(qǐng)確保保持緩存可用。
除了這六個(gè)反模式之外,還有其他系統(tǒng)組件或參數(shù)需要檢查。
例如,可以查看請(qǐng)求或 RPC 調(diào)用的截止日期(deadline)。一般來說,很難設(shè)定好的截止日期。但是在級(jí)聯(lián)故障的情況下,經(jīng)常遇到的一個(gè)常見問題是客戶端超過了許多設(shè)定的截止日期,這意味著資源的大量浪費(fèi)。
AWS DynamoDB 示例從一開始也是這種情況。通常情況下服務(wù)器應(yīng)該檢查請(qǐng)求離截止日期是否還有時(shí)間剩余,從而可以避免工作的浪費(fèi)。一種常見的策略是所謂的期限傳播。也就是請(qǐng)求樹的頂部有一個(gè)絕對(duì)的截止日期。再往下的服務(wù)器只得到前一個(gè)服務(wù)器完成計(jì)算后剩下的時(shí)間值。例如,服務(wù)器 A 的期限為 20 秒,計(jì)算需要 5 秒,那么服務(wù)器 B 的期限為 15 秒,依此類推。
6. 結(jié)論
級(jí)聯(lián)故障是分布式系統(tǒng)中一種即可怕又特殊的現(xiàn)象。這是因?yàn)橛袝r(shí)必須采取違反直覺的路徑來避免它們,例如實(shí)際上旨在減少錯(cuò)誤的定制化工作,比如看似智能的負(fù)載平衡,可能會(huì)增加完全失敗的風(fēng)險(xiǎn)。
有時(shí),最好的策略就是向客戶顯示一條錯(cuò)誤消息,而不是實(shí)施復(fù)雜的重試邏輯并冒著 DDoS 攻擊系統(tǒng)的風(fēng)險(xiǎn)。但是,有時(shí)候又不得不做出妥協(xié)。測(cè)試、容量規(guī)劃和在系統(tǒng)設(shè)計(jì)中應(yīng)用某些模式有助于提高系統(tǒng)的彈性。
畢竟,大型科技公司的經(jīng)驗(yàn)教訓(xùn)和事后分析為進(jìn)一步采取行動(dòng)以避免未來出現(xiàn)級(jí)聯(lián)故障提供了很好的指導(dǎo)。但是,最新技術(shù)和趨勢(shì)也值得關(guān)注。