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