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