微服務架構如何避免大規模故障?

語言: CN / TW / HK

微服務架構通過一種良好的服務邊界劃分,能夠有效地進行故障隔離。但就像其他分散式系統一樣,在網路、硬體或者應用級別上容易出現問題的機率會更高。服務的依賴關係,導致在任何元件暫時不可用的情況下,就它們的消費者而言都是可以接受的。為了能夠降低部分服務中斷所帶來的影響,我們需要構建一個容錯服務,來優雅地應對特定型別的服務中斷。

本文基於一些在RisingStack的顧問諮詢與開發經驗,介紹瞭如何運用一些最常用的技術和架構模型,去構建與維護一個高可用的微服務系統。

如果你不熟悉本文中的模式,並不意味著你做錯了什麼。畢竟構建一個高可用的系統需要很多額外的付出。

*微服務架構的風險 The Risk of the Microservices Architecture

微服務的架構將應用的邏輯移動到一個服務裡面,服務之間通過網路層進行通訊互動。通過網路通訊互動的方式取代了記憶體的呼叫,同時需要多個物理和邏輯元件之間的相互協作,給系統帶來了額外的延遲性與複雜性。分散式系統複雜性的增加,導致了特定網路故障的可能性變得更大。

微服務允許你實現優雅的服務降級,因為元件可以被單獨的設定為失敗。

團隊可以獨立地設計、開發與部署他們的服務,是微服務的最大優點之一。他們完全擁有整個服務的生命週期,這也意味著團隊無法控制他們的服務依賴,因為這些服務更有可能是不同的團隊在管理。我們需要記住,提供者的服務由於釋出中斷、配置等等其他的改變而暫時不可用,他們是由別人控制,並且元件之間獨立活動。

*優雅的服務降級 Graceful Service Degradation

微服務最佳優勢之一,當某個元件單獨失敗時,你可以實現優雅的服務降級,進行故障隔離。例如,一個照片共享的應用,由於中斷,使用者可能無法上傳新的照片,但他們仍然可以瀏覽、編輯和分享他們現有的照片。

微服務的獨立失敗(理論上)

在大多數情況下,在一個分散式系統中,應用程式之間互相依賴,實現一種優雅的服務降級,這是很困難的,你需要採取多種故障切換邏輯(其中一些會在本文後面進行討論),應對臨時的故障與中斷。

服務之間彼此依賴,在沒有故障切換邏輯的情況下,一起失敗。

*變更管理 Change management

谷歌網站的可靠性團隊(SRE)發現,大約70%的中斷是由一個實時系統的改變而引起。當你在服務中更改某些內容時——你部署了新版本的程式碼或更改了一些配置——總會導致更高的失敗機率或者引入一個新的bug。

在微服務架構中,服務之間彼此依賴。這就是為什麼你應該儘量減少失敗,並限制它們的負面影響。如果要處理來自變更的問題,你可以使用變更管理策略和自動升級。

例如,當需要部署新程式碼或者更改某些配置時,你應該逐漸地將這些更改應用於例項的子集,監控它們,甚至當你看到關鍵指標有負面影響時,它們會自動回滾恢復。

變更管理 - 滾動釋出

另一個解決方案,就是執行兩個生產環境。只部署其中一個,並且在驗證新版本的執行符合預期之後,才會將負載均衡指向新版本。這被稱為藍綠色部署,或紅黑色部署。

恢復程式碼不是一件壞事情。你不應該把壞的程式碼留在生產中,然後再思考哪裡出了問題。必要的時候,總是要恢復你的改變(回滾),越快越好。

*健康檢查與負載均衡 Health-check and Load Balancing

例項會因為失敗、部署或自動伸縮,而不斷地啟動、重新啟動和停止。這會導致服務暫時或永久不可用。為了避免問題,你的負載均衡應該跳過不健康的例項,因為它們不能滿足你的使用者或子系統的需要。

應用例項健康可以通過外部觀察來決策。你可以反覆呼叫 GET /health 請求埋點或自身報告。現代服務發現解決方案,將不斷從例項中收集健康資訊,並配置負載均衡以保證健康的元件路由流量。

*自愈 Self-healing

自我修復可以幫助恢復應用程式。我們談論的自愈,是指應用程式可以做一些必要的步驟來恢復崩潰狀態。在大多數情況下,這樣的操作是經由一個外部系統來實現的,它會監控例項的健康,並在它們較長時間處於錯誤狀態的情況下,重新啟動應用程式。自愈是非常有用的,但是在某些情況下,不斷地重啟應用程式會引起麻煩。由於負載過高或者資料庫連線超時,你的應用程式不停的重啟,會導致無法提供一個正確的健康狀態。

