天天看點

扒一扒隔離熔斷之Hystrix VS Sentinel

作者:閃念基因

引言

為什麼需要在項目中引入Hystrix等熔斷隔離機制,其可以應用在什麼場景中?在分布式系統中,單個應用通常會有多個不同類型的外部依賴服務,内部通常依賴于各種RPC服務,外部則依賴于各種HTTP服務。這些依賴服務不可避免的會出現調用失敗,比如逾時、異常等情況,如何在外部依賴出問題的情況,仍然保證自身應用的穩定,就是Hystrix這類服務保障架構的工作了。常見的服務依賴如下圖所示,應用X依賴于服務A、B和C,A和B正常提供服務,C服務出錯,這是如何避免C服務對A、B服務産生影響,也引出了一個隔離的概念。

扒一扒隔離熔斷之Hystrix VS Sentinel

Hystrix

Hystrix [hɪst’rɪks],中文含義是豪豬,因其背上長滿棘刺,進而擁有了自我保護的能力。本文所說的Hystrix是Netflix開源的一款容錯架構,同樣具有自我保護能力。

Hystrix設計目标

•對來自依賴的延遲和故障進行防護和控制——這些依賴通常都是通過網絡通路的•阻止故障的連鎖反應•快速失敗并迅速恢複•回退并優雅降級•提供近實時的監控與告警

Hystrix遵循的設計原則

•防止任何單獨的依賴耗盡資源(線程)•過載立即切斷并快速失敗,防止排隊•盡可能提供回退以保護使用者免受故障•使用隔離技術(例如隔闆,泳道和斷路器模式)來限制任何一個依賴的影響•通過近實時的名額,監控和告警,確定故障被及時發現•通過動态修改配置屬性,確定故障及時恢複•防止整個依賴用戶端執行失敗,而不僅僅是網絡通信

主要流程

•使用指令模式将所有對外部服務(或依賴關系)的調用包裝在HystrixCommand或HystrixObservableCommand對象中,并将該對象放在單獨的線程中執行;•每個依賴都維護着一個線程池(或信号量),線程池被耗盡則拒絕請求(而不是讓請求排隊)。•記錄請求成功,失敗,逾時和線程拒絕。•服務錯誤百分比超過了門檻值,熔斷器開關自動打開,一段時間内停止對該服務的所有請求。•請求失敗,被拒絕,逾時或熔斷時執行降級邏輯。•近實時地監控名額和配置的修改。

扒一扒隔離熔斷之Hystrix VS Sentinel

Command對象封裝請求

類結構:

扒一扒隔離熔斷之Hystrix VS Sentinel

執行指令方式:

有4種方式可以執行一個Hystrix指令。

execute()和queue() 适用于HystrixCommand對象,而observe()和toObservable()适用于HystrixObservableCommand對象。

•execute()—該方法是阻塞的,從依賴請求中接收到單個響應(或者出錯時抛出異常)。•queue()—從依賴請求中傳回一個包含單個響應的Future對象。•observe()—訂閱一個從依賴請求中傳回的代表響應的Observable對象。•toObservable()—傳回一個Observable對象,隻有當你訂閱它時,它才會執行Hystrix指令并發射響應。

扒一扒隔離熔斷之Hystrix VS Sentinel

核心代碼AbstractCommand:

public Observable<R> toObservable() {
 ...
 final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
 @Override
 public Observable<R> call() {
 if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
 return Observable.never();
 }
 return applyHystrixSemantics(_cmd);//1.關鍵步驟,指令處理
 }
 };
 ...

