您的位置:首頁>正文

10招,提升你的微服務架構可用性

要 點

動態的環境和分散式的系統, 比如微服務, 它們出現故障的幾率更大。

發生故障的服務應該被隔離開來, 實現優雅的服務降級, 提升使用者體驗。

70% 的故障都是因為代碼變更引起的, 所以有時候回退代碼並不算是什麼壞事。

如果發生故障, 就要讓它們快速而獨立地發生。 一個團隊無法控制他們服務的依賴項。

緩存、隔板、回路斷路器和速率限定器這些架構模式有助於構建可靠的微服務。

手動分割線

微服務架構通過定義良好的邊界讓失效隔離成為可能, 但每一個分散式系統都存在同樣的問題——網路、硬體或應用程式層面都有可能出現故障。

因為服務之間存在依賴關係, 所以任何一個元件出現了問題都會影響到元件依賴者。 為了最小化局部故障所帶來的影響, 我們需要構建具有容錯能力的服務, 可以優雅地應對某些類型的故障。

這篇文章基於 RisingStack 的 Node.js 諮詢和開發經驗, 介紹構建高可用微服務系統的 常用技術 和 架構模式。

如果你不熟悉這篇文章所介紹的模式, 並不代表你現在所做的就是錯的, 畢竟構建一個可靠的系統需要付出額外的代價。

微服務架構的風險

微服務架構將業務邏輯分散到了各個微服務當中, 微服務間通過網路層進行通信。 網路通信帶來了額外的延遲和複雜性, 需要多個物理元件和邏輯元件共同協作。

分散式系統的額外複雜性增加了出現網路故障的幾率。

微服務架構相比單體架構最大的優勢之一在於, 不同的團隊可以獨立地設計、開發和部署他們的服務。 他們可以完全掌控自己的微服務生命週期。

當然, 這也意味著他們 無法控制服務依賴項, 因為依賴項的控制權掌握在其他團隊手中。 在採用微服務架構時, 我們要時刻銘記, 發佈、配置等方面的問題可能會導致服務提供者出現短暫的不可用。

優雅的服務降級

通過微服務架構可以實現失效隔離, 也就是說, 在元件發生故障時可以實現優雅的服務降級。 例如, 在圖片共用應用發生故障時, 使用者可能無法上傳新的圖片, 但他們仍然可以流覽、編輯和分享已有的圖片。

圖:理論上的微服務失效隔離

在大多數情況下, 實現這種優雅的服務降級是很困難的, 因為在分散式系統裡, 應用之間相互依賴, 為了應對臨時的故障, 需要應用到一些失效備援方案(稍後會提到)。

圖:相互依賴的服務, 在沒有失效備援方案的情況下就會全部失效。 變更管理

Google 的網站可靠性團隊發現, 70% 的故障都是由系統變更引起的。 更改服務、部署新代碼、變更配置, 這些都有可能引入新的缺陷或造成服務失效。

在微服務架構裡, 服務之間是相互依賴的。 所以我們要最小化出現故障的幾率,

限制故障所造成的負面影響。 我們需要良好的變更管理策略和自動回滾機制。

例如, 在部署新代碼時, 或者在對配置做出變更時, 要先在一小部分服務實例上進行, 然後監控它們, 一旦發現關鍵性度量指標出現異常, 馬上自動回滾。

圖:變更管理——回退部署

另一個解決方案就是運行兩套生產環境。在部署的時候只部署到其中一個生產環境,只有在確認這個環境沒問題了之後才能將負載等化器指向這個環境。這種部署方式被稱為 藍綠部署 或者 紅黑部署。

回退代碼並不是件壞事。你總不可能一邊把有問題的代碼留在生產環境裡,一邊想著到底發生了什麼問題。所以,在必要的時候回退代碼,越快越好。

健康監測和負載均衡

服務實例總是因為各種原因(故障、部署或自動伸縮)經歷著啟動、重啟、停止這樣的過程。這個過程會讓服務暫時或永久地不可用,為了避免出現問題,負載等化器需要忽略出現問題的服務實例,因為它們已經不具備為使用者或其他子系統提供服務的能力。

