日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring cloud——Hystrix 原理解析

發布時間:2023/12/4 javascript 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring cloud——Hystrix 原理解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1、背景

分布式系統環境下,服務間類似依賴非常常見,一個業務調用通常依賴多個基礎服務。如下圖,對于同步調用,當庫存服務不可用時,商品服務請求線程被阻塞,當有大批量請求調用庫存服務時,最終可能導致整個商品服務資源耗盡,無法繼續對外提供服務。并且這種不可用可能沿請求調用鏈向上傳遞,這種現象被稱為雪崩效應。

1.1 雪崩效應常見場景

  • 硬件故障:如服務器宕機,機房斷電,光纖被挖斷等。
  • 流量激增:如異常流量,重試加大流量等。
  • 緩存穿透:一般發生在應用重啟,所有緩存失效時,以及短時間內大量緩存失效時。大量的緩存不命中,使請求直擊后端服務,造成服務提供者超負荷運行,引起服務不可用。
  • 程序BUG:如程序邏輯導致內存泄漏,JVM長時間FullGC等。
  • 同步等待:服務間采用同步調用模式,同步等待造成的資源耗盡。

1.2 雪崩效應應對策略

針對造成雪崩效應的不同場景,可以使用不同的應對策略,沒有一種通用所有場景的策略,參考如下:

  • 硬件故障:多機房容災、異地多活等。
  • 流量激增:服務自動擴容、流量控制(限流、關閉重試)等。
  • 緩存穿透:緩存預加載、緩存異步加載等。
  • 程序BUG:修改程序bug、及時釋放資源等。
  • 同步等待:資源隔離、MQ解耦、不可用服務調用快速失敗等。資源隔離通常指不同服務調用采用不同的線程池;不可用服務調用快速失敗一般通過熔斷器模式結合超時機制實現。

綜上所述,如果一個應用不能對來自依賴的故障進行隔離,那該應用本身就處在被拖垮的風險中。 因此,為了構建穩定、可靠的分布式系統,我們的服務應當具有自我保護能力,當依賴服務不可用時,當前服務啟動自我保護功能,從而避免發生雪崩效應。本文將重點介紹使用Hystrix解決同步等待的雪崩問題。

2、初探Hystrix

Hystrix [h?st’r?ks],中文含義是豪豬,因其背上長滿棘刺,從而擁有了自我保護的能力。本文所說的Hystrix是Netflix開源的一款容錯框架,同樣具有自我保護能力。為了實現容錯和自我保護,下面我們看看Hystrix如何設計和實現的。

Hystrix設計目標:

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

Hystrix遵循的設計原則:

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

Hystrix如何實現這些設計目標?

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

2.1 Hystrix入門

2.1.1 Hystrix簡單示例

開始深入Hystrix原理之前,我們先簡單看一個示例。

第一步,繼承HystrixCommand實現自己的command,在command的構造方法中需要配置請求被執行需要的參數,并組合實際發送請求的對象,代碼如下:

public class QueryOrderIdCommand extends HystrixCommand<Integer> {private final static Logger logger = LoggerFactory.getLogger(QueryOrderIdCommand.class);private OrderServiceProvider orderServiceProvider;public QueryOrderIdCommand(OrderServiceProvider orderServiceProvider) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orderService")).andCommandKey(HystrixCommandKey.Factory.asKey("queryByOrderId")).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(10)//至少有10個請求,熔斷器才進行錯誤率的計算.withCircuitBreakerSleepWindowInMilliseconds(5000)//熔斷器中斷請求5秒后會進入半打開狀態,放部分流量過去重試.withCircuitBreakerErrorThresholdPercentage(50)//錯誤率達到50開啟熔斷保護.withExecutionTimeoutEnabled(true)).andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10)));this.orderServiceProvider = orderServiceProvider;}@Overrideprotected Integer run() {return orderServiceProvider.queryByOrderId();}@Overrideprotected Integer getFallback() {return -1;} }

第二步,調用HystrixCommand的執行方法發起實際請求。

@Test public void testQueryByOrderIdCommand() {Integer r = new QueryOrderIdCommand(orderServiceProvider).execute();logger.info("result:{}", r); }

2.1.2 Hystrix處理流程

Hystrix流程圖如下:


Hystrix整個工作流如下:

