分布式服務-防雪崩利器Hystrix(熔斷器)

前言

分布式系統中經常會出現某個基礎服務不可用造成整個系統不可用的情況, 這種現象被稱為服務雪崩效應. 為了應對服務雪崩, 一種常見的做法是手動服務降級. 而Hystrix的出現,給我們提供了另一種選擇.

服務雪崩效應的定義

服務雪崩效應是一種因服務提供者的不可用導致服務調用者的不可用,并將不可用逐漸放大的過程.如果所示:

上圖中, A為服務提供者, B為A的服務調用者, C和D是B的服務調用者. 當A的不可用,引起B的不可用,并將不可用逐漸放大C和D時, 服務雪崩就形成了.

服務雪崩效應形成的原因

我把服務雪崩的參與者簡化為服務提供者服務調用者, 并將服務雪崩產生的過程分為以下三個階段來分析形成的原因:

服務提供者不可用

重試加大流量

服務調用者不可用

服務雪崩的每個階段都可能由不同的原因造成, 比如造成服務不可用的原因有:

硬件故障

程序Bug

緩存擊穿

用戶大量請求

硬件故障可能為硬件損壞造成的服務器主機宕機, 網絡硬件故障造成的服務提供者的不可訪問.

緩存擊穿一般發生在緩存應用重啟, 所有緩存被清空時,以及短時間內大量緩存失效時. 大量的緩存不命中, 使請求直擊后端,造成服務提供者超負荷運行,引起服務不可用.

在秒殺和大促開始前,如果準備不充分,用戶發起大量請求也會造成服務提供者的不可用.

而形成重試加大流量的原因有:

用戶重試

代碼邏輯重試

在服務提供者不可用后, 用戶由于忍受不了界面上長時間的等待,而不斷刷新頁面甚至提交表單.

服務調用端的會存在大量服務異常后的重試邏輯.

這些重試都會進一步加大請求流量.

最后,服務調用者不可用產生的主要原因是:

同步等待造成的資源耗盡

當服務調用者使用同步調用時, 會產生大量的等待線程占用系統資源. 一旦線程資源被耗盡,服務調用者提供的服務也將處于不可用狀態, 于是服務雪崩效應產生了.

服務雪崩的應對策略

針對造成服務雪崩的不同原因, 可以使用不同的應對策略:

流量控制

改進緩存模式

服務自動擴容

服務調用者降級服務

流量控制的具體措施包括:

網關限流

用戶交互限流

關閉重試

因為Nginx的高性能, 目前一線互聯網公司大量采用Nginx+Lua的網關進行流量控制, 由此而來的OpenResty也越來越熱門.

用戶交互限流的具體措施有: 1. 采用加載動畫,提高用戶的忍耐等待時間. 2. 提交按鈕添加強制等待時間機制.

改進緩存模式的措施包括:

緩存預加載

同步改為異步刷新

服務自動擴容的措施主要有:

AWS的auto scaling

服務調用者降級服務的措施包括:

資源隔離

對依賴服務進行分類

不可用服務的調用快速失敗

資源隔離主要是對調用服務的線程池進行隔離.

我們根據具體業務,將依賴服務分為: 強依賴和若依賴. 強依賴服務不可用會導致當前業務中止,而弱依賴服務的不可用不會導致當前業務的中止.

不可用服務的調用快速失敗一般通過超時機制,熔斷器和熔斷后的降級方法來實現.

使用Hystrix預防服務雪崩

