業(yè)界通常用多少個(gè)9來(lái)衡量系統(tǒng)的可用性,如99.99%表示一年中有1小時(shí)左右的不可用時(shí)間。任何一個(gè)服務(wù)的可用性都不會(huì)是100%,意味著在服務(wù)運(yùn)行時(shí)間里還是有可能發(fā)生故障。當(dāng)把功能集中且運(yùn)行在同一個(gè)應(yīng)用中的單體架構(gòu)拆分成多個(gè)相互獨(dú)立的微服務(wù)架構(gòu)后,雖然可以降低一損俱損的全局性故障風(fēng)險(xiǎn),但由于微服務(wù)之間存在大量的依賴關(guān)系, 隨著微服務(wù)個(gè)數(shù)的增多,依賴關(guān)系也將會(huì)變得越來(lái)越復(fù)雜,而且每個(gè)微服務(wù)都有可能發(fā)生故障,如果不能做好相互依賴的隔離,避免故障的連鎖反應(yīng),結(jié)果可能比單體更糟糕。
假設(shè)有100個(gè)微服務(wù),并且每個(gè)微服務(wù)只會(huì)發(fā)生1種故障,那么總共會(huì)有 2 100 種不同的故障場(chǎng)景,而每個(gè)微服務(wù)自身可能不止1種故障。當(dāng)某個(gè)微服務(wù)發(fā)生故障時(shí),如何確保不會(huì)導(dǎo)致其他依賴的微服務(wù)不可用, 如何確保系統(tǒng)自動(dòng)降級(jí)把發(fā)生故障的微服務(wù)排除出去,如何確保故障不會(huì)擴(kuò)展到整個(gè)系統(tǒng)? 那么如何有效確保微服務(wù)架構(gòu)的可用性將會(huì)成為挑戰(zhàn)。
下圖是一個(gè)簡(jiǎn)化的用戶請(qǐng)求示意圖,設(shè)定一個(gè)用戶請(qǐng)求依賴5個(gè)微服務(wù)的協(xié)作完成(pod為K8S容器框架中的定義,為一組相同功能的容器)。

在一開(kāi)始每個(gè)依賴的Service都是正常的,現(xiàn)假設(shè)有一個(gè)Service異常了,這時(shí)可能會(huì)有三種情況:
1. 這個(gè)請(qǐng)求成功,假設(shè)因網(wǎng)絡(luò)異?;蝈礄C(jī)導(dǎo)致Service C某個(gè)節(jié)點(diǎn)不可用,但有高可用節(jié)點(diǎn)取代了這個(gè)失敗節(jié)點(diǎn),這時(shí)Service C不受影響,依然可用,如下圖所示:

2. 這個(gè)請(qǐng)求是成功的,假設(shè)是Service D故障,而這個(gè)Service不是關(guān)鍵性的,運(yùn)行失敗也可以繼續(xù)進(jìn)行,比如注冊(cè)用戶需要調(diào)用一個(gè)服務(wù)發(fā)送注冊(cè)成功的郵件給用戶,如果發(fā)郵件的這個(gè)Service不可用,但不會(huì)影響用戶的注冊(cè),所以用戶注冊(cè)還是會(huì)成功,郵件可以等服務(wù)恢復(fù)后再發(fā)送,只有時(shí)間上的延遲。這時(shí)Service A不受影響,依然可用,如下圖所示:

3. 這個(gè)請(qǐng)求失敗,比如異常的節(jié)點(diǎn)是Service E,而Service E是代碼級(jí)邏輯異常,所有高可用節(jié)點(diǎn)都不可用,這時(shí)需要將Service E進(jìn)行依賴隔離,否則ServiceA可能會(huì)受到ServiceE的影響而不可用。需要做一些措施確保Service A不會(huì)受影響,依然可用,如下圖所示:

可以從以下幾個(gè)策略可以 提高微服務(wù)架構(gòu)的可用性:
1) 失效轉(zhuǎn)移
提高服務(wù)的高可用性,最基本的原則就是消除單點(diǎn),通過(guò)負(fù)載均衡技術(shù)構(gòu)建集群,所有的集群節(jié)點(diǎn)都是無(wú)狀態(tài)且完全對(duì)等的。如上面講的第1種情況。當(dāng)一個(gè)節(jié)點(diǎn)異常時(shí),負(fù)載均衡服務(wù)器會(huì)把用戶發(fā)送的訪問(wèn)請(qǐng)求發(fā)送到可用的節(jié)點(diǎn)上。對(duì)用戶來(lái)說(shuō),某個(gè)節(jié)點(diǎn)異常是無(wú)感的,用戶請(qǐng)求會(huì)透明的轉(zhuǎn)移到了可用的節(jié)點(diǎn)上執(zhí)行。
2) 異步調(diào)用
避免一個(gè)服務(wù)失敗導(dǎo)致整個(gè)應(yīng)用請(qǐng)求失敗很重要的是使用異步調(diào)用。如上面講的第2種情況。如果采用的是同步調(diào)用,當(dāng)郵件服務(wù)異常時(shí),會(huì)導(dǎo)致其他兩個(gè)服務(wù)也無(wú)法執(zhí)行,最終導(dǎo)致用戶注冊(cè)失敗。如果采用異步調(diào)用,Service A把用戶注冊(cè)信息發(fā)送給消息隊(duì)列后立即返回用戶注冊(cè)成功的響應(yīng),雖然郵件服務(wù)不能用,但是寫(xiě)數(shù)據(jù)庫(kù)的服務(wù),權(quán)限開(kāi)通等服務(wù)都能正常執(zhí)行。所以即使郵件不能發(fā)送成功,也不會(huì)影響其他服務(wù)的執(zhí)行,用戶注冊(cè)可順利完成。
3) 依賴隔離
用戶請(qǐng)求發(fā)送給Service A,Service A分配線程資源通過(guò)網(wǎng)絡(luò)遠(yuǎn)程調(diào)用其他的Service,假設(shè)調(diào)用Service E發(fā)生異常時(shí),Service A中對(duì)Service E調(diào)用的線程就可能會(huì)響應(yīng)慢或僵死,而線程是系統(tǒng)的資源,如果短時(shí)間內(nèi)得不到釋放,在高并發(fā)的情況下資源就會(huì)被耗盡,結(jié)果會(huì)導(dǎo)致Service A也不可用,雖然其他的服務(wù)依然可用。

Service A的資源是有限的,比如Service A啟動(dòng)時(shí)分配了400個(gè)線程,當(dāng)400個(gè)線程都因調(diào)用Service E時(shí)異常不能及時(shí)正常的釋放,如線程死鎖,響應(yīng)時(shí)間慢,導(dǎo)致 400個(gè)線程全部僵死在調(diào)用Service E上,這里Service A就沒(méi)有空閑的線程來(lái)接收新的用戶請(qǐng)求,這時(shí)就會(huì)導(dǎo)致Service A掛起或僵死。所以避免Service A被依賴的服務(wù)拖垮就是要確保Service A的線程資源不會(huì)被調(diào)用的依賴服務(wù)耗盡,在 《Release It!》 一書(shū)中總結(jié)了非常重要的兩條方法: 設(shè)置超時(shí)和使用斷路器。