天天看點

如何提高微服務架構的可用性

下圖是一個簡化的使用者請求示意圖,設定一個使用者請求依賴5個微服務的協作完成(pod為k8s容器架構中的定義,為一組相同功能的容器)。

如何提高微服務架構的可用性

在一開始每個依賴的service都是正常的,現假設有一個service異常了,這時可能會有三種情況:

1. 這個請求成功,假設因網絡異常或當機導緻service c某個節點不可用,但有高可用節點取代了這個失敗節點,這時service c不受影響,依然可用,如下圖所示:

如何提高微服務架構的可用性

2. 這個請求是成功的,假設是service d故障,而這個service不是關鍵性的,運作失敗也可以繼續進行,比如注冊使用者需要調用一個服務發送注冊成功的郵件給使用者,如果發郵件的這個service不可用,但不會影響使用者的注冊,是以使用者注冊還是會成功,郵件可以等服務恢複後再發送,隻有時間上的延遲。這時service a不受影響,依然可用,如下圖所示:

如何提高微服務架構的可用性

3. 這個請求失敗,比如異常的節點是service e,而service e是代碼級邏輯異常,所有高可用節點都不可用,這時需要将service e進行依賴隔離,否則servicea可能會受到servicee的影響而不可用。需要做一些措施確定service a不會受影響,依然可用,如下圖所示:

如何提高微服務架構的可用性

可以從以下幾個政策可以提高微服務架構的可用性:

1) 失效轉移

提高服務的高可用性,最基本的原則就是消除單點,通過負載均衡技術建構叢集,所有的叢集節點都是無狀态且完全對等的。如上面講的第1種情況。當一個節點異常時,負載均衡伺服器會把使用者發送的通路請求發送到可用的節點上。對使用者來說,某個節點異常是無感的,使用者請求會透明的轉移到了可用的節點上執行。

2) 異步調用

避免一個服務失敗導緻整個應用請求失敗很重要的是使用異步調用。如上面講的第2種情況。如果采用的是同步調用,當郵件服務異常時,會導緻其他兩個服務也無法執行,最終導緻使用者注冊失敗。如果采用異步調用,service a把使用者注冊資訊發送給消息隊列後立即傳回使用者注冊成功的響應,雖然郵件服務不能用,但是寫資料庫的服務,權限開通等服務都能正常執行。是以即使郵件不能發送成功,也不會影響其他服務的執行,使用者注冊可順利完成。

3) 依賴隔離

使用者請求發送給service a,service a配置設定線程資源通過網絡遠端調用其他的service,假設調用service e發生異常時,service a中對service e調用的線程就可能會響應慢或僵死,而線程是系統的資源,如果短時間内得不到釋放,在高并發的情況下資源就會被耗盡,結果會導緻service a也不可用,雖然其他的服務依然可用。

如何提高微服務架構的可用性

service a的資源是有限的,比如service a啟動時配置設定了400個線程,當400個線程都因調用service e時異常不能及時正常的釋放,如線程死鎖,響應時間慢,導緻 400個線程全部僵死在調用service e上,這裡service a就沒有空閑的線程來接收新的使用者請求,這時就會導緻service a挂起或僵死。是以避免service a被依賴的服務拖垮就是要確定service a的線程資源不會被調用的依賴服務耗盡,在<b>《release it!》</b>一書中總結了非常重要的兩條方法: 設定逾時和使用斷路器。

設定逾時

在應用中設定服務調用的逾時時間後,一旦線程的執行時間超過了所設定的時間,就抛出異常資訊,自動斷開連接配接,這樣服務的線程就不會都長時間僵死在調用異常的服務上,導緻沒有空閑線程接收新的使用者請求,可以避免service a因為調用server e 異常而被拖垮,自身不可用。是以通過網絡調用外部依賴服務時,都必須設定逾時。

使用斷路器

斷路器大家都不陌生,家裡電表在電流過載或者短路時就會跳閘,如果不跳閘,電路就不斷開,電線就會升溫,造成火災。有了斷路器之後,電流過載時就會自動跳閘斷開電路,避免引起更大的災難。在程式中也是如此,當知道服務調用某個依賴服務有大量逾時的時候,再讓新的請求去通路也隻會逾時,并不能得到想到的結果,還會消耗現有資源,增加負載,導緻服務不可用。這個時候使用斷路器就能避免這種資源浪費,在自身服務和依賴服務之間放一個斷路器,通過斷路器的監控實時統計通路的狀态,當通路逾時或者失敗達到某個門檻值的時候(如50%請求逾時,或者連續20次請失敗),就打開斷路器,那麼後續的請求就直接傳回失敗,而不是一個長時間的等待,再根據一個時間間隔(如30秒)或請求逾時的情況(如0%的逾時)嘗試關閉斷路器(或者更換保險絲),看依賴是否恢複服務了。

一個服務依賴多個服務時,如果其中一個非核心的依賴不可用,通過設定逾時和使用斷路器,可以確定service a在調用異常的service e并不會導緻自身的異常,在大部分情況下服務還能健康運轉,可以很好的做到依賴隔離。如下圖所示:

如何提高微服務架構的可用性

4)設定限流

在服務通路的高峰期可能因為大量的并發導緻性能下降,嚴重時将會有大量的請求排隊,可能會導緻服務當機。為了保證應用的可用性,可拒絕低優先級的調用,讓高優先級的請求成功,避免所有調用都失敗的情況,并且為每個依賴服務提供一個小的線程池,如果線程池已滿調用将被立即拒絕,預設不采用排隊,可以加速失敗判定時間。這樣的結果是有些使用者可以通路,而有些使用者失敗,但失敗的使用者重新通路又可以是正常的通路。這樣能確定服務的可用性,而不是完全不可用。

雖然有了上面的一些可提高系統可用性的措施,但系統是複雜的,一個簡單的修複都有可能造成不可想像的後果,且系統又是動态的,有些系統可能一天都釋出幾次,幾十次。在這種情況下 故障依然是不可避免的。比起半夜深睡或正在享受節假日的美好時光時系統故障來當救火隊員,會做更多的措施來提高系統的可用性。比如在某些企業裡會定期舉行生産環境的故障應急演練。過去都是在業務低峰時進行人為故障測試高可用方案是否生效,包括主機,網絡,應用,存儲等每一層架構都進行演練,而現在也逐漸的在正常的生産時間進行故障應急演練,檢查系統的高可用性。問題在于可能在演練時能夠立即恢複,但真實故障發生時還是會出現長時間故障得不到恢複的情況。一個是演練是按照已知的場景制定的方案實施,二是演練的範圍基本是高可用節點的切換或災備系統的切換,第三個問題是這個演練是人為操作,需要全員的參與,并不會頻繁的舉行。但系統是動态的,這次是高可用的,不代表下周,或下個月還是高可用的。

當單體架構變成微服務架構後,應用層的演練就會變得複雜,就像前面提到的,如果每個服務隻有一個故障可能都會有2100種不同。是以需要有一種自動的故障測試方式來回避微服務化後演練實施的可操作性。netflix公司提出了一種自動故障測試的方案來提高微服務架構的可用性。這個測試方案也是在生産環境中進行,而故障測試的最終目的,是為了當真的有故障發生時,生産環境不會停止服務,并且整套系統可以在沒有人為幹預的情況下,非常優雅地通過降級将發生故障的部分元件排除出去。他們認為如果隻在測試環境中測試,而真實生産環境的業務壓力,業務場景,環境配置、網絡性能和硬體性能都沒有測試過,當故障在生産環境中真實發生時發現緩解問題的方案可能會失效。而且這個測試隻在工作時間運作,這樣工程師可以得到告警并及時響應。

