這篇文章的初衷,是記錄拜讀由Nathan Bronson, Aleksey Charapko, Abutalib Aghayev, and Timothy Zhu共同發表的論文Metastable Failures in Distributed Systems的收穫,這篇論文描述了一個在大規模分佈式系統中很常見的失敗場景:亞穩定失敗(metastable failures),它們為什麼通常在高負載分佈式系統中發生,以及解決問題的思路框架:如何識別和從亞穩定失敗中恢復,甚至如何避免發生亞穩定失敗。
現實世界中的亞穩定失敗
下圖是某個公園中非常著名的徒步路線中非常關鍵的一部分:兩座山之間一段狹長的山脊,在兩座山之間徒步,只有扶着鐵鏈穿過這段山樑才能保證安全。可以假想這段鐵鏈就是一個分佈式系統。
當公園不對徒步者人數做限制時,就有可能引起人羣擁擠以至於長時間在起點等待去嘗試減低負載。你可以想象一下”系統“經歷了以下的狀態轉換。
- 穩定狀態(Stable state)
當人羣低於某個安全閾值時,任何一個風險因素(例如:緩慢通過鐵鏈的徒步者)都可能引起降速,但是系統仍然能夠自愈。 - 脆弱狀態(Vulnerable state)
擁擠人羣的數量持續增加超過了某個閾值,對於每段鐵鏈的競爭也會增加 —— 下山的人必須要等待上山的人通過,或者乾脆冒險不使用鐵鏈而繞過他們。同樣,上山的人也必須等待下山的人通過。當這種模式發生時,只要擁擠人羣的數量低於某個閾值,系統仍然是可以工作的,但它現在是非常脆弱的,可能變成不可自愈的失敗狀態。 - 亞穩定失敗狀態(Metastable Failure State)
當系統處於脆弱狀態,某些風險因素可能導致情況惡化。想象一下,一組徒步者需要花費更長的時間通過鐵鏈,這將會使其他上山和下山的人速度降下來。越來越多的人等待,導致在鐵鏈兩端和身在其中的剩餘空間越來越狹窄,進而導致人們需要更多的時間通過,進一步導致更多的人等待...情況循環惡化 —— 進入亞穩定失敗狀態。
保持系統處於亞穩定失敗狀態的正反饋循環
由於這個自續的反饋循環,系統將保持在這個狀態,即使移除最初的誘發風險因素。為了恢復,需要採取其他的措施,例如將負載降低到特定閾值以下。
亞穩定失敗定義
關於亞穩定失敗的定義論文中描述如下
亞穩定失敗發生在對於負載來源沒有控制的開放系統中,一個風險因素導致系統進入一個糟糕的狀態,並且會持續存在甚至風險因素被移除。在這個狀態系統的效率通常會很低,並且會產生持續影響——通常使工作負載放大或者整體效率降低——阻止系統從這個糟糕的狀態中恢復。
Metastable failures occur in open systems with an uncontrolled source of load where a trigger causes the system to enter a bad state that persists even when the trigger is removed. In this state the goodput (i.e., throughput of useful work) is unusably low, and there is a sustaining effect — often involving work amplification or decreased overall efficiency — that prevents the system from leaving the bad state.
如果這樣,那麼為什麼不總是運行在穩定狀態?誠如,在許多系統中高效的資源利用是非常重要的,所以許多系統選擇運行在脆弱狀態而不是穩定狀態,已獲得更高的效率。
當系統處於亞穩定失敗狀態,系統不會自愈,想要恢復需要採取重要措施,例如降低負載。
問題的癥結:正反饋循環
正反饋循環是亞穩定失敗發生的根源。例如,在維基百科中給出了這樣例子:
A生產了更多的B,B反過來又生產了更多的A
又例如,人羣狂奔:假設美國紐約街道上正在進行獨立日遊行,突然有人開始奔跑(A)導致周圍人羣開始恐慌(B),更多的人開始奔跑(A)更多的人開始恐慌(B)更多人開始奔跑。。。最終導致人羣狂奔(也許現在還沒有槍響)。
可能有很多不同風險因素能夠將系統帶到亞穩定失敗狀態。但是,能夠將系統保持在這種狀態甚至在移除該風險因素之後仍然不能發生變化的,是自我維持的反饋循環。
亞穩定失敗的例子
- 重試導致工作負載放大
假設我們有一個web服務端承接客户端請求,持久層數據庫服務的性能為300 QPS和100毫秒的延遲,服務端有一個原生的重試機制:每次請求失敗都會立即重試。
只要服務端入流量的請求負載低於150QPS,即使發生重試,當一個風險因素(例如短暫的網絡故障)發生並導致請求積壓它依然可以自愈。這就是“健康”狀態。
然而,當負載超過150QPS,系統將進入“脆弱”狀態——假設負載為280QPS,當處於這種狀態時系統依然還是可以工作的。現在,如果在服務端和數據庫直接有10S的網絡斷開,數據庫將面對流量的陡增(原始流量+重試),這將增加請求延遲進而導致更多的重試,進一步增加請求延。導致一個自續的循環。
- 為happy path所做的優化工作
任何為happy path優化而做出的努力都更可能進入亞穩定失敗狀態。假設上邊同樣的系統但是有一個緩存服務以降低數據庫服務的調用次數。如果緩存命中率是90%,那麼服務端在脆弱狀態下能夠處理3000QPS(服務端處理300QPS)的負載。但是如果緩存服務由於某些原因需要重啓,那麼我們將面臨一個問題:它將是完美進入到亞穩定失敗狀態的誘因。論文中將3000QPS稱為表面容量(advertised capacity)(系統將處於脆弱狀態的上限),而300QPS是隱含容量(hidden capacity)(系統能夠自愈的上限)。
以上展示的工作負載放大在許多大規模請求中斷時是非常普遍的。但是研究的案例我發現最有感興趣的是“連接不平衡”,由於使用MRU策略,兩個系統之間的連接中最慢的連接總是被選擇。
如何處理已知的亞穩定失敗
在論文中描述了一些預防已知亞穩定失敗的技術:
- 專注於風險因素的持續反饋循環
去定位一個亞穩定失敗模式,專注於弱化正反饋循環,這涉及到工作負載放大,而不是針對風險因素打地鼠。 - 識別最強反饋循環
理解哪裏是產生工作負載放大最大的實例,然後為它定義一個最大值。大體上,識別最強反饋循環並且不要追逐每一個反饋循環。
如何識別他們呢?壓力測試是一個方式。但是挑戰是越大規模的服務,反饋循環越強,所以第一次在大規模服務中會發現很多問題。
因此,你能做的事情是識別被風險因素影響的特徵指標:例如,上述實例中的重試率。通過這樣,你可以理解穩定、脆弱和亞穩定失敗狀態的邊界。 - 弱化反饋循環
論文描述了一些弱化反饋循環的方法。例如包括削弱負載和熔斷機制。主要的挑戰是這將由客户端來決策何時重試和故障轉移,但是客户端通常沒有全局信息(並且試圖去提供這種服務本身也可能導致失敗)。區分短暫的負載峯值和持續性的過載是非常重要的,你可能需要有指標幫助你去準確的識別這兩者。
論文為組織激勵提供了一個很好的示例,獎勵正確的優化措施(例如優先降低資源使用率)而不是在更有可能導致亞穩定失敗的happy path上的優化。
總結
希望通過以上的討論能有所收益,希望下次在發現”一些列的請求失敗“、”死亡螺旋“、”徹底失敗“等等,你可以使用這些作為一個思考框架或者通用語言去理解他。論文中還有許多其他的示例和思考,感興趣可以閲讀一下論文原文。
原文地址:Metastable Failures in Distributed Systems: What Causes Them and 3 Things You can do to Tame Them
歡迎訪問博客雲原生.鹽酒員