引言
首先,之所以談這個話題呢,是發(fā)現(xiàn)現(xiàn)在很多人對微服務(wù)的設(shè)計(jì)缺乏認(rèn)識,所以寫一篇掃盲文。當(dāng)然,考慮到目前大多微服務(wù)的文章都是口水文,煙哥爭取將實(shí)現(xiàn)方式講透,點(diǎn)清楚,讓大家有所收獲!
OK,我要先說明一下,我有很長一段時間將服務(wù)降級和服務(wù)熔斷混在一起,認(rèn)為是一回事!
為什么我會有這樣的誤解呢?
針對下面的情形,如圖所示
當(dāng)Service A調(diào)用Service B,失敗多次達(dá)到一定閥值,Service A不會再去調(diào)Service B,而會去執(zhí)行本地的降級方法!
對于這么一套機(jī)制:在Spring cloud中結(jié)合Hystrix,將其稱為熔斷降級!
所以我當(dāng)時就以為是一回事了,畢竟熔斷和降級是一起發(fā)生的,而且這二者的概念太相近了!后面接觸了多了,發(fā)現(xiàn)自己理解的還是太狹隘了,因此本文中帶著點(diǎn)我自己的見解,大家如果有不同意見,請輕噴!畢竟還有很多人認(rèn)為兩者是一致的!
正文
服務(wù)雪崩
OK,我們從服務(wù)雪崩開始講起!假設(shè)存在如下調(diào)用鏈
而此時,Service A的流量波動很大,流量經(jīng)常會突然性增加!那么在這種情況下,就算Service A能扛得住請求,Service B和Service C未必能扛得住這突發(fā)的請求。
此時,如果Service C因?yàn)榭共蛔≌埱?,變得不可用。那么Service B的請求也會阻塞,慢慢耗盡Service B的線程資源,Service B就會變得不可用。緊接著,Service A也會不可用,這一過程如下圖所示
如上圖所示,一個服務(wù)失敗,導(dǎo)致整條鏈路的服務(wù)都失敗的情形,我們稱之為服務(wù)雪崩。
ps:誰發(fā)明的這個詞,真是面試裝13必備!
那么,服務(wù)熔斷和服務(wù)降級就可以視為解決服務(wù)雪崩的手段之一。
服務(wù)熔斷
那么,什么是服務(wù)熔斷呢?
服務(wù)熔斷:當(dāng)下游的服務(wù)因?yàn)槟撤N原因突然變得不可用或響應(yīng)過慢,上游服務(wù)為了保證自己整體服務(wù)的可用性,不再繼續(xù)調(diào)用目標(biāo)服務(wù),直接返回,快速釋放資源。如果目標(biāo)服務(wù)情況好轉(zhuǎn)則恢復(fù)調(diào)用。
需要說明的是熔斷其實(shí)是一個框架級的處理,那么這套熔斷機(jī)制的設(shè)計(jì),基本上業(yè)內(nèi)用的是斷路器模式,如Martin Fowler提供的狀態(tài)轉(zhuǎn)換圖如下所示
最開始處于closed狀態(tài),一旦檢測到錯誤到達(dá)一定閾值,便轉(zhuǎn)為open狀態(tài);
這時候會有個 reset timeout,到了這個時間了,會轉(zhuǎn)移到half open狀態(tài);
嘗試放行一部分請求到后端,一旦檢測成功便回歸到closed狀態(tài),即恢復(fù)服務(wù);
業(yè)內(nèi)目前流行的熔斷器很多,例如阿里出的Sentinel,以及最多人使用的Hystrix
在Hystrix中,對應(yīng)配置如下
//滑動窗口的大小,默認(rèn)為20
circuitBreaker.requestVolumeThreshold
//過多長時間,熔斷器再次檢測是否開啟,默認(rèn)為5000,即5s鐘
circuitBreaker.sleepWindowInMilliseconds
//錯誤率,默認(rèn)50%
circuitBreaker.errorThresholdPercentage
每當(dāng)20個請求中,有50%失敗時,熔斷器就會打開,此時再調(diào)用此服務(wù),將會直接返回失敗,不再調(diào)遠(yuǎn)程服務(wù)。直到5s鐘之后,重新檢測該觸發(fā)條件,判斷是否把熔斷器關(guān)閉,或者繼續(xù)打開。
這些屬于框架層級的實(shí)現(xiàn),我們只要實(shí)現(xiàn)對應(yīng)接口就好!
服務(wù)降級
那么,什么是服務(wù)降級呢?
這里有兩種場景:
當(dāng)下游的服務(wù)因?yàn)槟撤N原因響應(yīng)過慢,下游服務(wù)主動停掉一些不太重要的業(yè)務(wù),釋放出服務(wù)器資源,增加響應(yīng)速度!
當(dāng)下游的服務(wù)因?yàn)槟撤N原因不可用,上游主動調(diào)用本地的一些降級邏輯,避免卡頓,迅速返回給用戶!
其實(shí)乍看之下,很多人還是不懂熔斷和降級的區(qū)別!
其實(shí)應(yīng)該要這么理解:
服務(wù)降級有很多種降級方式!如開關(guān)降級、限流降級、熔斷降級!
服務(wù)熔斷屬于降級方式的一種!
可能有的人不服,覺得熔斷是熔斷、降級是降級,分明是兩回事??!其實(shí)不然,因?yàn)閺膶?shí)現(xiàn)上來說,熔斷和降級必定是一起出現(xiàn)。因?yàn)楫?dāng)發(fā)生下游服務(wù)不可用的情況,這個時候?yàn)榱藢ψ罱K用戶負(fù)責(zé),就需要進(jìn)入上游的降級邏輯了。因此,將熔斷降級視為降級方式的一種,也是可以說的通的!
我撇開框架,以最簡單的代碼來說明!上游代碼如下
try{
//調(diào)用下游的helloWorld服務(wù)
xxRpc.helloWorld();
}catch(Exception e){
//因?yàn)槿蹟?,所以調(diào)不通
doSomething();
}
注意看,下游的helloWorld服務(wù)因?yàn)槿蹟喽{(diào)不通。此時上游服務(wù)就會進(jìn)入catch里頭的代碼塊,那么catch里頭執(zhí)行的邏輯,你就可以理解為降級邏輯!
什么,你跟我說你不捕捉異常,直接丟頁面?
OK,那我甘拜下風(fēng),當(dāng)我理解錯誤!
服務(wù)降級大多是屬于一種業(yè)務(wù)級別的處理。當(dāng)然,我這里要講的是另一種降級方式,也就是開關(guān)降級!這也是我們生產(chǎn)上常用的另一種降級方式!
做法很簡單,做個開關(guān),然后將開關(guān)放配置中心!在配置中心更改開關(guān),決定哪些服務(wù)進(jìn)行降級。至于配置變動后,應(yīng)用怎么監(jiān)控到配置發(fā)生了變動,這就不是本文該討論的范圍。
那么,在應(yīng)用程序中部下開關(guān)的這個過程,業(yè)內(nèi)也有一個名詞,稱為埋點(diǎn)!
那接下來最關(guān)鍵的一個問題,哪些業(yè)務(wù)需要埋點(diǎn)?
一般有以下方法
(1)簡化執(zhí)行流程
自己梳理出核心業(yè)務(wù)流程和非核心業(yè)務(wù)流程。然后在非核心業(yè)務(wù)流程上加上開關(guān),一旦發(fā)現(xiàn)系統(tǒng)扛不住,關(guān)掉開關(guān),結(jié)束這些次要流程。
(2)關(guān)閉次要功能
一個微服務(wù)下肯定有很多功能,那自己區(qū)分出主要功能和次要功能。然后次要功能加上開關(guān),需要降級的時候,把次要功能關(guān)了吧!
(3)降低一致性
假設(shè),你在業(yè)務(wù)上發(fā)現(xiàn)執(zhí)行流程沒法簡化了,愁??!也沒啥次要功能可以關(guān)了,桑心啊!那只能降低一致性了,即將核心業(yè)務(wù)流程的同步改異步,將強(qiáng)一致性改最終一致性!
可是這些都是手動降級,有辦法自動降級么?
這里我摸著良心說,我們在生產(chǎn)上沒弄自動降級!因?yàn)橐话阈枰导壍膱鼍?,都是可以預(yù)見的,例如某某活動。假設(shè),平時真的有突發(fā)事件,流量異常,也有監(jiān)控系統(tǒng)發(fā)郵件通知,提醒我們?nèi)ソ导墸?/p>
當(dāng)然,這并不代表自動降級不能做,因此以下內(nèi)容可以認(rèn)為我在胡說八道,因?yàn)槲以谏a(chǎn)上沒實(shí)踐過,只是頭腦大概想了下,如果讓我來做自動降級我會怎么實(shí)現(xiàn):
(1)自己設(shè)一個閾值,例如幾秒內(nèi)失敗多少次,就啟動降級
(2)自己做接口監(jiān)控(有興趣的可以了解一下Rxjava),達(dá)到閾值就走推送邏輯。怎么推呢?比如你配置是放在git上,就用jgit去改配置中心的配置。如果配置放數(shù)據(jù)庫,就用jdbc去改。
(3)改完配置中心的配置后,應(yīng)用就可以自動檢測到配置的變化,進(jìn)行降級!(這句不了解的,了解一下配置中心的熱刷新功能)