分布式系統中經(jīng)常會(huì )出現某個(gè)基礎服務(wù)不可用造成整個(gè)系統不可用的情況, 這種現象被稱(chēng)為服務(wù)雪崩效應. 為了應對服務(wù)雪崩, 一種常見(jiàn)的做法是手動(dòng)服務(wù)降級. 而Hystrix的出現,給我們提供了另一種選擇.
服務(wù)雪崩效應是一種因 服務(wù)提供者 的不可用導致 服務(wù)調用者 的不可用,并將不可用 逐漸放大 的過(guò)程.如果所示:
上圖中, A為服務(wù)提供者, B為A的服務(wù)調用者, C和D是B的服務(wù)調用者. 當A的不可用,引起B的不可用,并將不可用逐漸放大C和D時(shí), 服務(wù)雪崩就形成了.
我把服務(wù)雪崩的參與者簡(jiǎn)化為 服務(wù)提供者 和 服務(wù)調用者, 并將服務(wù)雪崩產(chǎn)生的過(guò)程分為以下三個(gè)階段來(lái)分析形成的原因:
服務(wù)提供者不可用
重試加大流量
服務(wù)調用者不可用
服務(wù)雪崩的每個(gè)階段都可能由不同的原因造成, 比如造成 服務(wù)不可用 的原因有:
硬件故障
程序Bug
緩存擊穿
用戶(hù)大量請求
硬件故障可能為硬件損壞造成的服務(wù)器主機宕機, 網(wǎng)絡(luò )硬件故障造成的服務(wù)提供者的不可訪(fǎng)問(wèn).
緩存擊穿一般發(fā)生在緩存應用重啟, 所有緩存被清空時(shí),以及短時(shí)間內大量緩存失效時(shí). 大量的緩存不命中, 使請求直擊后端,造成服務(wù)提供者超負荷運行,引起服務(wù)不可用.
在秒殺和大促開(kāi)始前,如果準備不充分,用戶(hù)發(fā)起大量請求也會(huì )造成服務(wù)提供者的不可用.
而形成 重試加大流量 的原因有:
用戶(hù)重試
代碼邏輯重試
在服務(wù)提供者不可用后, 用戶(hù)由于忍受不了界面上長(cháng)時(shí)間的等待,而不斷刷新頁(yè)面甚至提交表單.
服務(wù)調用端的會(huì )存在大量服務(wù)異常后的重試邏輯.
這些重試都會(huì )進(jìn)一步加大請求流量.
最后, 服務(wù)調用者不可用 產(chǎn)生的主要原因是:
同步等待造成的資源耗盡
當服務(wù)調用者使用 同步調用 時(shí), 會(huì )產(chǎn)生大量的等待線(xiàn)程占用系統資源. 一旦線(xiàn)程資源被耗盡,服務(wù)調用者提供的服務(wù)也將處于不可用狀態(tài), 于是服務(wù)雪崩效應產(chǎn)生了.
針對造成服務(wù)雪崩的不同原因, 可以使用不同的應對策略:
流量控制
改進(jìn)緩存模式
服務(wù)自動(dòng)擴容
服務(wù)調用者降級服務(wù)
流量控制 的具體措施包括:
網(wǎng)關(guān)限流
用戶(hù)交互限流
關(guān)閉重試
因為Nginx的高性能, 目前一線(xiàn)互聯(lián)網(wǎng)公司大量采用Nginx+Lua的網(wǎng)關(guān)進(jìn)行流量控制, 由此而來(lái)的OpenResty也越來(lái)越熱門(mén).
用戶(hù)交互限流的具體措施有: 1. 采用加載動(dòng)畫(huà),提高用戶(hù)的忍耐等待時(shí)間. 2. 提交按鈕添加強制等待時(shí)間機制.
改進(jìn)緩存模式 的措施包括:
緩存預加載
同步改為異步刷新
服務(wù)自動(dòng)擴容 的措施主要有:
AWS的auto scaling
服務(wù)調用者降級服務(wù) 的措施包括:
資源隔離
對依賴(lài)服務(wù)進(jìn)行分類(lèi)
不可用服務(wù)的調用快速失敗
資源隔離主要是對調用服務(wù)的線(xiàn)程池進(jìn)行隔離.
我們根據具體業(yè)務(wù),將依賴(lài)服務(wù)分為: 強依賴(lài)和若依賴(lài). 強依賴(lài)服務(wù)不可用會(huì )導致當前業(yè)務(wù)中止,而弱依賴(lài)服務(wù)的不可用不會(huì )導致當前業(yè)務(wù)的中止.
不可用服務(wù)的調用快速失敗一般通過(guò) 超時(shí)機制, 熔斷器 和熔斷后的 降級方法 來(lái)實(shí)現.
Hystrix [h?st'r?ks]的中文含義是豪豬, 因其背上長(cháng)滿(mǎn)了刺,而擁有自我保護能力. Netflix的 Hystrix 是一個(gè)幫助解決分布式系統交互時(shí)超時(shí)處理和容錯的類(lèi)庫, 它同樣擁有保護系統的能力.
Hystrix的設計原則包括:
資源隔離
熔斷器
命令模式
貨船為了進(jìn)行防止漏水和火災的擴散,會(huì )將貨倉分隔為多個(gè), 如下圖所示:
這種資源隔離減少風(fēng)險的方式被稱(chēng)為:Bulkheads(艙壁隔離模式).
Hystrix將同樣的模式運用到了服務(wù)調用者上.
在一個(gè)高度服務(wù)化的系統中,我們實(shí)現的一個(gè)業(yè)務(wù)邏輯通常會(huì )依賴(lài)多個(gè)服務(wù),比如:
商品詳情展示服務(wù)會(huì )依賴(lài)商品服務(wù), 價(jià)格服務(wù), 商品評論服務(wù). 如圖所示:
調用三個(gè)依賴(lài)服務(wù)會(huì )共享商品詳情服務(wù)的線(xiàn)程池. 如果其中的商品評論服務(wù)不可用, 就會(huì )出現線(xiàn)程池里所有線(xiàn)程都因等待響應而被阻塞, 從而造成服務(wù)雪崩. 如圖所示:
Hystrix通過(guò)將每個(gè)依賴(lài)服務(wù)分配獨立的線(xiàn)程池進(jìn)行資源隔離, 從而避免服務(wù)雪崩.
如下圖所示, 當商品評論服務(wù)不可用時(shí), 即使商品服務(wù)獨立分配的20個(gè)線(xiàn)程全部處于同步等待狀態(tài),也不會(huì )影響其他依賴(lài)服務(wù)的調用.

熔斷器模式定義了熔斷器開(kāi)關(guān)相互轉換的邏輯:

服務(wù)的健康狀況 = 請求失敗數 / 請求總數.
熔斷器開(kāi)關(guān)由關(guān)閉到打開(kāi)的狀態(tài)轉換是通過(guò)當前服務(wù)健康狀況和設定閾值比較決定的.
當熔斷器開(kāi)關(guān)關(guān)閉時(shí), 請求被允許通過(guò)熔斷器. 如果當前健康狀況高于設定閾值, 開(kāi)關(guān)繼續保持關(guān)閉. 如果當前健康狀況低于設定閾值, 開(kāi)關(guān)則切換為打開(kāi)狀態(tài).
當熔斷器開(kāi)關(guān)打開(kāi)時(shí), 請求被禁止通過(guò).
當熔斷器開(kāi)關(guān)處于打開(kāi)狀態(tài), 經(jīng)過(guò)一段時(shí)間后, 熔斷器會(huì )自動(dòng)進(jìn)入半開(kāi)狀態(tài), 這時(shí)熔斷器只允許一個(gè)請求通過(guò). 當該請求調用成功時(shí), 熔斷器恢復到關(guān)閉狀態(tài). 若該請求失敗, 熔斷器繼續保持打開(kāi)狀態(tài), 接下來(lái)的請求被禁止通過(guò).
熔斷器的開(kāi)關(guān)能保證服務(wù)調用者在調用異常服務(wù)時(shí), 快速返回結果, 避免大量的同步等待. 并且熔斷器能在一段時(shí)間后繼續偵測請求執行結果, 提供恢復服務(wù)調用的可能.
Hystrix使用命令模式(繼承HystrixCommand類(lèi))來(lái)包裹具體的服務(wù)調用邏輯(run方法), 并在命令模式中添加了服務(wù)調用失敗后的降級邏輯(getFallback).
同時(shí)我們在Command的構造方法中可以定義當前服務(wù)線(xiàn)程池和熔斷器的相關(guān)參數. 如下代碼所示:
public class Service1HystrixCommand extends HystrixCommand<Response> { private Service1 service; private Request request; public Service1HystrixCommand(Service1 service, Request request){ supper( Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ServiceGroup")) .andCommandKey(HystrixCommandKey.Factory.asKey("servcie1query")) .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("service1ThreadPool")) .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() .withCoreSize(20))//服務(wù)線(xiàn)程池數量 .andCommandPropertiesDefaults(HystrixCommandProperties.Setter() .withCircuitBreakerErrorThresholdPercentage(60)//熔斷器關(guān)閉到打開(kāi)閾值 .withCircuitBreakerSleepWindowInMilliseconds(3000)//熔斷器打開(kāi)到關(guān)閉的時(shí)間窗長(cháng)度 )) this.service = service; this.request = request; ); } @Override protected Response run(){ return service1.call(request); } @Override protected Response getFallback(){ return Response.dummy(); }}在使用了Command模式構建了服務(wù)對象之后, 服務(wù)便擁有了熔斷器和線(xiàn)程池的功能.

下圖為Hystrix服務(wù)調用的內部邏輯:

構建Hystrix的Command對象, 調用執行方法.
Hystrix檢查當前服務(wù)的熔斷器開(kāi)關(guān)是否開(kāi)啟, 若開(kāi)啟, 則執行降級服務(wù)getFallback方法.
若熔斷器開(kāi)關(guān)關(guān)閉, 則Hystrix檢查當前服務(wù)的線(xiàn)程池是否能接收新的請求, 若超過(guò)線(xiàn)程池已滿(mǎn), 則執行降級服務(wù)getFallback方法.
若線(xiàn)程池接受請求, 則Hystrix開(kāi)始執行服務(wù)調用具體邏輯run方法.
若服務(wù)執行失敗, 則執行降級服務(wù)getFallback方法, 并將執行結果上報Metrics更新服務(wù)健康狀況.
若服務(wù)執行超時(shí), 則執行降級服務(wù)getFallback方法, 并將執行結果上報Metrics更新服務(wù)健康狀況.
若服務(wù)執行成功, 返回正常結果.
若服務(wù)降級方法getFallback執行成功, 則返回降級結果.
若服務(wù)降級方法getFallback執行失敗, 則拋出異常.
Hystrix的Metrics中保存了當前服務(wù)的健康狀況, 包括服務(wù)調用總次數和服務(wù)調用失敗次數等. 根據Metrics的計數, 熔斷器從而能計算出當前服務(wù)的調用失敗率, 用來(lái)和設定的閾值比較從而決定熔斷器的狀態(tài)切換邏輯. 因此Metrics的實(shí)現非常重要.
Hystrix在這些版本中的使用自己定義的滑動(dòng)窗口數據結構來(lái)記錄當前時(shí)間窗的各種事件(成功,失敗,超時(shí),線(xiàn)程池拒絕等)的計數.
事件產(chǎn)生時(shí), 數據結構根據當前時(shí)間確定使用舊桶還是創(chuàng )建新桶來(lái)計數, 并在桶中對計數器經(jīng)行修改.
這些修改是多線(xiàn)程并發(fā)執行的, 代碼中有不少加鎖操作,邏輯較為復雜.

Hystrix在這些版本中開(kāi)始使用RxJava的Observable.window()實(shí)現滑動(dòng)窗口.
RxJava的window使用后臺線(xiàn)程創(chuàng )建新桶, 避免了并發(fā)創(chuàng )建桶的問(wèn)題.
同時(shí)RxJava的單線(xiàn)程無(wú)鎖特性也保證了計數變更時(shí)的線(xiàn)程安全. 從而使代碼更加簡(jiǎn)潔.
以下為我使用RxJava的window方法實(shí)現的一個(gè)簡(jiǎn)易滑動(dòng)窗口Metrics, 短短幾行代碼便能完成統計功能,足以證明RxJava的強大:
@Testpublic void timeWindowTest() throws Exception{ Observable<Integer> source = Observable.interval(50, TimeUnit.MILLISECONDS).map(i -> RandomUtils.nextInt(2)); source.window(1, TimeUnit.SECONDS).subscribe(window -> { int[] metrics = new int[2]; window.subscribe(i -> metrics[i]++, InternalObservableUtils.ERROR_NOT_IMPLEMENTED, () -> System.out.println("窗口Metrics:" + JSON.toJSONString(metrics))); }); TimeUnit.SECONDS.sleep(3);}通過(guò)使用Hystrix,我們能方便的防止雪崩效應, 同時(shí)使系統具有自動(dòng)降級和自動(dòng)恢復服務(wù)的效果.
聯(lián)系客服