Hystrix[h?st'r?ks]的中文含義是豪豬, 因其背上長滿了刺,而擁有自我保護能力. Netflix的Hystrix是一個幫助解決分布式系統交互時超時處理和容錯的類庫, 它同樣擁有保護系統的能力.

Hystrix的設計原則包括:

資源隔離

熔斷器

命令模式

資源隔離

貨船為了進行防止漏水和火災的擴散,會將貨倉分隔為多個, 如下圖所示:

這種資源隔離減少風險的方式被稱為:Bulkheads(艙壁隔離模式).

Hystrix將同樣的模式運用到了服務調用者上.

在一個高度服務化的系統中,我們實現的一個業務邏輯通常會依賴多個服務,比如:

商品詳情展示服務會依賴商品服務, 價格服務, 商品評論服務. 如圖所示:

調用三個依賴服務會共享商品詳情服務的線程池. 如果其中的商品評論服務不可用, 就會出現線程池里所有線程都因等待響應而被阻塞, 從而造成服務雪崩. 如圖所示:

Hystrix通過將每個依賴服務分配獨立的線程池進行資源隔離, 從而避免服務雪崩.

如下圖所示, 當商品評論服務不可用時, 即使商品服務獨立分配的20個線程全部處于同步等待狀態,也不會影響其他依賴服務的調用.

熔斷器模式

熔斷器模式定義了熔斷器開關相互轉換的邏輯:

服務的健康狀況 = 請求失敗數 / 請求總數.

熔斷器開關由關閉到打開的狀態轉換是通過當前服務健康狀況和設定閾值比較決定的.

當熔斷器開關關閉時, 請求被允許通過熔斷器. 如果當前健康狀況高于設定閾值, 開關繼續保持關閉. 如果當前健康狀況低于設定閾值, 開關則切換為打開狀態.

當熔斷器開關打開時, 請求被禁止通過.

當熔斷器開關處于打開狀態, 經過一段時間后, 熔斷器會自動進入半開狀態, 這時熔斷器只允許一個請求通過. 當該請求調用成功時, 熔斷器恢復到關閉狀態. 若該請求失敗, 熔斷器繼續保持打開狀態, 接下來的請求被禁止通過.

熔斷器的開關能保證服務調用者在調用異常服務時, 快速返回結果, 避免大量的同步等待. 并且熔斷器能在一段時間后繼續偵測請求執行結果, 提供恢復服務調用的可能.

命令模式

Hystrix使用命令模式(繼承HystrixCommand類)來包裹具體的服務調用邏輯(run方法), 并在命令模式中添加了服務調用失敗后的降級邏輯(getFallback).

同時我們在Command的構造方法中可以定義當前服務線程池和熔斷器的相關參數. 如下代碼所示:

publicclassService1HystrixCommandextendsHystrixCommand{privateService1 service;privateRequest request;publicService1HystrixCommand(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))//服務線程池數量.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()? ? ? ? ? ? .withCircuitBreakerErrorThresholdPercentage(60)//熔斷器關閉到打開閾值.withCircuitBreakerSleepWindowInMilliseconds(3000)//熔斷器打開到關閉的時間窗長度))this.service = service;this.request = request;? ? );? }@OverrideprotectedResponserun(){returnservice1.call(request);? }@OverrideprotectedResponsegetFallback(){returnResponse.dummy();? }}

在使用了Command模式構建了服務對象之后, 服務便擁有了熔斷器和線程池的功能.

Hystrix的內部處理邏輯

下圖為Hystrix服務調用的內部邏輯:

構建Hystrix的Command對象, 調用執行方法.

Hystrix檢查當前服務的熔斷器開關是否開啟, 若開啟, 則執行降級服務getFallback方法.

若熔斷器開關關閉, 則Hystrix檢查當前服務的線程池是否能接收新的請求, 若超過線程池已滿, 則執行降級服務getFallback方法.

若線程池接受請求, 則Hystrix開始執行服務調用具體邏輯run方法.

若服務執行失敗, 則執行降級服務getFallback方法, 并將執行結果上報Metrics更新服務健康狀況.

若服務執行超時, 則執行降級服務getFallback方法, 并將執行結果上報Metrics更新服務健康狀況.

若服務執行成功, 返回正常結果.

若服務降級方法getFallback執行成功, 則返回降級結果.

若服務降級方法getFallback執行失敗, 則拋出異常.

Hystrix Metrics的實現

Hystrix的Metrics中保存了當前服務的健康狀況, 包括服務調用總次數和服務調用失敗次數等. 根據Metrics的計數, 熔斷器從而能計算出當前服務的調用失敗率, 用來和設定的閾值比較從而決定熔斷器的狀態切換邏輯. 因此Metrics的實現非常重要.

1.4之前的滑動窗口實現

Hystrix在這些版本中的使用自己定義的滑動窗口數據結構來記錄當前時間窗的各種事件(成功,失敗,超時,線程池拒絕等)的計數.

事件產生時, 數據結構根據當前時間確定使用舊桶還是創建新桶來計數, 并在桶中對計數器經行修改.

這些修改是多線程并發執行的, 代碼中有不少加鎖操作,邏輯較為復雜.

1.5之后的滑動窗口實現

Hystrix在這些版本中開始使用RxJava的Observable.window()實現滑動窗口.

RxJava的window使用后臺線程創建新桶, 避免了并發創建桶的問題.

同時RxJava的單線程無鎖特性也保證了計數變更時的線程安全. 從而使代碼更加簡潔.

以下為我使用RxJava的window方法實現的一個簡易滑動窗口Metrics, 短短幾行代碼便能完成統計功能,足以證明RxJava的強大:

@TestpublicvoidtimeWindowTest()throwsException{? Observable source = Observable.interval(50, TimeUnit.MILLISECONDS).map(i -> RandomUtils.nextInt(2));? source.window(1, TimeUnit.SECONDS).subscribe(window -> {int[] metrics =newint[2];? ? window.subscribe(i -> metrics[i]++,? ? ? InternalObservableUtils.ERROR_NOT_IMPLEMENTED,? ? ? () -> System.out.println("窗口Metrics:"+ JSON.toJSONString(metrics)));? });? TimeUnit.SECONDS.sleep(3);}

總結

通過使用Hystrix,我們能方便的防止雪崩效應, 同時使系統具有自動降級和自動恢復服務的效果.

參考資料:

https://segmentfault.com/a/1190000005988895

http://www.lxweimin.com/p/b9af028efebb

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容