應用的健康狀態可以通過外部的觀察來獲得,比如通過不斷重複地調用 /health 端點來得知應用的狀態,或者讓應用報告自己的狀態。服務發現機制會持續地收集服務實例的健康資訊,負載等化器應該被配置成隻將流量路由給健康的服務實例。

自 愈

自愈能力能夠讓應用在發生故障時進行自我恢復。如果一個應用能夠通過一系列步驟從一個故障狀態中恢復,那麼就可以說它具備了自愈能力。

在大多數情況下,這是通過一個外部系統來實現的。這個系統監控服務實例的健康狀態,如果服務長時間處於不健康狀態,系統就會將它重啟。

在大多數時候自愈能力是很有用的,不過如果持續不斷地重啟應用也會造成一些問題。在應用超載或資料庫連接出現超時的時候通常會發生這種情況。

要實現高級的自愈方案會比較麻煩,比如在發生資料庫連接逾時的情況下,你需要在應用程式裡添加額外的邏輯,讓外部系統知道此時不需要重啟服務實例。

失效備援緩存

服務總會因各種原因發生失效,比如網路問題等。不過,大部分這樣的錯誤都是臨時性的,而系統的自愈能力和高級負載均衡特性可以讓應用實例在出現這些問題時仍然能夠提供服務能力。失效備援緩存在這個時候就可以派上用場,它可以為應用程式提供必要的資料。

失效備援緩存通常會使用兩個不同的過期時間,一個是短期時間,表示正常情況下的緩存過期時間,另一個是長期時間,表示在發生故障期間的緩存過期時間。

圖:失效備援緩存

不過需要注意的是,失效備援緩存的資料可能是已經過期的資料,所以要確保這對於你的應用程式來說是可接受的。

可以通過 HTTP 的標準回應頭來設置緩存或失效備援緩存。例如,通過 max-age 指定資源的最長過期時間,通過 stale-if-error 指定在發生故障時緩存的有效時間。

現代的 CDN 和負載等化器提供了各種各樣的緩存和失效備援機制,你也可以為自己的公司創建適合自己使用的緩存解決方案。

重試邏輯

在某些情況下,我們無法緩存資料,或者我們想要更新緩存內容但更新失敗。在這個時候,我們可以進行重試,因為我們認為相關資源稍後會重新恢復過來,或者負載等化器會將請求發送給正常的實例。

在應用程式裡添加重試邏輯的時候要十分小心,因為大量的重試操作會讓事情變得更糟糕,甚至導致應用程式無法從故障中恢復。

在分散式系統中,一個微服務系統可能會觸發多個請求或重試操作,從而發生級聯效應。為了降低重試帶來的影響,應該要限制重試的次數,可以使用指數退避(exponential backoff)演算法來逐步增加重試之間的延遲,直到達到重試的上限。

速率限定和負載倒注器(Shedder)

速率限定規定了應用程式在一個時間視窗內能夠接收或處理的請求個數。通過速率限定,你可以在流量高峰期過濾掉一些用戶請求或發出請求的微服務,確保你的應用程式不會出現超載。

你也可以因此限制低優先順序的流量,將更多的資源優先用在處理更關鍵的事務上。

圖:速率限定器限制流量高峰

另一種速率限定器叫作併發請求限定器(concurrent request limiter),它在某些情況下會很有用。比如,你不希望某些端點被多次調用,但同時又想為所有的流量提供服務。

使用負載倒注器可以確保總是存在足夠的資源來處理關鍵性的事務。它為高優先順序的請求保留了一些資源,這些資源不會被用在低優先順序的事務上。

負載倒注器基於整個系統的狀態來決定如何保留資源,而不是基於請求桶大小。負載倒注器有助於系統在發生故障時進行恢復,因為在發生故障的時候,它們仍能保證系統核心功能正常運行。Stripe 的文章裡詳細介紹了速率限定器和負載倒注器。

快速而獨立地失效

在微服務架構裡,如果服務發生失效,我們就要讓它們快速而獨立地發生。我們可以應用隔板(bulkhead)模式在服務層面對問題進行隔離。稍後會對隔板模式進行更多的介紹。

如果服務元件發生失效,那麼就要讓它儘快失效,因為我們不想浪費太多時間等它發生超時。沒有什麼比一個被掛起的請求和無回應的介面更讓人感到沮喪的了,它不僅浪費了資源,也給用戶體驗造成影響。