private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
 ...
 if (circuitBreaker.attemptExecution()) {//1.【斷路器相關處理】,之後HystrixCircuitBreaker中展示
 ..
 if (executionSemaphore.tryAcquire()) {//2.擷取信号量,如果是THREAD線程池政策,【直接傳回true】,這裡需要注意,不然流程将進行不下去
 try {
 executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
 return executeCommandAndObserve(_cmd)//3.核心執行方法
 .doOnError(markExceptionThrown)
 .doOnTerminate(singleSemaphoreRelease)
 .doOnUnsubscribe(singleSemaphoreRelease);
 } ...
}           

斷路器實作邏輯

下面的圖展示了HystrixCommand和HystrixObservableCommand如何與HystrixCircuitBroker進行互動。

扒一扒隔離熔斷之Hystrix VS Sentinel

回路器打開和關閉有如下幾種情況:

•假設回路中的請求滿足了一定的門檻值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())•假設錯誤發生的百分比超過了設定的錯誤發生的門檻值HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()•回路器狀态由CLOSE變換成OPEN•如果回路器打開,所有的請求都會被回路器所熔斷。•一定時間之後HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),下一個的請求會被通過(處于半打開狀态),如果該請求執行失敗,回路器會在睡眠視窗期間傳回OPEN,如果請求成功,回路器會被置為關閉狀态,重新開啟1步驟的邏輯。

Hystrix 斷路器狀态:

熔斷器有三個狀态 CLOSED、 OPEN、HALF_OPEN 熔斷器預設關閉狀态,當觸發熔斷後狀态變更為 OPEN,在等待到指定的時間,Hystrix會放請求檢測服務是否開啟,這期間熔斷器會變為HALF_OPEN 半開啟狀态,熔斷探測服務可用則繼續變更為 CLOSED關閉熔斷器。

扒一扒隔離熔斷之Hystrix VS Sentinel

斷路器實作類:

扒一扒隔離熔斷之Hystrix VS Sentinel

核心代碼:

public boolean allowRequest() {

if (properties.circuitBreakerForceOpen().get()) {

// properties have asked us to force the circuit open so we will allow NO requests

return false;

}

if (properties.circuitBreakerForceClosed().get()) {

// we still want to allow isOpen() to perform it's calculations so we simulate normal behavior

isOpen();

// properties have asked us to ignore errors so we will ignore the results of isOpen and just allow all traffic through

return true;

}

return !isOpen() || allowSingleTest();

}

這裡代碼判斷邏輯

1.判斷是否強制開啟熔斷器,是則return false,command不能執行 2.判斷是否強制關閉熔斷器,是則return true, command可執行 3.判斷熔斷器是否開啟 circuitOpened.get() == -1表示沒有開啟,則return true,command可執行。4.到這步證明已經開啟了熔斷器,那麼判斷是否可嘗試請求,如果可以同時會把熔斷器的狀态改為HALF_OPEN

熔斷參數:

扒一扒隔離熔斷之Hystrix VS Sentinel

隔離:

Hystrix采用艙壁模式來隔離互相之間的依賴關系,并限制對其中任何一個的并發通路。

扒一扒隔離熔斷之Hystrix VS Sentinel

隔離方式:

•線程池隔離 請求并發量大,并且耗時長(一般是計算量大或者讀資料庫):采用線程池隔離,這樣的話,可以保證大量的容器線程可用,不會由于服務原因,一直處于阻塞或者等待狀态,快速失敗傳回。•信号量隔離請求并發量大,并且耗時短(一般是計算量小,或讀緩存):采用信号量隔離:因為這類服務的傳回往往非常快,不會占用容器線程太長時間,并且減少了線程切換的一些開銷,提高了緩存服務的效率

線程池信号量線程請求線程和調用provider線程不是同一條線程請求線程和調用provider線程是同一條線程開銷排隊、排程、上下文切換等無線程切換,開銷低異步支援不支援并發支援支援:最大線程池大小支援:最大信号量上限傳遞Header不支援支援支援逾時支援不支援

扒一扒隔離熔斷之Hystrix VS Sentinel
扒一扒隔離熔斷之Hystrix VS Sentinel

逾時器實作

HystrixCommand裡有個 TimedOutStatus 逾時狀态

扒一扒隔離熔斷之Hystrix VS Sentinel

實作流程:

