如何解决微服务架构中的雪崩问题?
? ? ?記得在三年前公司因為業(yè)務發(fā)展需要,就曾經(jīng)將單體應用遷移到分布式框架上來。當時就遇到了這樣一個問題:系統(tǒng)僅有一個控制單元,它會調用多個運算單元,如果某個運算單元(作為服務提供者)不可用,將導致控制單元(作為服務調用者)被阻塞,最終導致控制單元崩潰,進而導致整個系統(tǒng)都面臨著癱瘓的風險。
那個時候還不知道這其實就是服務的雪崩效應,雪崩效應好比就是蝴蝶效應,說的都是一個小因素的變化,卻往往有著無比強大的力量,以至于最后改變整體結構、產(chǎn)生意想不到的結果。雪崩效應也是我們目前研發(fā)的產(chǎn)品直面的一道坎,下面我們來看有哪些場景會引發(fā)雪崩,又如何避免?對于無法避免的雪崩效應,我們又有哪些應對措施?
1. 星火燎原
1.1農(nóng)民眼中的微服務
近年來,微服務就象一把燎原的大火,竄了出來并在整個技術社區(qū)燒了起來,微服務架構被認為是IT軟件服務化架構演進的目標。為什么微服務這么火,微服務能給企業(yè)帶來什么價值?
1.1.1 以種植農(nóng)作物的思想來理解微服務
我們以耕種為例來看如何充分利用一塊田地的:
-
先在地里種植了一排排玉米;
-
后來發(fā)現(xiàn)玉米腳下空地可以利用,再間隔一段距離再種上豆角,豆角長大后順著玉米桿往上爬,最后緊緊地纏繞在玉米桿上;
-
再后來發(fā)現(xiàn)每排玉米之間的空隙地還可以再種些土豆,土豆蔓藤以后會交織在一起,肆虐在玉米腳下吞食營養(yǎng)物質;
表面看來一塊土地得到了充分利用,實際上各農(nóng)作物得不到充分的光照和適宜的營養(yǎng),如此一來加大了后期除草、松土、施肥、灌溉及收割的成本。
下面的耕植思路是不是更好點呢? 一整塊地根據(jù)需要分配為若干大小土地塊,每塊地之間清晰分界,這樣就有了玉米地、土豆地、豆角地,再想種什么劃塊地再耕作就可以了。
這樣種植好處很多,比如玉米、豆角和土豆需要的營養(yǎng)物質是不一樣的,可由專業(yè)技術人員施肥;玉米,豆角和土豆分離,避免豆角藤爬上玉米,纏繞玉米不能自由生長。土豆又汲取玉米需要的營養(yǎng)物質等等問題。
軟件系統(tǒng)實現(xiàn)與農(nóng)作物的種植方式其實也很類似,傳統(tǒng)的應用在擴展性,可靠性,維護成本上表現(xiàn)都不盡人意。如何充分利用大量系統(tǒng)資源,管理和監(jiān)控服務生命周期都是頭疼的事情,軟件系統(tǒng)設計迫切需要上述的“土地分割種植法”。微服務架構應運而生:在微服務系統(tǒng)中,各個業(yè)務系統(tǒng)間通過對消息(字符序列)的處理都非常友好的RestAPI進行消息交互。如此一來,各個業(yè)務系統(tǒng)根據(jù)Restful架構風格統(tǒng)一成一個有機系統(tǒng)。
1.2 微服務架構下的冰山
泰坦尼克號曾經(jīng)是世界最大的客輪,在當時被稱為是”永不沉沒“的,但卻在北大西洋撞上冰山而沉沒。我們往往只看到它浮出水面的絢麗多彩,水下的基礎設施如資源規(guī)劃、服務注冊發(fā)現(xiàn)、部署升級,灰度發(fā)布等都是需要考慮的因素。
1.2.1 優(yōu)勢
-
復雜應用分解:復雜的業(yè)務場景可被分解為多個業(yè)務系統(tǒng),每個業(yè)務系統(tǒng)的每個服務都有一個用消息驅動API定義清楚的邊界。
-
契約驅動:每個業(yè)務系統(tǒng)可自由選擇技術,組建技術團隊利用Mock服務提供者和消費者,并行開發(fā),最終實現(xiàn)依賴解耦。
-
自由擴展:每個系統(tǒng)可根據(jù)業(yè)務需要獨自進行擴展。
-
獨立部署:每個業(yè)務系統(tǒng)互相獨立,可根據(jù)實際需要部署到合適的硬件機器上。
-
良好隔離:一個業(yè)務系統(tǒng)資源泄漏不會導致整個系統(tǒng)宕掉,容錯性較好。
1.2.2 面臨的挑戰(zhàn)
-
服務管理:敏捷迭代后的微服務可能越來越多,各個業(yè)務系統(tǒng)之間的交互也越來越多,如何做高效集群通信方案也是問題。
-
應用管理: 每個業(yè)務系統(tǒng)部署后對應著一個進程,進程可以啟停。如果機器掉電或者宕機了,如何做無縫切換都需要強大的部署管理機制。
-
負載均衡:為應對大流量場景及提供系統(tǒng)可靠性,同一個業(yè)務系統(tǒng)也會做分布式部署即一個業(yè)務實例部署在多臺機器上。如果某個業(yè)務系統(tǒng)掛掉了,如何按需做自動伸縮分布式方案方案也需要考慮。
-
問題定位:單體應用的日志集中在一起,出現(xiàn)問題定位很方便,而分布式環(huán)境的問題定界定位,日志分析都較為困難。
-
雪崩問題:分布式系統(tǒng)都存在這樣一個問題,由于網(wǎng)絡的不穩(wěn)定性,決定了任何一個服務的可用性都不是 100% 的。當網(wǎng)絡不穩(wěn)定的時候,作為服務的提供者,自身可能會被拖死,導致服務調用者阻塞,最終可能引發(fā)雪崩效應。
Michael T. Nygard 在精彩的《Release It!》一書中總結了很多提高系統(tǒng)可用性的模式,其中非常重要的兩條是:使用超時策略和使用熔斷器機制。
-
超時策略:如果一個服務會被系統(tǒng)中的其它部分頻繁調用,一個部分的故障可能會導致級聯(lián)故障。例如,調用服務的操作可以配置為執(zhí)行超時,如果服務未能在這個時間內響應,將回復一個失敗消息。然而,這種策略可能會導致許多并發(fā)請求到同一個操作被阻塞,直到超時期限屆滿。這些阻塞的請求可能會存儲關鍵的系統(tǒng)資源,如內存、線程、數(shù)據(jù)庫連接等。因此,這些資源可能會枯竭,導致需要使用相同的資源系統(tǒng)的故障。在這種情況下,它將是優(yōu)選的操作立即失敗。設置較短的超時可能有助于解決這個問題,但是一個操作請求從發(fā)出到收到成功或者失敗的消息需要的時間是不確定的。
-
熔斷器模式:熔斷器的模式使用斷路器來檢測故障是否已得到解決,防止請求反復嘗試執(zhí)行一個可能會失敗的操作,從而減少等待糾正故障的時間,相對與超時策略更加靈活。
一年一度的雙十一已經(jīng)悄然來臨,下面將介紹某購物網(wǎng)站一個Tomcat容器在高并發(fā)場景下的雪崩效應來探討Hystrix的線程池隔離技術和熔斷器機制。
2. 從雪崩看應用防護
2.1 雪崩問題的本質:Servlet Container在高并發(fā)下崩潰
我們先來看一個分布式系統(tǒng)中常見的簡化的模型。Web服務器中的Servlet Container,容器啟動時后臺初始化一個調度線程,負責處理Http請求,然后每個請求過來調度線程從線程池中取出一個工作者線程來處理該請求,從而實現(xiàn)并發(fā)控制的目的。
Servlet Container是我們的容器,如Tomcat。一個用戶請求有可能依賴其它多個外部服務。考慮到應用容器的線程數(shù)目基本都是固定的(比如Tomcat的線程池默認200),當在高并發(fā)的情況下,如果某一外部依賴的服務(第三方系統(tǒng)或者自研系統(tǒng)出現(xiàn)故障)超時阻塞,就有可能使得整個主線程池被占滿,增加內存消耗,這是長請求擁塞反模式(一種單次請求時延變長而導致系統(tǒng)性能惡化甚至崩潰的惡化模式)。
更進一步,如果線程池被占滿,那么整個服務將不可用,就又可能會重復產(chǎn)生上述問題。因此整個系統(tǒng)就像雪崩一樣,最終崩塌掉。
2.2 雪崩效應產(chǎn)生的幾種場景
-
流量激增:比如異常流量、用戶重試導致系統(tǒng)負載升高;
-
緩存刷新:假設A為client端,B為Server端,假設A系統(tǒng)請求都流向B系統(tǒng),請求超出了B系統(tǒng)的承載能力,就會造成B系統(tǒng)崩潰;
-
程序有Bug:代碼循環(huán)調用的邏輯問題,資源未釋放引起的內存泄漏等問題;
-
硬件故障:比如宕機,機房斷電,光纖被挖斷等。
-
線程同步等待:系統(tǒng)間經(jīng)常采用同步服務調用模式,核心服務和非核心服務共用一個線程池和消息隊列。如果一個核心業(yè)務線程調用非核心線程,這個非核心線程交由第三方系統(tǒng)完成,當?shù)谌较到y(tǒng)本身出現(xiàn)問題,導致核心線程阻塞,一直處于等待狀態(tài),而進程間的調用是有超時限制的,最終這條線程將斷掉,也可能引發(fā)雪崩;
2.3 雪崩效應的常見解決方案
針對上述雪崩情景,有很多應對方案,但沒有一個萬能的模式能夠應對所有場景。
-
針對流量激增,采用自動擴縮容以應對突發(fā)流量,或在負載均衡器上安裝限流模塊。
-
針對緩存刷新,參考Cache應用中的服務過載案例研究
-
針對硬件故障,多機房容災,跨機房路由,異地多活等。
-
針對同步等待,使用Hystrix做故障隔離,熔斷器機制等可以解決依賴服務不可用的問題。
通過實踐發(fā)現(xiàn),線程同步等待是最常見引發(fā)的雪崩效應的場景,本文將重點介紹使用Hystrix技術解決服務的雪崩問題。后續(xù)再分享流量激增和緩存刷新等應對方案。
3. 隔離和熔斷
Hystrix 是由Netflix發(fā)布,旨在應對復雜分布式系統(tǒng)中的延時和故障容錯,基于Apache License 2.0協(xié)議的開源的程序庫,目前托管在GitHub上。
Hystrix采用了命令模式,客戶端需要繼承抽象類HystrixCommand并實現(xiàn)其特定方法。為什么使用命令模式呢?使用過RPC框架都應該知道一個遠程接口所定義的方法可能不止一個,為了更加細粒度的保護單個方法調用,命令模式就非常適合這種場景。
命令模式的本質就是分離方法調用和方法實現(xiàn),在這里我們通過將接口方法抽象成HystricCommand的子類,從而獲得安全防護能力,并使得的控制力度下沉到方法級別。
Hystrix核心設計理念基于命令模式,命令模式UML如下圖:
可見,Command是在Receiver和Invoker之間添加的中間層,Command實現(xiàn)了對Receiver的封裝。那么Hystrix的應用場景如何與上圖對應呢?
API既可以是Invoker又可以是Reciever,通過繼承Hystrix核心類HystrixCommand來封裝這些API(例如,遠程接口調用,數(shù)據(jù)庫的CRUD操作可能會產(chǎn)生延時),就可以為API提供彈性保護了。
3.1 資源隔離模式
Hystrix之所以能夠防止雪崩的本質原因,是其運用了資源隔離模式,我們可以用蓄水池做比喻來解釋什么是資源隔離。生活中一個大的蓄水池由一個一個小的池子隔離開來,這樣如果某一個水池的水被污染,也不會波及到其它蓄水池,如果只有一個蓄水池,水池被污染,整池水都不可用了。軟件資源隔離如出一轍,如果采用資源隔離模式,將對遠程服務的調用隔離到一個單獨的線程池后,若服務提供者不可用,那么受到影響的只會是這個獨立的線程池。
(1)線程池隔離模式:使用一個線程池來存儲當前的請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求堆積入線程池隊列。這種方式需要為每個依賴的服務申請線程池,有一定的資源消耗,好處是可以應對突發(fā)流量(流量洪峰來臨時,處理不完可將數(shù)據(jù)存儲到線程池隊里慢慢處理)。這個大家都比較熟悉,參考Java自帶的ThreadPoolExecutor線程池及隊列實現(xiàn)。線程池隔離參考下圖:
線程隔離的優(yōu)點:
-
請求線程與依賴代碼的執(zhí)行線程可以完全隔離第三方代碼;
-
當一個依賴線程由失敗變成可用時,線程池將清理后并立即恢復可用;
-
線程池可設置大小以控制并發(fā)量,線程池飽和后可以拒絕服務,防止依賴問題擴散。
線程隔離的缺點:
-
增加了處理器的消耗,每個命令的執(zhí)行涉及到排隊(默認使用SynchronousQueue避免排隊)和調度;
-
增加了使用ThreadLocal等依賴線程狀態(tài)的代碼復雜性,需要手動傳遞和清理線程狀態(tài)。
(2)信號量隔離模式:使用一個原子計數(shù)器來記錄當前有多少個線程在運行,請求來先判斷計數(shù)器的數(shù)值,若超過設置的最大線程個數(shù)則丟棄該類型的新請求,若不超過則執(zhí)行計數(shù)操作請求來計數(shù)器+1,請求返回計數(shù)器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發(fā)流量(流量洪峰來臨時,處理的線程超過數(shù)量,其他的請求會直接返回,不繼續(xù)去請求依賴的服務),參考Java的信號量的用法。
Hystrix默認采用線程池隔離機制,當然用戶也可以配置 HystrixCommandProperties為隔離策略為ExecutionIsolationStrategy.SEMAPHORE。
信號隔離的特點:
-
信號隔離與線程隔離最大不同在于執(zhí)行依賴代碼的線程依然是請求線程,該線程需要通過信號申請;
-
如果客戶端是可信的且可以快速返回,可以使用信號隔離替換線程隔離,降低開銷。
線程池隔離和信號隔離的區(qū)別見下圖,使用線程池隔離,用戶請求了15條線程,10條線程依賴于A線程池,5條線程依賴于B線程池;如果使用信號量隔離,請求到C客戶端的信號量若設置了15,那么圖中左側用戶請求的10個信號與右邊的5個信號量需要與設置閾值進行比較,小于等于閾值則執(zhí)行,否則直接返回。
建議使用的場景:根據(jù)請求服務級別劃分不同等級業(yè)務線程池,甚至可以將核心業(yè)務部署在獨立的服務器上。
3.2 熔斷器機制
熔斷器與家里面的保險絲有些類似,當電流過大時,保險絲自動熔斷以保護我們的電器。假設在沒有熔斷器機制保護下,我們可能會無數(shù)次的重試,勢必持續(xù)加大服務端壓力,造成惡性循環(huán);如果直接關閉重試功能,當服務端又可用的時候,我們如何恢復?
熔斷器正好適合這種場景:當請求失敗比率(失敗/總數(shù))達到一定閾值后,熔斷器開啟,并休眠一段時間,這段休眠期過后熔斷器將處與半開狀態(tài)(half-open),在此狀態(tài)下將試探性的放過一部分流量(Hystrix只支持single request),如果這部分流量調用成功后,再次將熔斷器閉合,否則熔斷器繼續(xù)保持開啟并進入下一輪休眠周期。
建議使用場景:Client端直接調用遠程的Server端(server端由于某種原因不可用,從client端發(fā)出請求到server端超時響應之間占用了系統(tǒng)資源,如內存,數(shù)據(jù)庫連接等)或共享資源。
不建議的場景如下:
-
應用程序直接訪問如內存中的數(shù)據(jù),若使用熔斷器模式只會增加系統(tǒng)額外開銷。
-
作為業(yè)務邏輯的異常處理替代品。
- 針對說到的一些東西我特意整理了一下,有很多技術不是靠幾句話能講清楚,所以干脆找朋友錄制了一些視頻,很多問題其實答案很簡單,但是背后的思考和邏輯不簡單,要做到知其然還要知其所以然。如果想學習Java工程化、高性能及分布式、深入淺出。微服務、Spring,MyBatis,Netty源碼分析的朋友可以加我的Java進階群:318261748 群里有阿里大牛直播講解技術,以及Java大型互聯(lián)網(wǎng)技術的視頻免費分享給大家。
總結思考
本文從自己曾經(jīng)開發(fā)的項目應用的分布式架構引出服務的雪崩效應,進而引出Hystrix(當然了,Hystrix還有很多優(yōu)秀的特性,如緩存,批量處理請求,主從分擔等,本文主要介紹了資源隔離和熔斷)。主要分三部分進行說明:
第一部分:以耕種田地的思想引出軟件領域設計的微服務架構, 簡單的介紹了其優(yōu)點,著重介紹面臨的挑戰(zhàn):雪崩問題。
第二部分:以Tomcat Container在高并發(fā)下崩潰為例揭示了雪崩產(chǎn)生的過程,進而總結了幾種誘發(fā)雪崩的場景及各種場景的應對解決方案,針對同步等待引出了Hystrix框架。
第三部分:介紹了Hystrix背景,資源隔離(總結了線程池和信號量特點)和熔斷機制工作過程,并總結各自使用場景。
如Martin Fowler 在其文中所說,盡管微服務架構未來需要經(jīng)歷時間的檢驗,但我們已經(jīng)走在了微服務架構轉型的道路上,對此我們可以保持謹慎的樂觀,這條路依然值得去探索。
總結
以上是生活随笔為你收集整理的如何解决微服务架构中的雪崩问题?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java.awt.Graphics2D
- 下一篇: 微服务架构---服务降级