談到高併發和高可用往往引起很多人的興趣,有時候成爲框架選擇的噱頭。實際上,它們往往和框架關係不大,而是跟架構息息相關。在很多時候,老碼農會直面一個問題:
“系統的服務可用性是多少?是怎麼得來?”
但在思考這個問題之前,先要澄清一個概念,那就是——
什麼是服務可用性
可用性就是一個系統處在可工作狀態的時間的比例,這通常被描述爲任務可行率。數學上來講,相當於1減去不可用性。——wiki 百科
相應的,我們的軟件系統處於可工作的時間比例,就是服務的可用性,也就是說,服務可用性可以描述爲一個百分比的數值。我們經常用這個SLO(service-level objective ,服務級目標)來代表服務可用性,至於SLO,SLA,SLI 等概念之間的差異,這裏暫不做深入討論。
SLO用數字來定義可用性對於特定服務的意義,來表示服務幾乎總是活着,總是處於可以快速運行的狀態。制定SLO是根據如下:
絕大多數軟件服務和系統的目標應該是近乎完美的可用性,而不是完美的可用性。服務可用性一般是99.999% 或99.99% ,而不是100%,因爲用戶無法區分服務是100% 可用和不“完美”可用之間的區別。在用戶和服務之間還有許多其他的系統,例如筆記本電腦、家庭 WiFi、互聯網等等 ,這些系統的可用性遠遠低於100% 。因此,99.99% 和100% 之間的邊際差異在其他不可用性的噪音中丟失了,並且,即使爲增加最後一部分可用性付出了巨大努力,用戶也可能沒有從中獲得任何好處。
很多雲服務的目標是向用戶提供99.99% 的可用性(就是我們常說的“四個9”)。一些服務在外部承諾較低的數字,但在內部可能設定了99.99% 的目標。作爲SLA,這個嚴格的目標描述了用戶在違反合同之前對服務性能不滿意的情況,因爲軟件服務的首要目標是讓用戶滿意。對於許多服務,99.99% 的內部目標代表了平衡成本、複雜性和可用性的最佳位置。
服務可用性解讀
服務可用性是中斷頻率和持續時間的函數。它是通過以下方式衡量的: * 停機頻率,或者是它的倒數: MTTF (平均停機時間)。* 持續時間,使用 MTTR (平均修復時間)。持續時間根據用戶的經歷定義: 從故障開始持續到正常行爲恢復。
因此,可用性在數學上定義爲使用適當單位的 MTTF / (MTTF + MTTR)。
四個9的服務可用性可能是很多軟件系統的目標,如何達到這一目標呢?需要先明確一下導致服務不可用的來源。服務不可用有兩個主要來源: 服務本身的問題和服務的關鍵依賴的問題。關鍵依賴是指如果出現故障,就會導致服務相應故障的依賴項。
關鍵依賴
服務的可用性不能超過其所有關鍵依賴關係的交集。如果服務的目標是提供99.99% 的可用性,那麼所有的關鍵依賴項必須遠遠超過99.99% 的可用性。據說在谷歌內部,使用這樣一個經驗法則: 因爲任何服務都有幾個關鍵依賴項,以及自身的特殊問題,關鍵依賴必須提供一個與服務相關的額外9% 的可用性(這裏爲99.999%) 。
如果有一個關鍵依賴,一個相對常見的挑戰是沒有提供足夠的可用性,就必須採取措施來增加依賴項的有效可用性(例如,通過緩存、限流、優雅降級等等)。
降低期望
從數學上看,服務的可用性不能超過其事件頻率乘以其檢測和恢復時間。例如,每年有4次完全宕機,每次持續15分鐘,結果總共是60分鐘。即使該服務在這一年中剩下的時間裏都運行良好,99.99% 的可用性(每年停機時間不超過53分鐘)也是沒有達的。
如果服務被依賴於無法提供相應水平的可用性級別,那麼就應該努力糾正這種情況,可以通過增加自身服務的可用性等級,或者如前所述的增加緩解措施。降低期望值(即公佈的可用性)也是一種選擇,而且往往是正確的選擇: 向相關服務明確表示,它應該重新設計系統以彌補我們服務可用性,或者降低自己的目標。如果不糾正或解決這種差異,可用性將無法達到要求。
服務可用性的計算
考慮一個目標可用性爲99.99% 的示例服務,並處理依賴項和停機響應的需求。
一個例子
假設這個99.99% 的可用服務具有以下特徵:
-
每年一次大停機和三次小停機。這些數字聽起來很高,但是99.99% 的可用性目標確實意味着每年有20到30分鐘的大範圍停機和幾次短暫的部分停機。這裏的假設是: 單個分片的失敗並不被認爲是整個系統的失敗,總體可用性是根據分片可用性的加權和來計算的。
-
有五個獨立關鍵依賴, 服務可用性爲99.999%。
-
這五個相互獨立的碎片,不能相互轉移。
-
所有變更逐個進行,每次一個分片。
那麼,本年度服務中斷的總預算爲每年525,600分鐘的的0.01%或53分鐘(以每年365天爲基礎)。分配給關鍵依賴的服務中斷預算是5個525,600分鐘的0.001%,即525,600分鐘的0.005% 或26分鐘。考慮到關鍵依賴的服務中斷,該服務的中斷時間預算爲53-26=27分鐘。
進一步,預計停機次數爲4次(1次完全停機,3次停機僅影響一個分片), 預期服務中斷的總影響: (1 x 100%) + (3 x 20%)= 1.6。那麼,可用於檢測和從中斷恢復的時間爲27 / 1.6 = 17分鐘。如果監控和告警的時間是2分鐘,值班人員調查警報的時間爲5分鐘的話,則有效解決問題的剩餘時間是 10分鐘。
提高可用性的方向
仔細研究這些數字,有三個主要的因素可以使服務更可靠。
-
透過流程、測試、設計review等手段,減少宕機的次數。
-
通過分片、地理隔離、優雅降級或客戶隔離,縮小停機範圍。
-
縮短恢復時間ーー透過監控、一鍵式回滾等。
可以在這三個方向之間進行權衡,以便實現更加容易。例如,如果17分鐘的 MTTR 很難實現,那麼應該將精力集中在減少平均停用的範圍上。
服務可用性之依賴嵌套
一個不經意的推斷,依賴鏈中的每個額外鏈接都需要增加額外的可用性等級麼?例如,二級依賴需要兩個額外的9,三級依賴需要三個額外的9,以此類推。
這種推論是不正確的,它基於依賴關係層次結構,即在每個級別上具有常量扇出的樹 具有許多獨立關鍵依賴的高可用性服務系統顯然是不現實的。
無論在依賴項樹中出現在哪裏,關鍵依賴項本身都可能導致整個服務(或服務分片)失敗。因此,如果給定的組件A表現爲幾個服務的依賴項,那麼 A應該只計算一次,因爲無論有多少中間的服務受到影響,A的故障終將導致服務的故障。
正確的依賴計算可能是這樣的:
-
如果一個服務具有N個唯一的關鍵依賴項,那麼每個依賴對服務導致的不可用性貢獻1 / N,而不管它在層次結構中的深度如何。
-
即使它在依賴項層次結構中出現多次,每個依賴也只計算一次。
例如,假設服務b 的故障預算爲0.01% 。服務擁有者願意花一半的預算在他們自己的 bug 和損失上,另一半花在關鍵依賴上。如果服務有 N個這樣的依賴項,每個依賴項接收剩餘故障預算的1 / N。典型的服務通常有5到10個關鍵依賴項,因此每個服務的失敗率只有服務b 的十分之一或二十分之一。因此,根據一般經驗,服務的關鍵依賴項必須增加額外的可用性。
服務可用性之故障預算
一般地,使用故障預算來平衡可用性和創新速度。這個預算定義了在一段時間內(通常是一個月)服務可接受的故障水平。故障預算只是1減去服務的 SLO,因此,99.99% 可用的服務是故障爲0.01% 的“預算”。只要服務沒有花費當月的故障預算,開發團隊就可以在合理範圍內自由地發佈新特性、更新等等。
如果使用了故障預算,除了緊急安全修復和解決最初導致違規的更改之外,服務可能將凍結變更。直到服務在預算中贏得了空間,或者時間重置。使用 SLOs 滑動窗口,因此故障預算逐漸增加。對於 SLO 大於99.99% 的成熟服務,每季度重置預算是適當的,因爲允許的停機時間很小。
故障預算提供了一個共同的、數據驅動的機制來評估發佈風險,從而消除了可能在運維團隊和產品開發團隊之間產生的結構性緊張。故障預算還提供了一個共同的目標,在不“超出預算”的情況下實現更快的創新和更多的發佈。
提高服務可用性:減少關鍵依賴
現在,可以重點討論服務的依賴關係,如何進行設計以減少並最小化關鍵依賴。
關於關鍵依賴
一般的,任何關鍵部件的可用性必須是整個系統目標的10倍。因此,在一個理想的世界中,目標是使盡可能多的組件成爲非依賴的。這樣做意味着組件可以堅持較低的可靠性標準,獲得創新和承擔風險的自由。
減少關鍵依賴性的最基本和最明顯的策略是儘可能消除 SPOFs (單點故障)。較大的系統應該能夠在沒有任何非關鍵依賴項或 SPOF 的給定組件的情況下可以可接受地運行。
實際上,您可能無法擺脫所有關鍵的依賴關係,但是您可以遵循一些圍繞系統設計的最佳實踐來優化可靠性。雖然這樣做並不總是可行的,但是如果你在設計和規劃階段計劃可靠性,而不是在系統運行並影響實際用戶之後,那麼實現系統可靠性就會更容易和更有效。
當考慮一個新的系統或服務時,當重構或改進一個現有系統或服務時,一個架構/設計評審可以識別出內部與外部的依賴。如果服務使用的是共享基礎設施(例如,多個用戶可見產品使用的基礎數據庫服務) ,要考慮該基礎設施是否得到正確使用。明確地將共享基礎結構的所有者確定爲附加的利益相關者。另外,要注意不要讓依賴關係超載,小心地與這些依賴關係的所有者協調工作。
有時,產品或服務取決於公司無法控制的因素,例如,代碼庫、第三方提供的服務或數據,要識別這些因素可以減少它們帶來的不可預測性。
冗餘和隔離
通過將依賴設計爲具有多個獨立實例來減輕對關鍵依賴的依賴。例如,如果在一個實例中存儲的數據提供了該數據99.9% 的可用性,那麼在三個分佈的實例中存儲三個副本提供了9個9的理論可用性級別。
在現實世界中,相關性永遠不會爲零,因此實際可用性不會接近9個9,而是遠遠高於3個9。如果一個系統或服務是“廣泛分佈的” ,地理上的分離並不總是不相關的。在鄰近地點使用多個系統,可能比在較遠地點使用同一個系統更好。
類似地,向一個集羣中的一個服務器池發送 RPC可以提供99.9% 的可用性,但是向三個不同的服務器池發送三個併發 RPC 並接受到達的第一個響應,這樣有助於將可用性提高到遠遠超過三個9的級別。如果服務器池與 RPC 發送方的距離大致相等,那麼這種策略還可以減少延遲。
故障切換與回滾
一個的基本經驗是,當必須人工在線引發故障切換時,可能已經超出了故障預算。最好進行故障的安全切換,如果出現問題,這些軟件可以自動隔離。在無法實現的情況下,可以執行自動腳本。同樣,如果問題依賴於某一個人來檢查,那麼滿足SLO 的機會會很小。
將人引入緩解計劃大大增加了 SLO 的風險,需要構建方便、快速而可靠回滾的系統。隨着系統逐漸成熟,並且對檢測問題的監視獲得了信心,就可以通過設計系統自動觸發安全回滾來降低 MTTR。
在可能的情況下,將依賴項設計爲異步的,而不是同步的,這樣它們就不會意外地變得非常重要。如果服務等待來自其非關鍵依賴項之一的 RPC 響應,並且該依賴項的延遲會大大增加,那麼這種延遲將不必要地影響父服務的延遲。通過將 RPC 調用設置爲非關鍵的異步依賴項,可以將父服務的延遲與依賴項的延遲解耦。雖然異步性可能會使代碼和基礎結構複雜化,但這種權衡可能是值得的。
檢查所有可能的失效模式
檢查每個組件和依賴項,並確定其故障的影響。以下問題可能是一些方向:
-
如果其中一個依賴項失敗,服務能否繼續以降級模式提供服務?換句話說,爲優雅的降級而設計。
-
如何處理在不同情況下依賴項不可用的問題?在服務啓動時?在運行期間?
設計和實現一個健壯的測試環境,確保每個依賴項都有自己的測試覆蓋率,並且使用專門針對環境的用例進行測試。以下是一些推薦的測試策略:
-
使用集成測試執行故障注入ーー驗證系統能否在任何依賴關係發生故障時倖存下來。
-
進行災難測試以識別弱點或隱藏的依賴關係。記錄後續行動,以糾正發現的bug。
-
故意讓系統過載,看看它是如何退化的。無論如何,系統對負載的響應都將被測試; 最好是自己執行這些測試,而不是將負載測試留給用戶。
容量規劃
確保每個依賴項都得到了正確的供給,如果成本可以接受,就過度供給。如果可能,將依賴項的配置標準化,以限制子系統之間的不一致性,並避免一次性的故障模式。
檢測、故障排除和診斷問題要儘可能簡單,有效的監測是能夠及時發現問題的關鍵組成部分。診斷具有嚴重依賴關係的系統是困難的,但總是有一個不需要操作員就可以減輕故障的方案。
期待隨着規模而來的變化,當在一臺機器上以二進制文件開始的服務在更大的規模上部署時,可能會有許多明顯或不明顯的依賴關係。每一個規模的數量級都會暴露出新的瓶頸, 不僅僅是自己服務,還有所依賴的服務。考慮一下,如果依賴項不能像所需要的那樣快速擴展,將會發生什麼。
還要注意,系統依賴關係會隨着時間的推移而發展,並且依賴關係的列表可能會隨着時間的推移而增長。在基礎設施方面,一個典型的設計是建立一個不需要重大變更就可以擴展到初始目標負載10倍的系統。
結束語
服務的用性並不高深莫測,它只是一個百分比的數字。服務可用性的指標(例如99.99%)往往令人不安,但並非不可實現。提供超過四個9的服務可用性,不是通過超出常人的智慧,而是通過不斷地完善規則形成最佳實踐,並且全面應用。
關聯閱讀
-
Beyer B, Jones C, Petoff J, et al. Site Reliability Engineering: How Google Runs Production Systems[J]. 2016