有兩個線程,一個是hystrixCommand任務執行線程,一個是等着給hystrixCommand判定逾時的線程,現在兩個線程看誰能先把hystrixCommand的狀态置換,隻要任何一個線程對hystrixCommand打上标就意味着逾時判定結束。

扒一扒隔離熔斷之Hystrix VS Sentinel

逾時器實作類

扒一扒隔離熔斷之Hystrix VS Sentinel

HystrixObservableTimeoutOperator.call(),TimerListener的實作

TimerListener listener = new TimerListener() {

 @Override
 public void tick() {

 if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) {
 // 标記事件,可以認為是開的hook,這裡暫忽略
 originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey);

 //取消原Obserable的訂閱
 s.unsubscribe();

 final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, hystrixRequestContext, new Runnable() {

 @Override
 public void run() {
 child.onError(new HystrixTimeoutException());
 }
 });
 timeoutRunnable.run();
 }
 }

 //擷取配置的逾時時間配置
 @Override
 public int getIntervalTimeInMilliseconds() {
 return originalCommand.properties.executionTimeoutInMilliseconds().get();
 }
 };           

應用監控

監控名額:

扒一扒隔離熔斷之Hystrix VS Sentinel

實心圓:包含兩個含義,顔色表示執行個體的健康程度,健康程度從綠色、黃色、橙色、紅色遞減;大小則根據請求流量的大小發生變化,流量越大則實心圓越大,反之則越小。

曲線:統計了2分鐘内的請求流量的變化,通過該曲線可以對流量進行上升和下降的趨勢分析。

實作邏輯:

訂閱了執行的完成事件後會把執行結果彙總到HystrixThreadEventStream。顧名思義就是一個事件流。

接下來的操作也比較容易猜到,我們需要一個訂閱者來訂閱這個事件來進行彙總。最終會把處理的結果寫入HystrixThreadPoolCompletionStream和HystrixThreadPoolCompletionStream這兩個流裡面。

扒一扒隔離熔斷之Hystrix VS Sentinel

統計實作:HealthCountsStream(訂閱者)

處理的結果會寫到HystrixThreadPoolCompletionStream和HystrixThreadPoolCompletionStream。最核心的統計實作邏輯HealthCountsStream。

扒一扒隔離熔斷之Hystrix VS Sentinel

滑行視窗:

扒一扒隔離熔斷之Hystrix VS Sentinel

類圖:

扒一扒隔離熔斷之Hystrix VS Sentinel

核心代碼:

protected BucketedRollingCounterStream(HystrixEventStream<Event> stream, final int numBuckets, int bucketSizeInMs,
 final Func2<Bucket, Event, Bucket> appendRawEventToBucket,
 final Func2<Output, Bucket, Output> reduceBucket) {
 super(stream, numBuckets, bucketSizeInMs, appendRawEventToBucket);
 Func1<Observable<Bucket>, Observable<Output>> reduceWindowToSummary = new Func1<Observable<Bucket>, Observable<Output>>() {
 @Override
 public Observable<Output> call(Observable<Bucket> window) {
 return window.scan(getEmptyOutputValue(), reduceBucket).skip(numBuckets);
 }
 };
 this.sourceStream = bucketedStream //stream broken up into buckets
 .window(numBuckets, 1) //emit overlapping windows of buckets
 .flatMap(reduceWindowToSummary) //convert a window of bucket-summaries into a single summary
 .doOnSubscribe(new Action0() {
 @Override
 public void call() {
 isSourceCurrentlySubscribed.set(true);
 }
 })
 .doOnUnsubscribe(new Action0() {
 @Override
 public void call() {
 isSourceCurrentlySubscribed.set(false);
 }
 })
 .share() //multiple subscribers should get same data
 .onBackpressureDrop(); //if there are slow consumers, data should not buffer
 }           

環形數組資料結構:

扒一扒隔離熔斷之Hystrix VS Sentinel

資料結構類:

class ListState {

/*

* 這裡的data之是以用AtomicReferenceArray而不是普通數組,是因為data需要

* 在不同的ListState對象中跨線程來引用,需要可見性和并發性的保證。

*/

private final AtomicReferenceArray<Bucket> data;

private final int size;

private final int tail;

private final int head;

private ListState(AtomicReferenceArray<Bucket> data, int head, int tail) {

this.head = head;

this.tail = tail;

if (head == 0 && tail == 0) {

size = 0;

} else {

this.size = (tail + dataLength - head) % dataLength;

}

this.data = data;

}

}

Sentinel

Sentinel(哨兵) 是阿裡中間件團隊開源的,面向分布式服務架構的輕量級高可用流量控制元件,主要以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個次元來幫助使用者保護服務的穩定性。

扒一扒隔離熔斷之Hystrix VS Sentinel

Hystrix vs Sentinel

Hystrix 的關注點在于以隔離和熔斷為主的容錯機制,逾時或被熔斷的調用将會快速失敗,并可以提供 fallback 機制。

Sentinel 的側重點在于:

•多樣化的流量控制•熔斷降級•系統負載保護•實時監控和控制台

兩者解決的問題還是有比較大的不同的。

扒一扒隔離熔斷之Hystrix VS Sentinel

資源模型和執行模型上的對比

Sentinel 提供多樣化的規則配置方式。除了直接通過 loadRules API 将規則注冊到記憶體态之外,使用者還可以注冊各種外部資料源來提供動态的規則。使用者可以根據系統目前的實時情況去動态地變更規則配置,資料源會将變更推送至 Sentinel 并即時生效

扒一扒隔離熔斷之Hystrix VS Sentinel

隔離設計上的對比

線程池隔離會讓機器資源碎片化。

線程池模式比較徹底的隔離性使得 Hystrix 可以針對不同資源線程池的排隊、逾時情況分别進行處理,但這其實是逾時熔斷和流量控制要解決的問題,如果元件具備了逾時熔斷和流量控制的能力,線程池隔離就顯得沒有那麼必要了。

Hystrix 的信号量隔離overhead 比較小,但是效果不錯。但缺點是無法對慢調用自動進行降級,隻能等待用戶端自己逾時,是以仍然可能會出現級聯阻塞的情況。

Sentinel 可以通過并發線程數模式的流量控制來提供信号量隔離的功能。并且結合基于響應時間的熔斷降級模式,可以在不穩定資源的平均響應時間比較高的時候自動降級,防止過多的慢調用占滿并發數,影響整個系統。

扒一扒隔離熔斷之Hystrix VS Sentinel

熔斷降級的對比

Sentinel 與 Hystrix 都支援基于失敗比率(異常比率)的熔斷降級

Sentinel 還支援基于平均響應時間的熔斷降級,可以在服務響應時間持續飙高的時候自動熔斷,拒絕掉更多的請求,直到一段時間後才恢複。這樣可以防止調用非常慢造成級聯阻塞的情況。

•降級判斷标準•平均響應時間•異常比例•異常數•系統保護規則 (SystemRule):系統負載保護:Sentinel 對系統的次元提供保護,負載保護算法借鑒了 TCP BBR 的思想,讓系統的入口流量和系統的負載達到一個平衡,保證系統在能力範圍之内處理最多的請求。

扒一扒隔離熔斷之Hystrix VS Sentinel

Sentinel控制台界面:

扒一扒隔離熔斷之Hystrix VS Sentinel

Sentinel之流量控制

Sentinel 的「設計理念」是讓編碼人員自由選擇控制流量的角度,并進行靈活組合,進而達到想要的效果。

我們可以通過以下的幾個角度實作流量控制:

•資源的調用關系:根據調用方限流 根據調用鍊路入口限流-鍊路限流 具有關系的資源流量控制-關聯流量控制•運作名額:例如 QPS、線程池、系統負載等;•控制的效果:例如直接限流、冷啟動、排隊等。

Sentinel之流量整形

Sentinel 支援多樣化的流量整形政策,

在 QPS 過高的時候可以自動将流量調整成合适的形狀。常用的有:

•直接拒絕模式:即超出的請求直接拒絕。•慢啟動預熱模式:當流量激增的時候,控制流量通過的速率,讓通過的流量緩慢增加,在一定時間内逐漸增加到門檻值上限,給冷系統一個預熱的時間,避免冷系統被壓垮。•勻速器模式:利用 Leaky Bucket 算法實作的勻速模式,嚴格控制了請求通過的時間間隔,同時堆積的請求将會排隊,超過逾時時長的請求直接被拒絕。Sentinel 還支援基于調用關系的限流,包括基于調用方限流、基于調用鍊入口限流、關聯流量限流等,依托于 Sentinel 強大的調用鍊路統計資訊,可以提供精準的不同次元的限流。

扒一扒隔離熔斷之Hystrix VS Sentinel

實時名額統計實作的對比

Hystrix 1.5 之前的版本是通過環形數組實作的滑動視窗,通過鎖配合 CAS 的操作對每個桶的統計資訊進行更新。

Hystrix 1.5 開始對實時名額統計的實作進行了重構,将名額統計資料結構抽象成了響應式流(reactive stream)的形式,友善消費者去利用名額資訊。同時底層改造成了基于 RxJava 的事件驅動模式,在服務調用成功/失敗/逾時的時候釋出相應的事件,通過一系列的變換和聚合最終得到實時的名額統計資料流,可以被熔斷器或 Dashboard 消費。

Sentinel 目前抽象出了 Metric 名額統計接口,底層可以有不同的實作,目前預設的實作是基于 LeapArray 的滑動視窗,後續根據需要可能會引入 reactive stream 等實作。

扒一扒隔離熔斷之Hystrix VS Sentinel
扒一扒隔離熔斷之Hystrix VS Sentinel
扒一扒隔離熔斷之Hystrix VS Sentinel

比對彙總

比較項 Sentinel Hystrix 說明
隔離政策 信号量隔離(并發線程數限流)(模拟信号量) 線程池隔離/信号量隔離 Sentinel不建立線程依賴tomcat或jetty容器的線程池,存在的問題就是運作容器的線程數量限制了sentinel設定值的上限可能設定不準。比如tomcat線程池為10,sentinel設定100是沒有意義的,同時隔離性不好 hystrix使用自己建立的線程池,隔離性會更好
熔斷降級政策 基于響應時間、異常比率、異常數 基于異常比率 快速失敗的本質功能
實時統計實作 滑動視窗(LeapArray) 滑動視窗(基于 RxJava)
動态規則配置 支援多種資料源 支援多種資料源
擴充性 多個擴充點 插件的形式
注解 支援 支援
限流 基于 QPS,支援基于調用關系的限流 有限的支援(并發線程數或信号量大小) 快速失敗的本質功能
流量整形 支援預熱模式、勻速器模式、預熱排隊模式 不支援(排隊)
系統自适應保護 支援(僅對linux/unix生效) 不支援 設定一個伺服器最大允許處理量的門檻值
控制台 提供開箱即用的控制台,可配置規則、檢視秒級監控、機器發現等 簡單的監控檢視 接近實時資料 控制台是非常有競争力的功能,因為能集中配置限 制資料更友善,但是展示資料和實時性沒有hystrix 直覺。
配置持久化 ZooKeeper, Apollo, Nacos、本地檔案 Git/svn/本地檔案 Sentinel用戶端采用直接連結持久化存儲,應用客戶 端引用了更多的依賴,同樣的存儲連結可能有多個 配置
動态配置 支援 支援
黑白名單 支援 不支援
springcloud內建 非常高 Spring boot使用hystrix內建度更高
整體優勢 集中配置設定及監控+更細的控制規則 漂亮的界面+接近實時的統計結果 docker容器化部署之後sentinel可能更會發揮作用

作者:李彩雲

來源-微信公衆号:到家交易平台技術

出處:https://mp.weixin.qq.com/s/TiuplYZBjV5u7h17G7fqhw

繼續閱讀