實現一種為微妙的情況而準備的高階自我修復解決方案,可能會很棘手,比如資料庫連線丟失。在這種情況下,你需要為應用程式新增額外的邏輯來處理一些極端情況,並讓外部系統知道不需要立即重啟例項。

*故障切換快取 Failover Caching

服務通常會因為網路問題和系統的變更而失敗。由於自愈和先進的負載均衡,大多數中斷只是暫時的,然而我們還應該找到一個解決方案,讓我們的服務在這些故障中能夠正常工作。這就是故障切換快取,它可以幫助應用程式提供一些必要的資料。

故障切換快取一般使用兩個不同的過期時間。設定一個較短的時間,顯示在正常情況下可以使用多長時間的快取;設定另一個較長的時間,顯示在發生故障期間,可供使用快取資料的時間會有多久。

故障切換快取

很重要的一點是,只有當過時的資料比什麼都不做要好的情況出現時,才可執行故障切換緩。

可以通過使用HTTP中的標準響應頭(response header)來設定快取和故障轉移快取。

例如,通過設定 header 引數 max-age 來指定一個資源被重新整理時最大時間;也可以通過設定 header 引數 stale-if-error 來決定,在服務失敗的情況下,需要多長時間從快取獲取資料。

現代的CDN和負載均衡器提供了各種快取和故障切換的方式,你也可以為公司建立一個包含了統一的可靠性解決方案的共享標準庫。

*重試機制 Retry Logic

在某些特定的場景下,我們可能無法快取資料,或者我們想對其做出一些更改,但是我們的操作最終還是會失敗。在這些情況下,我們可以重新嘗試我們的操作,因為我們可以預計資源在一段時間後會恢復,或者我們的負載均衡將我們的請求轉發到一個健康的例項。

在應用程式和客戶端新增重試邏輯需保持謹慎,因為大量的重試會讓事情變得更糟,甚至會阻止應用程式的恢復。

在分散式系統中,微服務系統重試會觸發多個其他的請求或重試,引起一個級聯效應。為了儘量減少重試帶來的影響,你應該最大限度限制它們的發生次數,並使用指數補償演算法來持續增加重試之間的延遲。

重試由客戶端(瀏覽器,其他微服務等)發起,客戶端不知道這個操作是在處理請求之前失敗還是之後失敗的,你應該準備好應用程式來處理冪等性(idempotency)。例如,當操作重試購買時,不應該對使用者進行重複扣費。對於每個事務,使用唯一的 冪等令牌(idempotency-key ),可以幫助處理重試。

*限流與降級 Rate Limiters and Load Shedders

限流是指在一個時間段內,特定的使用者或應用程式可以接收或處理多少請求的技術。例如,有了限流,你就可以找出引起流量高峰的使用者和微服務,或者可以確保應用不會在負載過高情況下,發生自動擴容都不能拯救。

你還可以限制業務優先順序較低的流量,以便為核心業務提供足夠的資源。

限速器可以抑制流量高峰

另外一種型別的限速器稱為併發請求限制。當有一些昂貴的端點不應該超過指定的呼叫次數,但你仍然希望提供流量服務時,選擇這樣的操作是很有用的。

快速降級可以確保總是有足夠的可用資源去服務關鍵的事務。它為高優先順序請求保留一些資源,並且不允許低優先順序事務使用所有的資源。降級與否是根據系統的整個狀態進行判斷的,而不是基於單個使用者的請求桶大小。服務降級用於幫助恢復系統,當發生一些事故時,它們可以保證核心功能仍然繼續工作。

如需獲取更多有關限流與降級的資訊,推薦前往https://stripe.com/blog/rate-limiters,閱讀Stripe的文章。

*快速且獨立地失敗 Fail Fast and Independently

在微服務體系結構中,我們希望我們的服務能夠快速、獨立地失敗。為了在服務級別上隔離問題,我們可以採用艙壁模式(bulkhead pattern)。你稍後可以在這篇文章中讀到更多關於艙壁的資訊。

我們還希望我們的元件快速失敗,因為我們不想等待壞的例項超時。沒有什麼比一個掛著的請求和一個沒有響應的UI更令人失望的了。這樣不僅浪費資源,而且還會對使用者體驗造成影響。我們的服務是相互呼叫的,所以更應該額外注意,在這些延遲結束之前,阻止掛起操作。

第一個想到的想法是在每個服務呼叫上運用一個較好級別的超時時間。這種方法的問題在於,你不可能真正知道什麼是一個好的超時時間值,因為在某些情況下,網路故障和其他問題只會影響到一兩個操作。在這種情況下,如果只有少數幾個請求超時,你可能不想拒絕這些請求。

我們可以說,在微服務中使用超時來實現快速失敗的例子是一種反模式,你應該避免它。你可以依賴於操作成功/失敗統計資料的斷路器(circuit-breaker)模式,而不是超時。

*艙壁 Bulkheads

艙壁被用來將一艘船劃分成多個部分,這樣就可以在船體破裂的情況下對部分封閉。

隔離壁的概念可以應用於軟體開發中,做到資源隔離。

通過採用艙壁模式,我們可以保護有限的資源不被耗盡。例如,如果我們有兩種操作,它們與相同的資料庫例項互動,我們的連線數量有限,那麼我們可以使用兩個連線池,而不是共享連線池。由於此客戶端資源分離,當發生超時或者過度使用連線池的操作,不會導致所有其他操作的關閉。

泰坦尼克號沉沒的主要原因之一,就是它的艙壁有一個設計上的失敗,水可以通過艙壁頂部上的甲板注入,淹沒整個船體。

泰坦尼克的艙壁(他們沒有工作)

*斷路器 Circuit Breakers

為了限制操作的持續時間,我們可以使用超時。超時可以防止掛起操作並保持系統響應。然而,在微服務通訊中使用靜態的、微調的超時是一種反模式,因為我們處在一個高度動態的環境中,幾乎不可能發現正確的時間限制,以確保在每個場景下都能很好地工作。

我們可以使用熔斷來處理錯誤,而不是使用小的特定事務的靜態超時。斷路器是以真實世界電子元件命名的,因為它們的行為是相同的(簡單的說,這種模式主要是參考電路熔斷,如果一條線路電壓過高,保險絲會熔斷,防止火災)。你可以保護資源,幫助他們用斷路器恢復。它們在分散式系統中非常有用,因為重複的失敗會導致滾雪球效應(snowball effect),導致整個系統癱瘓。

當一個特定型別的錯誤在短時間內多次出現時,斷路器就會開啟。斷路器的開啟,阻止了進一步的資源請求——就像真的阻止了電流的流動。斷路器通常在一定時間後關閉,為基礎服務提供足夠的空間來恢復。

請記住,並非所有的錯誤都應該觸發斷路器。例如,你可能希望跳過客戶端問題,比如跳過 4xx 狀態碼響應的請求,但不包括 5xx 伺服器端錯誤的請求。一些斷路器也可以有半開狀態,在此狀態下,服務傳送第一個請求檢測系統的可用性,同時讓其他請求失敗。如果第一個請求成功,它將斷路器恢復到一個關閉狀態,並允許流量進入。否則,它就會開啟。

*測試失敗 Testing for Failures

你應該不斷地測試你的系統以防止常見問題,以確保你的服務能夠承受住各種失敗。你應該頻繁地測試失敗,讓你的團隊為發生事故而做好準備。

對於測試,你可以使用一個外部服務來標識例項組,並隨機終止該組中的一個例項。有了這個,你就可以為單個例項的失敗做準備,你甚至可以關閉整個可用區來模擬雲提供商的中斷。

最流行的測試解決方案之一是由Netflix提供的ChaosMonkey彈性工具。

*結尾

實現和執行可靠的服務並不容易。這需要你付出很大的努力,也要花費你的公司很多錢。

可靠性有很多的層次和方面,所以為你的團隊找到最好的解決方案是很重要的。你應該將可靠性作為業務決策過程中的一個因素,併為它分配足夠的預算和時間。

*主要收穫

動態環境和分散式系統——比如微服務——會導致更大的失敗機率。 服務應該單獨失敗,實現優雅的降級,用以改善使用者體驗。 70%的中斷是由變更引起的,恢復程式碼並不是件壞事。 快速和獨立的失敗。團隊無法控制他們服務的依賴。 架構模式和技術,如快取、艙壁、限流、熔斷,有助於建立可靠的微服務。

原文:https://blog.risingstack.com/designing-microservices-architecture-for-failure/<br> 譯文:https://blog.csdn.net/xushuai110/article/details/77726447

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2021最新版)

2.別在再滿屏的 if/ else 了,試試策略模式,真香!!

3.臥槽!Java 中的 xx ≠ null 是什麼新語法?

4.Spring Boot 2.5 重磅釋出,黑暗模式太炸了!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!