在服務生態系統裡,服務之間相互調用,我們要防止掛起的請求操作造成雪崩效應。

你可能首先會想到為每個服務調用定義二級超時時間,但問題是,你無法確切地知道多長的超時時間才是最合適的,因為有時候網路故障等問題只會影響到一兩個操作。很顯然,如果是這種情況,那麼就不應該因為少數的請求過時就拒絕其他請求。

可以說,在微服務架構裡通過使用超時來實現快速失效機制是一種反模式,所以應該儘量避免這麼做。相反,我們可以使用 回路斷路器(circuit breaker) 模式,它基於 成功操作和失敗操作的統計結果 來決定服務是否已經失效。

隔板

在造船業,隔板被用於將船隔成多個部分,如果船體發生洩漏,發生洩漏的部分就可以被單獨封閉。

隔板的概念也被應用在軟體發展裡,用於分隔資源。

通過應用隔板模式,我們可以防止有限的資源被耗盡。例如,如果我們有兩種針對資料庫的操作,我們可以使用連個連接池,而不是一個。這樣一來,如果有些操作超時或者過度使用了連接池,就不會對另一個連接池上的操作造成影響。

回路斷路器

為了限定操作的時長,我們可以為操作定義超時時間。超時機制可以防止出現長時間的掛起操作,保證系統可以正常回應。不過,在微服務架構裡使用固定的超時機制是一種反模式,因為我們的環境是 高度動態 的,所以幾乎不可能為每一種情況定義正確的限定時間。

我們可以使用回路斷路器,而不是使用固定的超時機制。回路斷路器的名字源於真實世界的電子元件,因為它們的行為極其相似。它們可以保護資源,有助於進行系統恢復。在分散式系統裡,它們能夠起到很大的作用,特別是當重複性的故障造成雪崩效應進而威脅到整個系統的時候。

如果某種類型的錯誤在一定時間段內多次發生,回路斷路器就會斷開。斷開的回路斷路器會阻止後續的請求,就像電子元件裡的斷路器一樣。回路斷路器會在一段時間之後關閉,給底層的服務更多的空間進行恢復。

要記住,並不是所有的錯誤都需要觸發回路斷路器。例如,你可能想要忽略用戶端的一些問題,比如那些包含 4xx 回應碼的請求,但同時想要保留 5xx 的伺服器端錯誤。

有些回路斷路器會出現半開閉狀態。這個時候,服務會發送一個請求來檢測系統的可用性,同時拒絕其他的請求。如果檢測系統可用性的請求成功返回,那麼就會關閉斷路器,繼續處理後續的請求。否則的話,它就保持打開狀態。

圖:回路斷路器做好故障測試

我們應該持續不斷地針對各種常見問題對我們的系統進行測試,確保我們的服務能夠應對各種故障。

例如,我們可以使用一個外部服務來識別一組服務實例,然後隨機地終止其中的一個實例。這樣就知道該如何應對單個實例故障,當然,也可以關閉整組服務來類比雲服務中斷。

Netflix 的 ChaosMonkey 是一個非常受歡迎的彈性測試工具。

總 結

實現和運行可靠的服務並不是一件簡單的事情。你需要付出很多的努力,你的公司也因此要付出很高的代價。

可靠性可以分為多種層次,也涉及到多個方面的內容,你要找到適合自己團隊的解決方案。你應該把可靠性作為業務決策的一個考量因素,並為它分配足夠的預算和時間。

Java工程化、高性能及分散式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大資料等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿裡面試但擔心面試不過的,你都可以來,群號為:647631030

注:加群要求

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿裡Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!

6.小號或者小白之類加群一律不給過,謝謝。

圖:變更管理——回退部署

另一個解決方案就是運行兩套生產環境。在部署的時候只部署到其中一個生產環境,只有在確認這個環境沒問題了之後才能將負載等化器指向這個環境。這種部署方式被稱為 藍綠部署 或者 紅黑部署。

回退代碼並不是件壞事。你總不可能一邊把有問題的代碼留在生產環境裡,一邊想著到底發生了什麼問題。所以,在必要的時候回退代碼,越快越好。

