1. 程式人生 > 實用技巧 >7、Spring Cloud 之 Hystrix元件

7、Spring Cloud 之 Hystrix元件

什麼是Hystrix(概述)

微服務架構中,我們是將一個單體應用拆分成多個服務單元,各個服務單元之間通過註冊中心彼此發現和消費對方提供的服務,每個服務單元都是單獨部署, 在各自的服務程序中執行,服務之間通過遠端呼叫實現資訊互動,那麼當某個服務的響應太慢或者故障,又或者因為網路波動或故障,則會造成呼叫者延遲或呼叫失敗,當出現大量請求的到來,則會造成請求堆積,導致呼叫者的執行緒掛起,從而引發呼叫者也無法響應,呼叫者也發生了故障。因為一個服務單元的故障,導致服務呼叫單元也發生故障。

例如:
電商中的使用者下訂單,我們有兩個服務,訂單服務和庫存服務,使用者下訂單時呼叫下訂單服務,然後下訂單服務又呼叫減庫存服務,如果減庫存服務響應延遲或者沒有響應,則會造成下訂單服務的執行緒掛起等待,如果大量的使用者請求下訂單,或導致大量的請求堆積,引起下訂單服務也不可用,如果還有另外一個服務依賴於訂單服務,比如使用者服務,它需要查詢使用者訂單,那麼使用者服務查詢訂單也會引起大量的延遲和請求堆積,導致使用者服務也不可用。 所以在微服務架構中,很容易造成服務故障的蔓延,引發整個微服務系統癱瘓不可用。 為了解決此問題,微服務架構中引入了一種叫熔斷器的服務保護機制。

熔斷器也叫斷路器,最早來源於微服務之父 Martin Fowler的論文CircuitBreaker一文。“熔斷器”本身是一種開關裝置,用於在電路上保護線路過載,當線路中有電器發生短路時,能夠及時切斷故障電路,防 止發生過載、發熱甚至起火等嚴重後果。 微服務架構中的熔斷器,就是當被呼叫方沒有響應,呼叫方直接返回一個錯誤響 應即可,而不是長時間的等待,這樣避免呼叫時因為等待而執行緒一直得不到釋放, 避免故障在分散式系統間蔓延;

Spring Cloud Hystrix實現了熔斷器、執行緒隔離等一系列服務保護功能。該功能也是基於 Netflix 的開源框架 Hystrix 實現的,該框架的目標在於通過控制那些 訪問遠端系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix 具備服務降級、服務熔斷、執行緒和訊號隔離、請求快取、請求合併 以及服務監控等強大功能。

Hystrix 入門

步驟

1.新增依賴

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

2.在入口類中使用@EnableCircuitBreaker註解開啟斷路器功能。
也可以使用@SpringCloudApplication註解,該註解相當於是@EnableCircuitBreaker,@SpringBootApplication,@EnableEurekaClient的複合註解

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //開啟斷路器功能
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 在呼叫遠端服務的方法上新增@HystrixCommand註解
    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod = "error") //當發生熔斷,回撥error方法
    public String hystrix(){
        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }


    public String error(){
        return "error";
    }

環境

  • 至少一個Eureka註冊中心程式
  • 至少兩個Eureka服務提供程式,服務名相同
  • 一個服務呼叫程式

服務呼叫程式選擇負載均衡策略為:重試策略

    @Bean
    public IRule iRule(){
        return new RetryRule(); //重試策略
    }

配置如下

#每間隔30秒,向服務端傳送一次心跳,證明自己依然“存活”(預設30秒)
eureka.instance.lease-renewal-interval-in-seconds=2
#告訴服務端,如果90s內沒有傳送心跳,就代表該服務故障了,讓服務端登出該服務(預設90秒)
eureka.instance.lease-expiration-duration-in-seconds=8

啟動所有程式,然後停止其中一個服務提供程式,模擬程式發生故障,這時請求訪問該方法使用的還是,輪詢負載均衡策略,正常的節點依舊可以訪問,故障的節點無法響應,這時會呼叫消費端程式的錯誤響應方法返回(也就是error方法)這就是熔斷器的作用,當註冊中心還處於服務保護機制中,會再次對發生故障的節點進行重試,8秒後服務沒有傳送心跳,則註冊中心將該服務登出,使得再發送該請求時,只會訪問正常的節點。

服務消費者 Hystrix 測試

hystrix 預設超時時間是 1000 毫秒,如果你後端的響應超過此時間,就會觸發熔斷器;

    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod = "error",
            commandProperties={ @HystrixProperty(
                    name="execution.isolation.thread.timeoutInMilliseconds",
                    value="1500")}) //當發生熔斷,回撥error方法,通過commandProperties屬性設定超時時間
    public String hystrix(){
        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }

    public String error(){
        return "error";
    }