  • 構造一個 HystrixCommand或HystrixObservableCommand對象,用于封裝請求,并在構造方法配置請求被執行需要的參數;
  • 執行命令,Hystrix提供了4種執行命令的方法,后面詳述;
  • 判斷是否使用緩存響應請求,若啟用了緩存,且緩存可用,直接使用緩存響應請求。Hystrix支持請求緩存,但需要用戶自定義啟動;
  • 判斷熔斷器是否打開,如果打開,跳到第8步;
  • 判斷線程池/隊列/信號量是否已滿,已滿則跳到第8步;
  • 執行HystrixObservableCommand.construct()或HystrixCommand.run(),如果執行失敗或者超時,跳到第8步;否則,跳到第9步;
  • 統計熔斷器監控指標;
  • 走Fallback備用邏輯
  • 返回請求響應
  • 從流程圖上可知道,第5步線程池/隊列/信號量已滿時,還會執行第7步邏輯,更新熔斷器統計信息,而第6步無論成功與否,都會更新熔斷器統計信息。

    2.1.3 執行命令的幾種方法

    Hystrix提供了4種執行命令的方法,execute()和queue() 適用于HystrixCommand對象,而observe()和toObservable()適用于HystrixObservableCommand對象。

    execute()
    以同步堵塞方式執行run(),只支持接收一個值對象。hystrix會從線程池中取一個線程來執行run(),并等待返回值。

    queue()
    以異步非阻塞方式執行run(),只支持接收一個值對象。調用queue()就直接返回一個Future對象。可通過 Future.get()拿到run()的返回結果,但Future.get()是阻塞執行的。若執行成功,Future.get()返回單個返回值。當執行失敗時,如果沒有重寫fallback,Future.get()拋出異常。

    observe()
    事件注冊前執行run()/construct(),支持接收多個值對象,取決于發射源。調用observe()會返回一個hot Observable,也就是說,調用observe()自動觸發執行run()/construct(),無論是否存在訂閱者。

    如果繼承的是HystrixCommand,hystrix會從線程池中取一個線程以非阻塞方式執行run();如果繼承的是HystrixObservableCommand,將以調用線程阻塞執行construct()。

    observe()使用方法:

    • 調用observe()會返回一個Observable對象
    • 調用這個Observable對象的subscribe()方法完成事件注冊,從而獲取結果

    toObservable()
    事件注冊后執行run()/construct(),支持接收多個值對象,取決于發射源。調用toObservable()會返回一個cold Observable,也就是說,調用toObservable()不會立即觸發執行run()/construct(),必須有訂閱者訂閱Observable時才會執行。

    如果繼承的是HystrixCommand,hystrix會從線程池中取一個線程以非阻塞方式執行run(),調用線程不必等待run();如果繼承的是HystrixObservableCommand,將以調用線程堵塞執行construct(),調用線程需等待construct()執行完才能繼續往下走。

    toObservable()使用方法:

    • 調用observe()會返回一個Observable對象
    • 調用這個Observable對象的subscribe()方法完成事件注冊,從而獲取結果

    需注意的是,HystrixCommand也支持toObservable()和observe(),但是即使將HystrixCommand轉換成Observable,它也只能發射一個值對象。只有HystrixObservableCommand才支持發射多個值對象。

    幾種方法的關系

    • execute()實際是調用了queue().get()
    • queue()實際調用了toObservable().toBlocking().toFuture()
    • observe()實際調用toObservable()獲得一個cold Observable,再創建一個ReplaySubject對象訂閱Observable,將源Observable轉化為hot Observable。因此調用observe()會自動觸發執行run()/construct()。

    Hystrix總是以Observable的形式作為響應返回,不同執行命令的方法只是進行了相應的轉換。

    3、Hystrix容錯

    Hystrix的容錯主要是通過添加容許延遲和容錯方法,幫助控制這些分布式服務之間的交互。 還通過隔離服務之間的訪問點,阻止它們之間的級聯故障以及提供回退選項來實現這一點,從而提高系統的整體彈性。Hystrix主要提供了以下幾種容錯方法:

    • 資源隔離
    • 熔斷
    • 降級

    下面我們詳細談談這幾種容錯機制。

    3.1 資源隔離

    資源隔離主要指對線程的隔離。Hystrix提供了兩種線程隔離方式:線程池和信號量。

    3.1.1 線程隔離-線程池

    Hystrix通過命令模式對發送請求的對象和執行請求的對象進行解耦,將不同類型的業務請求封裝為對應的命令請求。如訂單服務查詢商品,查詢商品請求->商品Command;商品服務查詢庫存,查詢庫存請求->庫存Command。并且為每個類型的Command配置一個線程池,當第一次創建Command時,根據配置創建一個線程池,并放入ConcurrentHashMap,如商品Command:

    final static ConcurrentHashMap<String, HystrixThreadPool> threadPools = new ConcurrentHashMap<String, HystrixThreadPool>(); ... if (!threadPools.containsKey(key)) {threadPools.put(key, new HystrixThreadPoolDefault(threadPoolKey, propertiesBuilder)); }

    后續查詢商品的請求創建Command時,將會重用已創建的線程池。線程池隔離之后的服務依賴關系:


    通過將發送請求線程與執行請求的線程分離,可有效防止發生級聯故障。當線程池或請求隊列飽和時,Hystrix將拒絕服務,使得請求線程可以快速失敗,從而避免依賴問題擴散。

    線程池隔離優缺點
    優點:

    • 保護應用程序以免受來自依賴故障的影響,指定依賴線程池飽和不會影響應用程序的其余部分。
    • 當引入新客戶端lib時,即使發生問題,也是在本lib中,并不會影響到其他內容。
    • 當依賴從故障恢復正常時,應用程序會立即恢復正常的性能。
    • 當應用程序一些配置參數錯誤時,線程池的運行狀況會很快檢測到這一點(通過增加錯誤,延遲,超時,拒絕等),同時可以通過動態屬性進行實時糾正錯誤的參數配置。
    • 如果服務的性能有變化,需要實時調整,比如增加或者減少超時時間,更改重試次數,可以通過線程池指標動態屬性修改,而且不會影響到其他調用請求。
    • 除了隔離優勢外,hystrix擁有專門的線程池可提供內置的并發功能,使得可以在同步調用之上構建異步門面(外觀模式),為異步編程提供了支持(Hystrix引入了Rxjava異步框架)。

    注意:盡管線程池提供了線程隔離,我們的客戶端底層代碼也必須要有超時設置或響應線程中斷,不能無限制的阻塞以致線程池一直飽和。

    缺點:

    • 線程池的主要缺點是增加了計算開銷。每個命令的執行都在單獨的線程完成,增加了排隊、調度和上下文切換的開銷。因此,要使用Hystrix,就必須接受它帶來的開銷,以換取它所提供的好處。

    • 通常情況下,線程池引入的開銷足夠小,不會有重大的成本或性能影響。但對于一些訪問延遲極低的服務,如只依賴內存緩存,線程池引入的開銷就比較明顯了,這時候使用線程池隔離技術就不適合了,我們需要考慮更輕量級的方式,如信號量隔離。

    3.1.2 線程隔離-信號量

    上面提到了線程池隔離的缺點,當依賴延遲極低的服務時,線程池隔離技術引入的開銷超過了它所帶來的好處。這時候可以使用信號量隔離技術來代替,通過設置信號量來限制對任何給定依賴的并發調用量。下圖說明了線程池隔離和信號量隔離的主要區別:


    使用線程池時,發送請求的線程和執行依賴服務的線程不是同一個,而使用信號量時,發送請求的線程和執行依賴服務的線程是同一個,都是發起請求的線程。先看一個使用信號量隔離線程的示例:

    public class QueryByOrderIdCommandSemaphore extends HystrixCommand<Integer> {private final static Logger logger = LoggerFactory.getLogger(QueryByOrderIdCommandSemaphore.class);private OrderServiceProvider orderServiceProvider;public QueryByOrderIdCommandSemaphore(OrderServiceProvider orderServiceProvider) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("orderService")).andCommandKey(HystrixCommandKey.Factory.asKey("queryByOrderId")).andCommandPropertiesDefaults(HystrixCommandProperties.Setter().withCircuitBreakerRequestVolumeThreshold(10)至少有10個請求,熔斷器才進行錯誤率的計算.withCircuitBreakerSleepWindowInMilliseconds(5000)//熔斷器中斷請求5秒后會進入半打開狀態,放部分流量過去重試.withCircuitBreakerErrorThresholdPercentage(50)//錯誤率達到50開啟熔斷保護.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE).withExecutionIsolationSemaphoreMaxConcurrentRequests(10)));//最大并發請求量this.orderServiceProvider = orderServiceProvider;}@Overrideprotected Integer run() {return orderServiceProvider.queryByOrderId();}@Overrideprotected Integer getFallback() {return -1;} }

    由于Hystrix默認使用線程池做線程隔離,使用信號量隔離需要顯示地將屬性execution.isolation.strategy設置為ExecutionIsolationStrategy.SEMAPHORE,同時配置信號量個數,默認為10??蛻舳诵柘蛞蕾嚪瞻l起請求時,首先要獲取一個信號量才能真正發起調用,由于信號量的數量有限,當并發請求量超過信號量個數時,后續的請求都會直接拒絕,進入fallback流程。

    信號量隔離主要是通過控制并發請求量,防止請求線程大面積阻塞,從而達到限流和防止雪崩的目的。

    線程隔離總結
    線程池和信號量都可以做線程隔離,但各有各的優缺點和支持的場景,對比如下:

    線程切換支持異步支持超時支持熔斷限流限流
    信號量
    線程池

    線程池和信號量都支持熔斷和限流。相比線程池,信號量不需要線程切換,因此避免了不必要的開銷。但是信號量不支持異步,也不支持超時,也就是說當所請求的服務不可用時,信號量會控制超過限制的請求立即返回,但是已經持有信號量的線程只能等待服務響應或從超時中返回,即可能出現長時間等待。線程池模式下,當超過指定時間未響應的服務,Hystrix會通過響應中斷的方式通知線程立即結束并返回。

    3.2 熔斷

    3.2.1 熔斷器簡介

    現實生活中,可能大家都有注意到家庭電路中通常會安裝一個保險盒,當負載過載時,保險盒中的保險絲會自動熔斷,以保護電路及家里的各種電器,這就是熔斷器的一個常見例子。Hystrix中的熔斷器(Circuit Breaker)也是起類似作用,Hystrix在運行過程中會向每個commandKey對應的熔斷器報告成功、失敗、超時和拒絕的狀態,熔斷器維護并統計這些數據,并根據這些統計信息來決策熔斷開關是否打開。如果打開,熔斷后續請求,快速返回。隔一段時間(默認是5s)之后熔斷器嘗試半開,放入一部分流量請求進來,相當于對依賴服務進行一次健康檢查,如果請求成功,熔斷器關閉。

    3.2.2 熔斷器配置

    Circuit Breaker主要包括如下6個參數:

    1、circuitBreaker.enabled

    是否啟用熔斷器,默認是TRUE。
    2 、circuitBreaker.forceOpen

    熔斷器強制打開,始終保持打開狀態,不關注熔斷開關的實際狀態。默認值FLASE。
    3、circuitBreaker.forceClosed
    熔斷器強制關閉,始終保持關閉狀態,不關注熔斷開關的實際狀態。默認值FLASE。

    4、circuitBreaker.errorThresholdPercentage
    錯誤率,默認值50%,例如一段時間(10s)內有100個請求,其中有54個超時或者異常,那么這段時間內的錯誤率是54%,大于了默認值50%,這種情況下會觸發熔斷器打開。

    5、circuitBreaker.requestVolumeThreshold

    默認值20。含義是一段時間內至少有20個請求才進行errorThresholdPercentage計算。比如一段時間了有19個請求,且這些請求全部失敗了,錯誤率是100%,但熔斷器不會打開,總請求數不滿足20。

    6、circuitBreaker.sleepWindowInMilliseconds

    半開狀態試探睡眠時間,默認值5000ms。如:當熔斷器開啟5000ms之后,會嘗試放過去一部分流量進行試探,確定依賴服務是否恢復。

    3.2.3 熔斷器工作原理

    下圖展示了HystrixCircuitBreaker的工作原理:


    熔斷器工作的詳細過程如下:

    第一步,調用allowRequest()判斷是否允許將請求提交到線程池

  • 如果熔斷器強制打開,circuitBreaker.forceOpen為true,不允許放行,返回。
  • 如果熔斷器強制關閉,circuitBreaker.forceClosed為true,允許放行。此外不必關注熔斷器實際狀態,也就是說熔斷器仍然會維護統計數據和開關狀態,只是不生效而已。
  • 第二步,調用isOpen()判斷熔斷器開關是否打開

  • 如果熔斷器開關打開,進入第三步,否則繼續;
  • 如果一個周期內總的請求數小于circuitBreaker.requestVolumeThreshold的值,允許請求放行,否則繼續;
  • 如果一個周期內錯誤率小于circuitBreaker.errorThresholdPercentage的值,允許請求放行。否則,打開熔斷器開關,進入第三步。
  • 第三步,調用allowSingleTest()判斷是否允許單個請求通行,檢查依賴服務是否恢復

  • 如果熔斷器打開,且距離熔斷器打開的時間或上一次試探請求放行的時間超過circuitBreaker.sleepWindowInMilliseconds的值時,熔斷器器進入半開狀態,允許放行一個試探請求;否則,不允許放行。
  • 此外,為了提供決策依據,每個熔斷器默認維護了10個bucket,每秒一個bucket,當新的bucket被創建時,最舊的bucket會被拋棄。其中每個blucket維護了請求成功、失敗、超時、拒絕的計數器,Hystrix負責收集并統計這些計數器。

    3.2.4 熔斷器測試

    1、以QueryOrderIdCommand為測試command

    2、配置orderServiceProvider不重試且500ms超時

    <dubbo:reference id="orderServiceProvider" interface="com.huang.provider.OrderServiceProvider"timeout="500" retries="0"/>

    3、OrderServiceProviderImpl實現很簡單,前10個請求,服務端休眠600ms,使得客戶端調用超時。

    @Service public class OrderServiceProviderImpl implements OrderServiceProvider {private final static Logger logger = LoggerFactory.getLogger(OrderServiceProviderImpl.class);private AtomicInteger OrderIdCounter = new AtomicInteger(0);@Overridepublic Integer queryByOrderId() {int c = OrderIdCounter.getAndIncrement();if (logger.isDebugEnabled()) {logger.debug("OrderIdCounter:{}", c);}if (c < 10) {try {Thread.sleep(600);} catch (InterruptedException e) {}}return c;}@Overridepublic void reset() {OrderIdCounter.getAndSet(0);} }

    4、單測代碼

    @Test public void testExecuteCommand() throws InterruptedException {orderServiceProvider.reset();int i = 1;for (; i < 15; i++) {HystrixCommand<Integer> command = new QueryByOrderIdCommand(orderServiceProvider);Integer r = command.execute();String method = r == -1 ? "fallback" : "run";logger.info("call {} times,result:{},method:{},isCircuitBreakerOpen:{}", i, r, method, command.isCircuitBreakerOpen());}//等待6s,使得熔斷器進入半打開狀態Thread.sleep(6000);for (; i < 20; i++) {HystrixCommand<Integer> command = new QueryByOrderIdCommand(orderServiceProvider);Integer r = command.execute();String method = r == -1 ? "fallback" : "run";logger.info("call {} times,result:{},method:{},isCircuitBreakerOpen:{}", i, r, method, command.isCircuitBreakerOpen());} }

    5、輸出結果

    2018-02-07 11:38:36,056 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 1 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:36,564 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 2 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:37,074 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 3 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:37,580 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 4 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:38,089 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 5 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:38,599 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 6 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:39,109 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 7 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:39,622 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 8 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:40,138 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 9 times,result:-1,method:fallback,isCircuitBreakerOpen:false 2018-02-07 11:38:40,647 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 10 times,result:-1,method:fallback,isCircuitBreakerOpen:true 2018-02-07 11:38:40,651 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 11 times,result:-1,method:fallback,isCircuitBreakerOpen:true 2018-02-07 11:38:40,653 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 12 times,result:-1,method:fallback,isCircuitBreakerOpen:true 2018-02-07 11:38:40,656 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 13 times,result:-1,method:fallback,isCircuitBreakerOpen:true 2018-02-07 11:38:40,658 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:36 call 14 times,result:-1,method:fallback,isCircuitBreakerOpen:true 2018-02-07 11:38:46,671 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:44 call 15 times,result:10,method:run,isCircuitBreakerOpen:false 2018-02-07 11:38:46,675 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:44 call 16 times,result:11,method:run,isCircuitBreakerOpen:false 2018-02-07 11:38:46,680 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:44 call 17 times,result:12,method:run,isCircuitBreakerOpen:false 2018-02-07 11:38:46,685 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:44 call 18 times,result:13,method:run,isCircuitBreakerOpen:false 2018-02-07 11:38:46,691 INFO [main] com.huang.test.command.QueryByOrderIdCommandTest:testExecuteCommand:44 call 19 times,result:14,method:run,isCircuitBreakerOpen:false

    前9個請求調用超時,走fallback邏輯;

    10-14個請求,熔斷器開關打開,直接快速失敗走fallback邏輯;

    15-19個請求,熔斷器進入半開狀態,放行一個試探請求調用成功,熔斷器關閉,后續請求恢復。

    3.3 回退降級

    降級,通常指務高峰期,為了保證核心服務正常運行,需要停掉一些不太重要的業務,或者某些服務不可用時,執行備用邏輯從故障服務中快速失敗或快速返回,以保障主體業務不受影響。Hystrix提供的降級主要是為了容錯,保證當前服務不受依賴服務故障的影響,從而提高服務的健壯性。要支持回退或降級處理,可以重寫HystrixCommand的getFallBack方法或HystrixObservableCommand的resumeWithFallback方法。

    Hystrix在以下幾種情況下會走降級邏輯:

    • 執行construct()或run()拋出異常
    • 熔斷器打開導致命令短路
    • 命令的線程池和隊列或信號量的容量超額,命令被拒絕
    • 命令執行超時

    3.3.1 降級回退方式

    Fail Fast 快速失敗
    快速失敗是最普通的命令執行方法,命令沒有重寫降級邏輯。 如果命令執行發生任何類型的故障,它將直接拋出異常。

    Fail Silent 無聲失敗
    指在降級方法中通過返回null,空Map,空List或其他類似的響應來完成。

    @Override protected Integer getFallback() {return null; }@Override protected List<Integer> getFallback() {return Collections.emptyList(); }@Override protected Observable<Integer> resumeWithFallback() {return Observable.empty(); }

    Fallback: Static
    指在降級方法中返回靜態默認值。 這不會導致服務以“無聲失敗”的方式被刪除,而是導致默認行為發生。如:應用根據命令執行返回true / false執行相應邏輯,但命令執行失敗,則默認為true

    @Override protected Boolean getFallback() {return true; } @Override protected Observable<Boolean> resumeWithFallback() {return Observable.just( true ); }

    Fallback: Stubbed

    當命令返回一個包含多個字段的復合對象時,適合以Stubbed 的方式回退。

    @Override protected MissionInfo getFallback() {return new MissionInfo("missionName","error"); }

    Fallback: Cache via Network
    有時,如果調用依賴服務失敗,可以從緩存服務(如redis)中查詢舊數據版本。由于又會發起遠程調用,所以建議重新封裝一個Command,使用不同的ThreadPoolKey,與主線程池進行隔離。

    @Override protected Integer getFallback() {return new RedisServiceCommand(redisService).execute(); }

    Primary + Secondary with Fallback
    有時系統具有兩種行為- 主要和次要,或主要和故障轉移。主要和次要邏輯涉及到不同的網絡調用和業務邏輯,所以需要將主次邏輯封裝在不同的Command中,使用線程池進行隔離。為了實現主從邏輯切換,可以將主次command封裝在外觀HystrixCommand的run方法中,并結合配置中心設置的開關切換主從邏輯。由于主次邏輯都是經過線程池隔離的HystrixCommand,因此外觀HystrixCommand可以使用信號量隔離,而沒有必要使用線程池隔離引入不必要的開銷。原理圖如下:


    主次模型的使用場景還是很多的。如當系統升級新功能時,如果新版本的功能出現問題,通過開關控制降級調用舊版本的功能。示例代碼如下:

    public class CommandFacadeWithPrimarySecondary extends HystrixCommand<String> {private final static DynamicBooleanProperty usePrimary = DynamicPropertyFactory.getInstance().getBooleanProperty("primarySecondary.usePrimary", true);private final int id;public CommandFacadeWithPrimarySecondary(int id) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")).andCommandKey(HystrixCommandKey.Factory.asKey("PrimarySecondaryCommand")).andCommandPropertiesDefaults(// 由于主次command已經使用線程池隔離,Facade Command使用信號量隔離即可HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)));this.id = id;}@Overrideprotected String run() {if (usePrimary.get()) {return new PrimaryCommand(id).execute();} else {return new SecondaryCommand(id).execute();}}@Overrideprotected String getFallback() {return "static-fallback-" + id;}@Overrideprotected String getCacheKey() {return String.valueOf(id);}private static class PrimaryCommand extends HystrixCommand<String> {private final int id;private PrimaryCommand(int id) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")).andCommandKey(HystrixCommandKey.Factory.asKey("PrimaryCommand")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("PrimaryCommand")).andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(600)));this.id = id;}@Overrideprotected String run() {return "responseFromPrimary-" + id;}}private static class SecondaryCommand extends HystrixCommand<String> {private final int id;private SecondaryCommand(int id) {super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("SystemX")).andCommandKey(HystrixCommandKey.Factory.asKey("SecondaryCommand")).andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("SecondaryCommand")).andCommandPropertiesDefaults( HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(100)));this.id = id;}@Overrideprotected String run() {return "responseFromSecondary-" + id;}}public static class UnitTest {@Testpublic void testPrimary() {HystrixRequestContext context = HystrixRequestContext.initializeContext();try {ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", true);assertEquals("responseFromPrimary-20", new CommandFacadeWithPrimarySecondary(20).execute());} finally {context.shutdown();ConfigurationManager.getConfigInstance().clear();}}@Testpublic void testSecondary() {HystrixRequestContext context = HystrixRequestContext.initializeContext();try {ConfigurationManager.getConfigInstance().setProperty("primarySecondary.usePrimary", false);assertEquals("responseFromSecondary-20", new CommandFacadeWithPrimarySecondary(20).execute());} finally {context.shutdown();ConfigurationManager.getConfigInstance().clear();}}} }

    通常情況下,建議重寫getFallBack或resumeWithFallback提供自己的備用邏輯,但不建議在回退邏輯中執行任何可能失敗的操作。

    4、總結

    本文介紹了Hystrix及其工作原理,還介紹了Hystrix線程池隔離、信號量隔離和熔斷器的工作原理,以及如何使用Hystrix的資源隔離,熔斷和降級等技術實現服務容錯,從而提高系統的整體健壯性。

    Hystrix主要用來做容錯處理,提高系統的整體彈性。

  • 資源隔離,主要是線程隔離,通過線程池或信號量實現
  • 熔斷(負載過載的時候,熔斷,然后超時后自動半打開恢復)
  • 回退降級(Hystrix提供的降級主要是為了容錯,保證當前服務不受依賴服務故障的影響,從而提高服務的健壯性。)
  • Hystrix git wiki介紹:

    https://github.com/Netflix/Hystrix/wiki/How-it-Works

    總結

    以上是生活随笔為你收集整理的Spring cloud——Hystrix 原理解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。