健康監測和負載均衡

服務實例總是因為各種原因(故障、部署或自動伸縮)經歷著啟動、重啟、停止這樣的過程。這個過程會讓服務暫時或永久地不可用,為了避免出現問題,負載等化器需要忽略出現問題的服務實例,因為它們已經不具備為使用者或其他子系統提供服務的能力。

應用的健康狀態可以通過外部的觀察來獲得,比如通過不斷重複地調用 /health 端點來得知應用的狀態,或者讓應用報告自己的狀態。服務發現機制會持續地收集服務實例的健康資訊,負載等化器應該被配置成隻將流量路由給健康的服務實例。

自 愈

自愈能力能夠讓應用在發生故障時進行自我恢復。如果一個應用能夠通過一系列步驟從一個故障狀態中恢復,那麼就可以說它具備了自愈能力。

在大多數情況下,這是通過一個外部系統來實現的。這個系統監控服務實例的健康狀態,如果服務長時間處於不健康狀態,系統就會將它重啟。

在大多數時候自愈能力是很有用的,不過如果持續不斷地重啟應用也會造成一些問題。在應用超載或資料庫連接出現超時的時候通常會發生這種情況。

要實現高級的自愈方案會比較麻煩,比如在發生資料庫連接逾時的情況下,你需要在應用程式裡添加額外的邏輯,讓外部系統知道此時不需要重啟服務實例。

失效備援緩存

服務總會因各種原因發生失效,比如網路問題等。不過,大部分這樣的錯誤都是臨時性的,而系統的自愈能力和高級負載均衡特性可以讓應用實例在出現這些問題時仍然能夠提供服務能力。失效備援緩存在這個時候就可以派上用場,它可以為應用程式提供必要的資料。

失效備援緩存通常會使用兩個不同的過期時間,一個是短期時間,表示正常情況下的緩存過期時間,另一個是長期時間,表示在發生故障期間的緩存過期時間。

圖:失效備援緩存

不過需要注意的是,失效備援緩存的資料可能是已經過期的資料,所以要確保這對於你的應用程式來說是可接受的。

可以通過 HTTP 的標準回應頭來設置緩存或失效備援緩存。例如,通過 max-age 指定資源的最長過期時間,通過 stale-if-error 指定在發生故障時緩存的有效時間。

現代的 CDN 和負載等化器提供了各種各樣的緩存和失效備援機制,你也可以為自己的公司創建適合自己使用的緩存解決方案。

重試邏輯

在某些情況下,我們無法緩存資料,或者我們想要更新緩存內容但更新失敗。在這個時候,我們可以進行重試,因為我們認為相關資源稍後會重新恢復過來,或者負載等化器會將請求發送給正常的實例。

在應用程式裡添加重試邏輯的時候要十分小心,因為大量的重試操作會讓事情變得更糟糕,甚至導致應用程式無法從故障中恢復。

在分散式系統中,一個微服務系統可能會觸發多個請求或重試操作,從而發生級聯效應。為了降低重試帶來的影響,應該要限制重試的次數,可以使用指數退避(exponential backoff)演算法來逐步增加重試之間的延遲,直到達到重試的上限。

速率限定和負載倒注器(Shedder)

速率限定規定了應用程式在一個時間視窗內能夠接收或處理的請求個數。通過速率限定,你可以在流量高峰期過濾掉一些用戶請求或發出請求的微服務,確保你的應用程式不會出現超載。

你也可以因此限制低優先順序的流量,將更多的資源優先用在處理更關鍵的事務上。

圖:速率限定器限制流量高峰

另一種速率限定器叫作併發請求限定器(concurrent request limiter),它在某些情況下會很有用。比如,你不希望某些端點被多次調用,但同時又想為所有的流量提供服務。

使用負載倒注器可以確保總是存在足夠的資源來處理關鍵性的事務。它為高優先順序的請求保留了一些資源,這些資源不會被用在低優先順序的事務上。

負載倒注器基於整個系統的狀態來決定如何保留資源,而不是基於請求桶大小。負載倒注器有助於系統在發生故障時進行恢復,因為在發生故障的時候,它們仍能保證系統核心功能正常運行。Stripe 的文章裡詳細介紹了速率限定器和負載倒注器。