其中一個服務提供者程式使用sleep方法模擬超時,遠端呼叫服務方法的時間為1500毫秒,而服務本身執行時間大於4500毫秒,所以會進行熔斷

@RestController
public class HelloController {
    @RequestMapping("/service/hello")
    public String hello(){
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "hello spring clound provider 1";
    }
}

Hystrix 的服務降級

有了服務的熔斷,隨之就會有服務的降級,所謂服務降級,就是當某個服務熔斷之後,服務端提供的服務將不再被呼叫,此時由客戶端自己準備一個本地的 fallback回撥,返回一個預設值來代表服務端的返回; 這種做法,雖然不能得到正確的返回結果,但至少保證了服務的可用,比直接丟擲錯誤或服務不可用要好很多,當然這需要根據具體的業務場景來選擇;

Hystrix 的異常處理

我們在呼叫服務提供者時,我們自己也有可能會拋異常,預設情況下方法拋了異常會自動進行服務降級,交給服務降級中的方法去處理;服務消費端本身也是一個程式,在遠端呼叫程式的時候本身也可能出現異常;不管是服務呼叫者還是服務消費者出現異常時,都會觸發Hystrix熔斷機制,也會進行熔斷降級呼叫回撥方法;

當我們自己發生異常後,只需要在服務降級方法中新增一個 Throwable 型別的引數就能夠獲取到丟擲的異常的型別,如下:

服務呼叫者異常

    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod = "error",
            commandProperties={ @HystrixProperty(
                    name="execution.isolation.thread.timeoutInMilliseconds",
                    value="1500")}) //當發生熔斷,回撥error方法,通過commandProperties屬性設定超時時間
    public String hystrix(){

        //模擬呼叫者端發生異常
        int a = 10/0;

        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }


    public String error(Throwable throwable){
        //列印異常資訊
        System.out.println(throwable.getMessage());
        return "error";
    }

服務提供者異常
服務提供者方法

    @RequestMapping("/service/hello")
    public String hello(){
        int i = 10/0;
        return "hello spring clound provider 2";
    }

服務呼叫者方法

    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod = "error",
            commandProperties={ @HystrixProperty(
                    name="execution.isolation.thread.timeoutInMilliseconds",
                    value="1500")}) //當發生熔斷,回撥error方法,通過commandProperties屬性設定超時時間
    public String hystrix(){

        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }


    public String error(Throwable throwable){
        //列印異常資訊
        System.out.println(throwable.getMessage());
        return "error";
    }

此時我們可以在控制檯看到異常的型別;
如果遠端服務有一個異常丟擲後我們不希望進入到服務降級方法中去處理,而是
直接將異常拋給使用者,那麼我們可以在@HystrixCommand註解中新增忽略異
常,如下:

@HystrixCommand(ignoreExceptions = Exception.class){}

加上ignoreException屬性後表示當出現該類異常時,不進行服務降級處理,直接將異常返回,這裡寫的是Exception異常類,也就是出現任何異常都不進行服務降級處理;

    @RequestMapping("/web/hystrix")
    @HystrixCommand(fallbackMethod = "error",
                    ignoreExceptions = Exception.class,
                    commandProperties={ @HystrixProperty(
                    name="execution.isolation.thread.timeoutInMilliseconds",
                    value="1500")}) //當發生熔斷,回撥error方法,通過commandProperties屬性設定超時時間
    public String hystrix(){

        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();
    }


    public String error(Throwable throwable){
        //列印異常資訊
        System.out.println(throwable.getMessage());
        return "error";
    }

測試之後發現,當發生異常的情況下,並不會走服務降級處理方法(級error方法),需要注意的是服務超時並不是異常,所以會進行服務降級處理

自定義Hystrix請求的服務異常熔斷處理

我們也可以自定義類繼承自 HystrixCommand 來實現自定義的Hystrix 請求,在 getFallback 方法中呼叫 getExecutionException 方法來獲取服務丟擲的異常

服務消費端自定義異常熔斷處理類

/**
 * 自定義的Hystrix請求
 */
public class MyHystrixCommand extends HystrixCommand<String>{

    private RestTemplate restTemplate;

    /**
     * setter是服務的分組命名等資訊
     * @param setter
     * @param restTemplate
     */
    public MyHystrixCommand(Setter setter, RestTemplate restTemplate){
        super(setter);
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
//        呼叫遠端服務
        return restTemplate.getForEntity("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello", String.class).getBody();

    }

    /**
     * 覆蓋getFallback方法實現服務熔斷降級的邏輯
     * 當遠端服務超時,異常,不可用等情況時,會觸發熔斷方法
     * @return
     */
    @Override
    protected String getFallback() {
//        表示觸發熔斷
        return "error";
    }
}

Controller呼叫


    @RequestMapping("/web/hystrix2")
    public String hystrix2(){
        MyHystrixCommand myHystrixCommand = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")) ,restTemplate);
//        如果執行遠端呼叫沒有問題則返回結果,如果遠端調用出現異常,則返回異常處理的結果
        String execute = myHystrixCommand.execute();

        return execute;
    }

自定義異常熔斷處理類,呼叫時可以進行同步或非同步呼叫,使用execute()方法屬於同步呼叫,該方法執行後,會等待遠端的返回結果,獲得返回結果後才返回,程式碼繼續往下執行;如果是queue()方法,則該方法是非同步呼叫,並不等待結果放回才執行下面的程式,而是執行非同步方法後,直接執行下面的程式;

    @RequestMapping("/web/hystrix3")
    public String hystrix3() throws ExecutionException, InterruptedException {
        MyHystrixCommand myHystrixCommand = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")) ,restTemplate);
//      非同步呼叫(該方法執行後,不會馬上有遠端的返回結果,而是去執行下面的其他程式碼)
        Future<String> queue = myHystrixCommand.queue();

        //業務邏輯程式碼(省略)

        //通過Future物件獲取,如果沒有獲取到,該方法會一直阻塞
        String str = queue.get();

        //業務邏輯程式碼(省略)

        return str;
    }

自定義的熔斷處理類可以通過getExecutionException方法來獲取服務丟擲的異常;

/**
     * 覆蓋getFallback方法實現服務熔斷降級的邏輯
     * 當遠端服務超時,異常,不可用等情況時,會觸發熔斷方法
     * @return
     */
    @Override
    protected String getFallback() {
        //呼叫父類的getExecutionException方法獲取異常資訊
        Throwable throwable = super.getExecutionException();

        System.out.println("=============" + throwable.getMessage());
        System.out.println("=============" + throwable.getStackTrace());

//        表示觸發熔斷
        return "error";
    }

Hystrix 儀表盤監控

Hystrix 儀表盤(Hystrix Dashboard),就像汽車的儀表盤實時顯示汽車的各項資料一樣,Hystrix 儀表盤主要用來監控 Hystrix 的實時執行狀態,通過它我們可以看到Hystrix的各項指標資訊,從而快速發現系統中存在的問題進而解決它。

要使用Hystrix儀表盤功能,我們首先需要有一個Hystrix Dashboard,這個功能我們可以在原來的消費者應用上新增,讓原來的消費者應用具備Hystrix儀表盤功能,但一般地,微服務架構思想是推崇服務的拆分,Hystrix Dashboard也是一個服務,所以通常會單獨建立一個新的工程專門用做 Hystrix Dashboard服務;

建立一個新的SpringBoot專案

新增依賴

        <!--hystrix-dashboard 功能的起步依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在啟動類上新增 @EnableHystrixdashboard註解開啟儀表盤功能

@SpringBootApplication
@EnableHystrixDashboard //註解開啟儀表盤功能
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

屬性配置

# 應用服務 WEB 訪問埠(hystrix的儀表盤服務埠)
server.port=3721

搭建完成後,啟動專案,通過http://localhost:3721/hystrix 進行訪問;

Hystrix 儀表盤工程已經建立好了,現在我們需要有一個服務,讓這個服務提供一個路徑為/actuator/hystrix.stream 介面,然後就可以使用 Hystrix 儀表盤來對該服務進行監控了;

我們改造消費者服務,讓其能提供/actuator/hystrix.stream介面,步驟如下:
1、消費者專案需要有hystrix的依賴

        <!-- spring cloud 熔斷器起步依賴-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

2、需要有一個spring boot的服務監控依賴

        <!-- springboot 提供的健康檢查依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>

3、配置檔案需要配置spring boot監控端點的訪問許可權:

監控端點就是服務消費者程式

#springboot 的監控端點訪問許可權,*代表所有的訪問端點都允許訪問,方便測試;
# hystrix.stream 則代表health 和 info 以及hystrix.stream三個可以訪問
#預設不配置是支援health 和 info
management.endpoints.web.exposure.include=*

注意

這裡有一個細節需要注意,要訪問/hystrix.stream介面,首先得訪問consumer工程中的任意一個使用熔斷器的介面,否則直接訪問/hystrix.stream介面時會輸出出一連串的ping: ping: …,先訪問consumer中的任意一個其他介面,然後再訪問/hystrix.stream介面即可;

先訪問consumer介面 http://localhost:8082/web/hystrix2

    @RequestMapping("/web/hystrix2")
    public String hystrix2(){
        MyHystrixCommand myHystrixCommand = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")) ,restTemplate);
//        如果執行遠端呼叫沒有問題則返回結果,如果遠端調用出現異常,則返回異常處理的結果
        String execute = myHystrixCommand.execute();

        return execute;
    }

再訪問Hystrix-bashboard工程的介面:localhost:8082/actuator/hystrix.steam
注意 8082是consumer的埠