徹底搞懂集群容錯和服務(wù)隔離
在分布式系統(tǒng)中,由于網(wǎng)絡(luò)條件以及系統(tǒng)自身缺陷等問題,服務(wù)與服務(wù)的調(diào)用過程難免會出現(xiàn)失敗。為了把這種失敗的影響程度降到最小,目前,業(yè)界有兩種主流的最佳實踐方案,分別是集群容錯和服務(wù)隔離。這節(jié)課,我就帶你利用這兩種方案來解決服務(wù)雪崩效應(yīng),幫你提升分布式系統(tǒng)服務(wù)訪問的可靠性。
假如你正在設(shè)計和開發(fā)一個分布式服務(wù)系統(tǒng),該系統(tǒng)中存在一批獨立運行的服務(wù)實例。圍繞某一個具體的業(yè)務(wù)場景,通常都會涉及多個服務(wù)之間的交互。
圖片
在這個典型場景中,存在一個從服務(wù) E 到服務(wù) A 的完整調(diào)用鏈路。如果其中有一個服務(wù)出現(xiàn)了宕機,那么整個調(diào)用鏈路中的其它服務(wù)因為資源的競爭也會處于不可用狀態(tài),這種擴散效應(yīng)就是所謂的服務(wù)雪崩效應(yīng)。
我們該怎么應(yīng)對這種服務(wù)雪崩效應(yīng)呢?
集群容錯策略
目前主流的應(yīng)對策略是提供服務(wù)訪問的容錯(Fault Tolerance)機制。想要實現(xiàn)容錯機制,集群就要做到冗余,也就是讓每個服務(wù)具備多個實例。這樣,當一個服務(wù)實例出現(xiàn)問題時,請求還可以嘗試訪問其它服務(wù)實例。
我們知道,集群的構(gòu)建相當于已經(jīng)具備了冗余的條件,而要想在訪問一個服務(wù)實例出現(xiàn)問題時確定下一個想要訪問的服務(wù)實例,就涉及具體的集群容錯策略了。
常見的集群容錯策略包括 Failover、Failback、Failsafe 和 Failfast 等。下面我們一一來了解。
Failover 是最常見、最實用的集群容錯策略。它又叫失效轉(zhuǎn)移策略,當發(fā)生服務(wù)調(diào)用異常時,可以重新在集群中查找下一個可用的服務(wù)實例。同時,為了防止無限重試,開發(fā)人員通常會對失敗重試最大次數(shù)進行限制,一般控制在 3 次之內(nèi)。
圖片
對應(yīng)的,F(xiàn)ailback 可以理解為失效自動恢復。當請求失敗時,系統(tǒng)會把這個失敗請求記錄下來,并通過一定的定時機制進行重試。這種策略適用于那些對時效性要求不高的場景,例如消息通知。
而 Failsafe 代表失敗安全策略,當獲取服務(wù)調(diào)用異常時,直接忽略。它通常將異常寫入審計日志等媒介,確保后續(xù)可以根據(jù)日志記錄找到引起異常的原因并解決。
Failfast 也叫快速失敗策略,它在獲取服務(wù)調(diào)用異常時,立即報錯。顯然,F(xiàn)ailfast 已經(jīng)徹底放棄了重試機制,等同于沒有容錯,可以用于非冪等性的寫入操作,這是一種常見的應(yīng)用場景。而在特定場景中也可以使用該策略確保非核心業(yè)務(wù)服務(wù)只調(diào)用一次,為重要的核心服務(wù)節(jié)約寶貴時間。
后三種集群容錯策略很容易混淆,它們之間的區(qū)別你可以參考這張圖:
圖片
除了這些最常見的集群容錯機制之外,在諸如 Dubbo 等主流分布式服務(wù)框架中,開發(fā)人員還實現(xiàn)了一些特殊的策略。比如 Forking 策略代表一種分支調(diào)用機制,在這種機制下請求會同時發(fā)送給集群中的多個服務(wù)實例,只要有一個服務(wù)實例響應(yīng)成功就行。再比如 Broadcast 策略,代表的是一種廣播機制,這種機制同樣也會調(diào)用所有服務(wù)實例,但只有在所有調(diào)用都成功的情況下才會返回成功。從適用場景上講,F(xiàn)orking 機制適用于實時性要求較高的場景,而 Broadcast 機制通常用于實現(xiàn)消息通知,不是簡單的遠程調(diào)用。
服務(wù)隔離策略
除了集群容錯策略外,應(yīng)對服務(wù)雪崩還有一種有效手段,就是服務(wù)隔離。
在架構(gòu)設(shè)計領(lǐng)域存在一個艙壁隔離(Bulkhead Isolation)的架構(gòu)模式,服務(wù)隔離的設(shè)計思想真是來自于此。那么,什么是艙壁隔離?你可以想象一艘船,這艘船有 8 個船艙,正常情況下,船艙壁是隔離海水或湖水的,如果一個船艙破了,進水了,那么只損失一個船艙,其它船艙可以不受影響。比如這張示意圖,2 號和 8 號船艙雖然已經(jīng)出現(xiàn)問題,但不影響其它 6 個船艙。
圖片
結(jié)合分布式服務(wù)架構(gòu)中的應(yīng)用場景,艙壁隔離模式的具體體現(xiàn)就是各種服務(wù)隔離機制。
其實,隔離本質(zhì)上是對系統(tǒng)中的服務(wù)進行分割,這樣當系統(tǒng)發(fā)生故障的時候,就能控制影響范圍。也就是說,隔離可以保證發(fā)生故障后只有出問題的服務(wù)不可用,其它服務(wù)仍然可用。
圖片
我們一起來看一下服務(wù)隔離的基本思路圖。圖中的隔離媒介是服務(wù)隔離實現(xiàn)的關(guān)鍵,針對它業(yè)界有一些成熟的實現(xiàn)方案,常見的包括線程隔離、進程隔離、集群隔離以及讀寫隔離等。
線程隔離
我們先來看看最常見的線程隔離。我們知道。系統(tǒng)執(zhí)行一個請求的基本單位是線程,所以使用線程隔離就相當于對請求與請求之間的資源進行了隔離。在日常開發(fā)過程中,我們通常使用線程池來創(chuàng)建線程,因此線程隔離的主要實現(xiàn)手段就是借助于線程池。當把不同的請求分配到不同的線程池進行處理時,就算一個線程池出現(xiàn)了問題,也不會把問題擴散到其它線程池。各個線程池之間相互獨立,各自確保自身業(yè)務(wù)的處理過程和可用性。
線程隔離可以說是實現(xiàn)服務(wù)隔離的基礎(chǔ),我們通過一個案例來進一步介紹它的工作場景和原理。假如系統(tǒng)存在服務(wù) A、服務(wù) B 和服務(wù) C 這 3 個服務(wù),我們通過設(shè)置運行時參數(shù)得到這 3 個服務(wù)一共使用 300 個線程,客戶端調(diào)用這 3 個服務(wù)會共享線程池。
圖片
你看這張示例圖,如果服務(wù) A 出現(xiàn)了問題,那么所有訪問它的線程就會因為同步等待響應(yīng)結(jié)果而不斷消耗線程資源,最終導致雪崩效應(yīng):
圖片
這里邊框為虛線的服務(wù)代表不可用,我們可以看到因為服務(wù) A 不可用導致共享的 300 線程全部耗盡,從而影響了其它兩個服務(wù)。因此基于線程隔離機制,我們就可以為每個服務(wù)分別創(chuàng)建獨立的線程池,從而對資源進行隔離,這樣就能夠避免雪崩效應(yīng)的產(chǎn)生。
下面,我們?yōu)槿齻€服務(wù)都獨立分配 100 個線程再來看看:
圖片
調(diào)整之后,我們會發(fā)現(xiàn)就算服務(wù) A 中的 100 個線程都已經(jīng)被消耗殆盡,其它服務(wù)中的請求過程也不會受到影響,因為它們運行在獨立的線程池中。
進程隔離
講完線程隔離,我們再來看進程隔離。進程隔離把資源的隔離范圍從線程上升到進程,在 Java 世界中,相當于就是上升到了 JVM 級別。這樣,我們就可以把整個業(yè)務(wù)系統(tǒng)拆分為多個獨立的子系統(tǒng),每個子系統(tǒng)完成自身的請求,而不依賴于其它子系統(tǒng),目前主流的微服務(wù)架構(gòu)就是這一隔離機制最好的實踐。
集群隔離
集群隔離是進程隔離的升級版,將某些服務(wù)單獨部署成集群,或?qū)τ谀承┓?wù)可以進行分組集群管理,某一個集群出現(xiàn)問題之后就不會影響到其它集群,從而實現(xiàn)了故障隔離,效果圖如下所示:
圖片
讀寫隔離
讀寫隔離也是一種常見的隔離技術(shù),當用于讀取操作的服務(wù)器出現(xiàn)故障時,寫服務(wù)器照??梢赃\作,反之也是一樣。對于離線分析類的應(yīng)用場景而言,讀寫隔離可以很好地控制讀取操作瓶頸和對寫入操作的影響。
實際運用中,讀寫隔離可以有不同的表現(xiàn)形式。例如,當使用 MongoDB 進行海量數(shù)據(jù)存儲時,我們可以采用熱庫和存檔庫概念,將新寫入的數(shù)據(jù)同時存儲到熱庫和存檔庫。熱庫只存儲最近一個月的數(shù)據(jù),而存檔庫則保存所有的歷史數(shù)據(jù)。當我們想對歷史數(shù)據(jù)進行讀取處理時,可以確保熱庫不受影響。
總結(jié)
總的來說,在日常開發(fā)過程中,你可以使用本文介紹的集群容錯和服務(wù)隔離機制來為分布式系統(tǒng)提升服務(wù)訪問的可靠性。我再給你梳理一下關(guān)鍵知識點。
在分布式系統(tǒng)中,錯誤總會發(fā)生,處理不好就會形成雪崩效應(yīng),我們應(yīng)對雪崩效應(yīng)的核心思想是努力控制它的影響范圍。一方面,我們可以借助于集群機制以及 Failover、Failback、Failsafe 和 Failfast 等機制來實現(xiàn)集群容錯;另一方面,我們可以使用線程隔離、進程隔離、集群隔離以及讀寫隔離等等策略來實現(xiàn)服務(wù)隔離。針對日常場景,集群容錯一般都是主流框架所默認提供的機制。而服務(wù)隔離方面,我建議你合理利用線程隔離和進程隔離來綜合使用不同粒度的隔離機制,確保服務(wù)的高可用。