netflix通過peter alvaro在論文<b>《路徑驅動的故障注入(lineage-driven fault injection)》</b>中提到了一套名為“molly”的算法和自身的故障注入測試fit(failure injection testing)實作了這套安全地自動化故障注入測試。molly是從一套系統的無故障狀态出發,然後試圖去回答說“系統是如何達到目前這種無故障的狀态的?”簡單舉例介紹一下這個算法的原理。先利用自身的追蹤系統繪制一個樹來表示每個請求經過的所有的微服務,假設如下圖所示:

如何提高微服務架構的可用性

                                                                        (a or r or p or b)

在最開始,上圖中的四個節點都是必須的,且正常的。然後從這個正确輸出反推,随機選擇一個節點注入故障,找到并建構支援其正确性的邏輯鍊條圖 。當節點注入故障後,這時可能有三種情況:

1.這個請求失敗,我們已經找到一個節點會故障,進而我們可以删除未來的實驗中包含這個故障。

2.這個請示是成功的-但這個失敗的節點不是關鍵性的

3.這個請求成功,有高可用節點取代了這個失敗

在這個例子中,首先在ratings中注入失敗,但請求是成功的。說明rating失敗并不會影響服務的使用,那就先把這個節點排除,重新繪制請求樹:

如何提高微服務架構的可用性

                                                             (a or p or b) and (a or p or b or r)

這時可以看到,請求可以通過(a or p or b)的方式實作,也可以通過  (a or p or b or r)的方式實作。接下來再在playlist中注入故障,這時請求還是成功的,因為請求轉發到備用節點上執行,這裡将會有一個新的節點可以通路。

如何提高微服務架構的可用性

                                                    (a or pf or b) and (a or p or b) and (a or p or b or r)

這時可以更新公式,說明可以通過(a or pf or b) and (a or p or b) and (a or p or b or r)三種方式請求服務。然後通過這樣不停的測試直到周遊完所有正确輸出,沒有失敗的節點可以找到。

Molly沒有規定怎麼搜尋空間,是以實作時會估算所有的方案,然後随機選擇最小的方案的集合。比如,最後的方案可能是[{a}, {pf}, {b}, {p,pf}, {r,a}, {r,b} …]。先選擇所有的單節點注入失敗,再選擇所的有雙節點的組合注入失敗,依此類推。

這個測試的目的是在影響大量成員前找到和修複故障,在生産環境上進行故障測試時,不能接受引起大量的問題節點。為了避免這個風險,隻能在指定的範圍建構測試,指定的範圍包含兩個關鍵的概念:故障範圍(failure scope)和注入點(injection points)。故障範圍指的是,把一次故障測試可能産生的影響,限制在一個可控的範圍内,這個範圍可以小到某個特定的使用者或者裝置,也可以大到所有使用者的1%。而注入點指的是系統内計劃會發生故障的元件,比如rpc層,緩存層,或者持久層。下圖是這個測試的流程示意圖:

如何提高微服務架構的可用性

故障模拟測試從fit服務把故障模拟中繼資料注入到zuul(緣邊網關服務)開始,如果請求符合故障範圍(failure scope)則注入失敗。這個故障可能是延遲服務調用,或達到持久層失敗。每個被接觸到的注入點(injection points)檢查這個請求的上下文是否為指定要被注入故障的元件,如果是,在這個注入點模拟故障。

參考:

<a target="_blank" href="http://techblog.netflix.com/2016/01/automated-failure-testing.html">http://techblog.netflix.com/2016/01/automated-failure-testing.html</a>

<a target="_blank" href="http://techblog.netflix.com/2014/10/fit-failure-injection-testing.html">http://techblog.netflix.com/2014/10/fit-failure-injection-testing.html</a>

作者簡介:

<b>陳愛珍</b>,七牛雲布道師。多年企業級系統的應用運維及分布式系統實戰經驗。現專注于容器、微服務及devops落地的研究與實踐。

本文來自中生代技術交流群

微信公衆号:<b> freshmantechnology</b>

繼續閱讀