微服務,助力BATJ避免大規模服務崩塌的產生

目錄

  • 優雅的服務降級 Graceful Service Degradation
  • 變更管理 Change management
  • 自愈 Self-healing
  • 故障切換緩存 Failover Caching
  • 重試機制 Retry Logic
  • 限流與降級 Rate Limiters and Load Shedders
  • 快速且獨立地失敗 Fail Fast and Independently
  • 艙壁 Bulkheads
  • 斷路器 Circuit Breakers

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

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

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

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

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

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

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

優雅的服務降級 Graceful Service Degradation

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

微服務,助力BATJ避免大規模服務崩塌的產生

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

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

變更管理 Change management

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

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

變更管理 - 滾動發佈

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

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

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

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

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

自愈 Self-healing

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

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

故障切換緩存 Failover Caching

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

故障切換緩存

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

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

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

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

重試機制 Retry Logic

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

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

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

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

限流與降級 Rate Limiters and Load Shedders

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

限速器可以抑制流量高峰

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

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

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

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

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

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

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

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

艙壁 Bulkheads

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

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

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

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

斷路器 Circuit Breakers

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

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

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

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

微服務,助力BATJ避免大規模服務崩塌的產生

*測試失敗 Testing for Failures

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

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

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


分享到:


相關文章: