Spring Cloud Hystrix入門和Hystrix命令原理分析

斷路由器模式

在分佈式架構中,當某個服務單元發生故障以後,經過斷路由器的故障監控(相似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免了故障在分佈式系統中的蔓延。java

Spring Cloud Hystrix針對上述問題實現了斷路由器、線程隔離等一系列服務保護功能。它是基於Netflix Hystrix實現,該框架的目標在於經過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。git

Hystrix具有服務降級、服務熔斷、線程和信號隔離、請求緩存、請求合併以及服務監控等強大功能。github

快速入門

構建一個以下架構圖的服務調用關係
服務架構圖
分析上述架構圖,主要有如下幾項工做:web

  1. eureka-server工程: 服務註冊中心,端口1111
  2. hello-service工程: HELLO-SERVICE服務單元,啓動兩個實例,端口分別爲8081和8082
  3. ribbon-consumer工程: 使用Ribbon實現的服務消費者,端口9000

修改ribbon-consumer模塊

修改pom.xml

首先在pom.xml文件中增長spring-cloud-starter-hystrix依賴spring

開啓斷路由器功能

在ribbon-consumer主類中使用@EnableCircuitBreaker註解開啓斷路由器功能,在這裏還有一個小技巧,可使用@SpringCloudApplicationd代替@EnableCircuitBreaker、@EnableEurekaClient、@SpringBootApplication這三個註解。緩存

改造服務消費方式

改造ribbon-consumer中的HelloService,以下網絡

package cn.sh.ribbon.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @author sh
 */
@Service
public class HelloService {

    private static final Logger logger = LoggerFactory.getLogger(HelloService.class);

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 使用@HystrixCommand註解指定回調方法
     * @param name
     * @return
     */
    @HystrixCommand(fallbackMethod = "ribbonHelloFallback", commandKey = "helloKey")
    public String ribbonHello(String name) {
        long start = System.currentTimeMillis();
        String result = restTemplate.getForObject("http://HELLO-SERVICE/hello?name=" + name, String.class);
        long end = System.currentTimeMillis();
        logger.info("Spend Time:" + (end - start));
        return result;
    }

    public String ribbonHelloFallback() {
        return "Hello, this is fallback";
    }
}

改造服務提供者

改造hello-service模塊中的HelloService.java,以下:架構

package cn.sh.hello.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.Random;

/**
 * @author sh
 */
@Service
public class HelloService {

    private static final Logger logger = LoggerFactory.getLogger(HelloService.class);

    public String hello(String name) throws InterruptedException {
        int sleepTime = new Random().nextInt(3000);
        logger.info("sleepTime:" + sleepTime);
        Thread.sleep(sleepTime);
        return "Hello, " + name;
    }
}

在服務提供者的改造中,咱們會讓方法阻塞幾秒中返回內容,因爲Hystrix默認的超時時間爲2000ms,在這裏產生0-3000的隨機數可讓處理過程有必定機率觸發斷路由器。框架

原理分析

工做流程圖
下面根據工做流程圖,咱們來分析一下Hystrix是如何工做的。dom

第1步 建立HystrixCommand或HystrixObservableCommand對象

首先,構建一個HystrixCommand或HystrixObservableCommand對象,用來表示對依賴服務的操做請求,同時傳遞全部須要的參數。這兩個對象都採用了命令模式來實現對服務調用操做的封裝,可是這兩個對象分別針對不一樣的應用場景。

  1. HystrixCommand: 用在依賴的服務返回單個操做結果的時候
  2. HystrixObservableCommand: 用在依賴的服務返回多個操做結果的時候

命令模式,未來自客戶端的請求封裝成一個對象,從而讓你可使用不一樣的請求對客戶端進行參數化。它能夠用於實現行爲請求者和行爲實現者的解耦,以便使二者能夠適應變化

命令模式的示例代碼在command模塊下

經過命令模式的示例代碼能夠分析出命令模式的幾個關鍵點:

  1. Receiver: 接收者,處理具體的業務邏輯
  2. Command: 抽象命令,定義了一個對象應具有的一系列命令操做,如execute()、undo()、redo()等。當命令操做被調用的時候就會觸發接收者作具體命令對應的業務邏輯。
  3. ConcreteCommand: 具體的命令實現,在這裏要綁定命令操做和接收者之間的關係,execute()命令的實現轉交給了Receiver的action()方法
  4. Invoker: 調用者,它擁有一個命令對象,能夠在須要時經過命令對象完成具體的業務邏輯

命令模式中Invoker和Receiver的關係很是相似於請求-響應模式,因此它比較適用於實現記錄日誌、撤銷操做、隊列請求等。

如下狀況咱們能夠考慮使用命令模式:

  1. 使用命令模式做爲回調在面向對象系統中的替代。
  2. 須要在不一樣的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者能夠有不一樣的生命週期。換言之,原先的請求發出者可能已經不在了,可是命令自己仍然是活動的。這時命令的接收者能夠是在本地,也能夠在網絡的另外一個地址。命令對象能夠在序列化以後傳送到另外一臺機器上。
  3. 系統須要支持命令的撤銷。命令對象能夠把狀態存儲起來,等到客戶端須要撤銷命令所產生的效果時,能夠調用undo()方法,把命令所產生的效果撤銷掉。命令對象還提供redo()方法,以供客戶端在須要時再從新實施命令效果。
  4. 若是要將系統中全部的數據更新到日誌裏,以便在系統崩潰時,能夠根據日誌讀回全部的數據更新命令,從新調用execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所作的數據更新。

第2步 命令執行

從圖中咱們能夠看到一共存在4種命令的執行方式,Hystrix在執行時會根據建立的Command對象以及具體的狀況來選擇一個執行。

HystrixCommand

HystrixCommand實現了兩個執行方式:

  1. execute(): 同步執行,從依賴的服務返回一個單一的結果對象,或是在錯誤時拋出異常
  2. queue(): 異步執行,直接返回一個Future對象,其中包含了服務執行結束時要返回的單一結果對象。
R value = command.execute();
Future<R> fValue = command.queue();

HystrixObservableCommand

HystrixObservableCommand實現了另兩種執行方式:

  1. observer(): 返回Observable對象,它表明了操做的多個結果,是一個HotObservable
  2. toObservable(): 一樣返回Observable對象,也表明操做的多個結果,返回的是一個ColdObservable
Observable<R> ohvalue = command.observe();
Observable<R> ocvalue = command.toObservable();

Hot Observable和Cold Observable,分別對應了上面command.observe()和command.toObservable的返回對象。

Hot Observable,不論事件源是否有訂閱者,都會在建立後對事件進行發佈,因此對Hot Observable的每個訂閱者都有多是從事件源的中途開始的,並可能只是看到了整個操做的局部過程。

Cold Observable在沒有訂閱者的時候不會發布事件,而是進行等待,直到有訂閱者後纔會發佈事件,因此對於Cold Observable的訂閱者,它能夠保證從一開始看到整個操做的所有過程。

HystrixCommand也使用RxJava實現:

  1. execute():該方法是經過queue()返回的異步對象Future<R>的get()方法來實現同步執行的。該方法會等待任務執行結束,而後得到R類型的結果返回。
  2. queue():經過toObservable()得到一個Cold Observable,而且經過經過toBlocking()將該Observable轉換成BlockingObservable,它能夠把數據以阻塞的方式發出來,toFuture方法則是把BlockingObservable轉換爲一個Future,該方法只是建立一個Future返回,並不會阻塞,這使得消費者能夠本身決定如何處理異步操做。execute()則是直接使用了queue()返回的Future中的阻塞方法get()來實現同步操做的。
  3. 經過這種方式轉換的Future要求Observable只發射一個數據,因此這兩個實現都只能返回單一結果。

RxJava觀察者-訂閱者模式入門介紹

在Hystrix的底層實現中大量使用了RxJava。上面提到的Observable對象就是RxJava的核心內容之一,能夠把Observable對象理解爲事件源或是被觀察者,與其對應的是Subscriber對象,能夠理解爲訂閱者或是觀察者

  1. Observable用來向訂閱者Subscriber對象發佈事件,Subscriber對象在接收到事件後對其進行處理,這裏所指的事件一般就是對依賴服務的調用。
  2. 一個Observable能夠發出多個事件,直到結束或是發生異常。
  3. Observable對象每發出一個事件,就會調用對應觀察者Subscriber對象的onNext()方法。
  4. 每個Observable的執行,最後必定會經過調用Subscriber.onCompleted()或是Subscriber.onError()來結束該事件的操做流。

第3步 結果是否被緩存

若當前命令的請求緩存功能是被啓用的,而且該命令緩存命中,那麼緩存的結果會當即以Observable對象的形式返回。

第4步 斷路器是否打開

在命令結果沒有緩存命中的時候,Hystrix在執行命令前須要檢查斷路器是否爲打開狀態:

  1. 若是斷路器是打開的,Hystrix不會執行命令,而是直接賺到fallback處理邏輯(對應下面第8步)
  2. 若是斷路器是關閉的,那麼Hystrix會跳到第5步,檢查是否有可用資源來執行命令。

第5步 線程池/請求隊列/信號量是否佔滿

若是與命令相關的線程池和請求隊列或者信號量(不使用線程池的時候)已被佔滿,那麼Hystrix不會執行命令,轉接到fallback處理邏輯(對應下面第8步)

Hystrix所判斷的線程池並不是容器的線程池,而是每一個依賴服務的專有線程池。Hystrix爲了保證不會由於某個依賴服務的問題影響到其餘依賴服務而採用了艙壁模式來隔離每一個依賴的服務。

第6步 HystrixObservableCommand.construct()或HystrixCommand.run()

Hystrix會根據咱們編寫的方法來決定採起什麼樣的方式去請求依賴服務:

  1. HystrixCommand.run(): 返回一個單一的結果,或者拋出異常
  2. HystrixObservableCommand.construct(): 返回一個Observable對象來發射多個結果,或經過onError發送錯誤通知

若是run()或construct()方法的執行時間超過了命令設置的超時閥值,當前處理線程會拋出一個TimeoutException(若是該命令不在其自身的線程中執行,則會經過單獨的計時線程拋出)。在這種狀況下,Hystrix會轉到fallback邏輯去處理(第8步)。同時,若是當前命令沒有被取消或中斷,那麼它最終會忽略run()或construct()方法的返回。

若是命令沒有拋出異常並返回告終果,那麼Hystrix在記錄一些日誌並採集監控報告以後將該結果返回。在使用run()時,返回一個Observable,它會發射單個結果併產生onCompleted的結束通知,在使用construct()時,會直接返回該方法產生的Observable對象。

第7步 計算斷路器的健康度

Hystrix會將成功、失敗、拒絕、超時等信息報告給斷路器,斷路器會維護一組計數器來統計這些數據。

斷路器會使用這些統計數據來決定是否要將斷路器打開,來對某個依賴服務的請求進行熔斷/短路,直到恢復期結束。若在恢復期結束後,根據統計數據判斷若是仍是未達到健康指標,就再次熔斷/短路。

第8步 fallback處理

當命令執行失敗時,Hystrix會進入fallback嘗試回退處理,咱們一般也稱之爲服務降級。可以引發服務降級處理的狀況主要有如下幾種:

  1. 第4步,當前命令處於熔斷/短路狀態,斷路器是打開的時候。
  2. 第5步,當前命令的線程池、請求隊列或者信號量被佔滿的時候。
  3. 第6步,HystrixObservableCommand.construct()或HystrixCommand.run()拋出異常的時候。

在服務降級邏輯中,咱們須要實現一個通用的響應結果,而且該結果的處理邏輯應當是從緩存或是根據一些靜態邏輯來獲取,而不是依賴網絡請求獲取。若是必定要在降級邏輯中包含網絡請求,那麼該請求也必須被包裝在HystrixCommand或是HystrixObservableCommand中,從而造成級聯的降級策略,而最終的降級邏輯必定不是一個依賴網絡請求的處理,而是一個可以穩定返回結果的處理邏輯。

HystrixCommand和HystrixObservableCommand中實現降級邏輯時有如下不一樣:

  1. 當使用HystrixCommand的時候,經過實現HystrixCommand.getFallback()來實現服務降級邏輯。
  2. 當使用HystrixObservableCommand的時候,經過HystrixObservableCommand.resumeWithFallback()實現服務降級邏輯,該方法會返回一個Observable對象來發射一個或多個降級結果。

當命令的降級邏輯返回結果以後,Hystrix就將該結果返回給調用者。當使用HystrixCommand.getFallback()時候,它會返回一個Observable對象,該對象會發射getFallback()的處理結果。而使用HystrixObservableCommand.resumeWithFallback()實現的時候,它會將Observable對象直接返回。

若是咱們沒有爲命令實現降級邏輯或在降級處理中拋出了異常,Hystrix依然會返回一個Observable對象,可是他不會發射任何結果數據,而是經過onError方法通知命令當即中斷請求,並經過onError()方法將引發命令失敗的異常發送給調用者。在降級策略的實現中咱們應儘量避免失敗的狀況。

若是在執行降級時發生失敗,Hystrix會根據不一樣的執行方法做出不一樣的處理:

  1. execute(): 拋出異常
  2. queue(): 正常返回Future對象,可是調用get()來獲取結果時會拋出異常
  3. observe(): 正常返回Observable對象,當訂閱它的時候,將當即經過訂閱者的onError方法來通知停止請求
  4. toObservable(): 正常返回Observable對象,當訂閱它的時候,將經過調用訂閱者的onError方法來通知停止請求

第9步 返回成功的響應

當Hystrix命令執行成功以後,它會將處理結果直接返回或是以Observable的形式返回。具體的返回形式取決於不一樣的命令執行方式。
返回結果

  1. toObservable(): 返回原始的Observable,必須經過訂閱它纔會真正觸發命令的執行流程
  2. observe(): 在toObservable()產生原始Observable以後當即訂閱它,讓命令可以立刻開始異步執行,並返回一個Observable對象,當調用它的subscribe時,將從新產生結果和通知給訂閱者。
  3. queue(): 將toObservable()產生的原始Observable經過toBlocking()方法轉換成BlockingObservable對象,並調用它的toFuture()方法返回異步的Future對象
  4. execute(): 在queue()產生異步結果Future對象以後,經過調用get()方法阻塞並等待結果的返回。

代碼地址

spring-cloud-example

相關文章
相關標籤/搜索