快速而獨立地失效

在微服務架構裡,如果服務發生失效,我們就要讓它們快速而獨立地發生。我們可以應用隔板(bulkhead)模式在服務層面對問題進行隔離。稍後會對隔板模式進行更多的介紹。

如果服務元件發生失效,那麼就要讓它儘快失效,因為我們不想浪費太多時間等它發生超時。沒有什麼比一個被掛起的請求和無回應的介面更讓人感到沮喪的了,它不僅浪費了資源,也給用戶體驗造成影響。

在服務生態系統裡,服務之間相互調用,我們要防止掛起的請求操作造成雪崩效應。

你可能首先會想到為每個服務調用定義二級超時時間,但問題是,你無法確切地知道多長的超時時間才是最合適的,因為有時候網路故障等問題只會影響到一兩個操作。很顯然,如果是這種情況,那麼就不應該因為少數的請求過時就拒絕其他請求。

可以說,在微服務架構裡通過使用超時來實現快速失效機制是一種反模式,所以應該儘量避免這麼做。相反,我們可以使用 回路斷路器(circuit breaker) 模式,它基於 成功操作和失敗操作的統計結果 來決定服務是否已經失效。

隔板

在造船業,隔板被用於將船隔成多個部分,如果船體發生洩漏,發生洩漏的部分就可以被單獨封閉。

隔板的概念也被應用在軟體發展裡,用於分隔資源。

通過應用隔板模式,我們可以防止有限的資源被耗盡。例如,如果我們有兩種針對資料庫的操作,我們可以使用連個連接池,而不是一個。這樣一來,如果有些操作超時或者過度使用了連接池,就不會對另一個連接池上的操作造成影響。

回路斷路器

為了限定操作的時長,我們可以為操作定義超時時間。超時機制可以防止出現長時間的掛起操作,保證系統可以正常回應。不過,在微服務架構裡使用固定的超時機制是一種反模式,因為我們的環境是 高度動態 的,所以幾乎不可能為每一種情況定義正確的限定時間。

我們可以使用回路斷路器,而不是使用固定的超時機制。回路斷路器的名字源於真實世界的電子元件,因為它們的行為極其相似。它們可以保護資源,有助於進行系統恢復。在分散式系統裡,它們能夠起到很大的作用,特別是當重複性的故障造成雪崩效應進而威脅到整個系統的時候。

如果某種類型的錯誤在一定時間段內多次發生,回路斷路器就會斷開。斷開的回路斷路器會阻止後續的請求,就像電子元件裡的斷路器一樣。回路斷路器會在一段時間之後關閉,給底層的服務更多的空間進行恢復。

要記住,並不是所有的錯誤都需要觸發回路斷路器。例如,你可能想要忽略用戶端的一些問題,比如那些包含 4xx 回應碼的請求,但同時想要保留 5xx 的伺服器端錯誤。

有些回路斷路器會出現半開閉狀態。這個時候,服務會發送一個請求來檢測系統的可用性,同時拒絕其他的請求。如果檢測系統可用性的請求成功返回,那麼就會關閉斷路器,繼續處理後續的請求。否則的話,它就保持打開狀態。

圖:回路斷路器做好故障測試

我們應該持續不斷地針對各種常見問題對我們的系統進行測試,確保我們的服務能夠應對各種故障。

例如,我們可以使用一個外部服務來識別一組服務實例,然後隨機地終止其中的一個實例。這樣就知道該如何應對單個實例故障,當然,也可以關閉整組服務來類比雲服務中斷。

Netflix 的 ChaosMonkey 是一個非常受歡迎的彈性測試工具。

總 結

實現和運行可靠的服務並不是一件簡單的事情。你需要付出很多的努力,你的公司也因此要付出很高的代價。

可靠性可以分為多種層次,也涉及到多個方面的內容,你要找到適合自己團隊的解決方案。你應該把可靠性作為業務決策的一個考量因素,並為它分配足夠的預算和時間。

Java工程化、高性能及分散式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大資料等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿裡面試但擔心面試不過的,你都可以來,群號為:647631030

注:加群要求

1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿裡Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!

6.小號或者小白之類加群一律不給過,謝謝。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示