新 Spring Cloud (三) 之 Hystrix熔斷保護

零、 前言

0. 之前寫過兩篇Spring Cloud,但是感覺不夠具體,所以重新寫了一份。

新 Spring Cloud (一) 之 Eureka 服務註冊中心
新 SpringCloud (二) 之 Ribbon 負載均衡
新 Spring Cloud (三) 之 Hystrix熔斷保護
新 Spring Cloud (四) 之 Fegin遠程調用
新 Spring Cloud (五) 之 Zuul 網關

一、簡介

Hystrix,英文意思是豪豬,是一種保護機制。Hystrix也是Netflix公司的一款組件。Hystix是Netflix開源的一個延遲和容錯庫,用於隔離訪問遠程服務、第三方庫,防止出現級聯失敗。

1. Hystrix設計原則

  1. 防止單個服務的故障,耗盡整個系統服務的容器(比如tomcat)的線程資源,避免分佈式環境裏大量級聯失敗。通過第三方客戶端訪問(通常是通過網絡)依賴服務出現失敗、拒絕、超時或短路時執行回退邏輯
  2. 用快速失敗代替排隊(每個依賴服務維護一個小的線程池或信號量,當線程池滿或信號量滿,會立即拒絕服務而不會排隊等待)和優雅的服務降級;當依賴服務失效後又恢復正常,快速恢復
  3. 提供接近實時的監控和警報,從而能夠快速發現故障和修復。監控信息包括請求成功,失敗(客戶端拋出的異常),超時和線程拒絕。如果訪問依賴服務的錯誤百分比超過閾值,斷路器會跳閘,此時服務會在一段時間內停止對特定服務的所有請求
  4. 將所有請求外部系統(或請求依賴服務)封裝到HystrixCommand或HystrixObservableCommand對象中,然後這些請求在一個獨立的線程中執行。使用隔離技術來限制任何一個依賴的失敗對系統的影響。每個依賴服務維護一個小的線程池(或信號量),當線程池滿或信號量滿,會立即拒絕服務而不會排隊等待

2. 雪崩問題

1. 介紹
在微服務架構中,服務之間的調用錯綜複雜,一個請求可能需要多個微服務接口才能實現,形成非常複雜的調用鏈路。

如圖,一次業務請求,需要調用A、P、H、I四個服務,這四個服務又可能調用其它服務。
在這裏插入圖片描述

如果此時,某個服務出現異常:
在這裏插入圖片描述
例如微服務I發生異常,請求阻塞,用戶不會得到響應,則tomcat的這個線程不會釋放,於是越來越多的用戶請求到來,越來越多的線程會阻塞:
在這裏插入圖片描述

服務器支持的線程和併發數有限,請求一直阻塞,會導致服務器資源耗盡,從而導致所有其它服務都不可用,最後導致整個系統崩潰不可用,形成雪崩效應。
比如:

2. 解決方案

  • 線程隔離、服務降級
  • 服務熔斷

3. 線程隔離、服務降級

爲了解決雪崩問題,Hystrix 提供了線程隔離(服務隔離)的做法。即爲每一個服務分配一個線程池,比如上面的I服務,線程池裏有5個線程,如果一個請求過來,阻塞在這,則佔用一個線程,有新請求過來時還可以從線程池中再獲取一個線程來訪問I服務,即不會影響後續請求的訪問。不過如果分配給I服務的線程已滿,即線程池中的5條線程都被佔用,後續請求過來時則不會再訪問I服務,而是直接拒絕訪問,引發服務降級。另外,如果當前正在訪問的請求超時,也會進行引發服務降級。
在這裏插入圖片描述

綜上:
Hystrix爲每個依賴服務調用分配一個小的線程池,如果線程池已滿調用將被立即拒絕,默認不採用排隊.加速失敗判定時間。用戶的請求將不再直接訪問服務,而是通過線程池中的空閒線程來訪問服務,如果線程池已滿,或者請求超時,則會進行降級處理,什麼是服務降級?
服務降級:優先保證核心服務,而非核心服務不可用或弱可用。
用戶的請求故障時,不會被阻塞,更不會無休止的等待或者看到系統崩潰,至少可以看到一個執行結果(例如返回友好的提示信息) 。服務降級雖然會導致請求失敗,但是不會導致阻塞,而且最多會影響這個依賴服務對應的線程池中的資源,對其它服務沒有響應。

觸發Hystix服務降級的情況:

  • 線程池已滿
  • 請求超時

4. 服務熔斷

熔斷機制的原理很簡單,像家裏的電路熔斷器,如果電路發生短路能立刻熔斷電路,避免發生災難。在分佈式系統中應用這一模式之後, 服務調用方可以自己進行判斷某些服務反應慢或者存在大量超時的情況時,能夠主動熔斷,防止整個系統被拖垮。不同於電路熔斷只能斷不能自動重連,Hystrix 可以實現彈性容錯,當情況好轉之後,可以自動重連。這通過斷路的方式,可以將後續請求直接拒絕掉,一段時間之後允許部分請求通過,如果調用成功則回到電路閉合狀態,否則繼續斷開。
當Hystrix Command請求後端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會發送到後端服務. 斷路器保持在開路狀態一段時間後(默認5秒), 自動切換到半開路狀態(HALF-OPEN).這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN). Hystrix的斷路器就像我們家庭電路中的保險絲, 一旦後端服務不可用, 斷路器會直接切斷請求鏈, 避免發送大量無效請求影響系統吞吐量, 並且斷路器有自我檢測並恢復的能力.

熔斷狀態機3個狀態:

  • Closed:關閉狀態,所有請求都正常訪問。
  • Open:打開狀態,所有請求都會被降級。Hystix會對請求情況計數,當一定時間內失敗請求百分比達到閾值,則觸發熔斷,斷路器會完全打開。默認失敗比例的閾值是50%,請求次數最少不低於20次。
  • Half Open:半開狀態,open狀態不是永久的,打開後會進入休眠時間(默認是5S)。隨後斷路器會自動進入半開狀態。此時會釋放部分請求通過,若這些請求都是健康的,則會完全關閉斷路器,否則繼續保持打開,再次進行休眠計時

二、項目實踐

以下所有操作都在服務消費者(EurekaServerConsumer) 中操作

1. 引入依賴

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

2.啓動類上添加註解 @EnableCircuitBreaker 或者將三個註解合併 使用 @SpringCloudApplication 註解

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker   // 開啓熔斷器
public class EurekaServerConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerConsumerApplication.class, args);
    }
    
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

在這裏插入圖片描述

1. 線程隔離、服務降級

1. 針對單一方法的熔斷

  1. 編寫服務降級邏輯,即編寫一個熔斷方法 callTaxiFallback,規則是要和被熔斷的方法有同樣的參數列表和返回參數。
  2. 在需要熔斷的方法上加上註解 @HystrixCommand(fallbackMethod = "fallbackMethod") 其中 fallbackMethod 指定了對應的熔斷方法。
@RequestMapping("passenger")
@RestController
public class PassengerController {
    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("callTaxi")
    @HystrixCommand(fallbackMethod = "callTaxiFallback")    // 指定熔斷方法
    public String callTaxi(String msg) {
        String url = "http://eureka-server-provider/driver/takeOrder";  // 通過 Eureka 服務註冊中心的服務名稱來調用
        // post方式嗲用傳遞參數
        MultiValueMap<String,String> multiValueMap =  new LinkedMultiValueMap<String,String>();
        multiValueMap.add("msg", msg);
        // 訪問服務
        String result = restTemplate.postForObject(url, multiValueMap, String.class);
        System.out.println("乘客收到信息: " + result);
        return result;
    }

    // callTaxi 的熔斷方法 - 參數列表和返回值要和代理的方法一致
    public String callTaxiFallback(String msg){
        return "服務請求失敗,請稍後重試";
    }
}

其中
callTaxiFallback 是 callTaxi 的熔斷方法。callTaxi通過 @HystrixCommand(fallbackMethod = "callTaxiFallback") 來指定其對應的熔斷方法。

  1. 關掉所有服務提供者(EurekaServerProvider)的服務,這樣可以模擬出服務超時的情況,隨後我們訪問http://localhost:10010/passenger/callTaxi。訪問結果如下,可以看到,熔斷方法生效。

2. 針對類的熔斷

上面的一種情況已經實現了對方法的熔斷,但是可以預見,實際服務中,會存在很多的接口,我們不可能爲每個方法都寫一個熔斷方法。這時候我們就可以將其配置在類的層面上。

  1. 編寫服務降級邏輯, 即編寫一個熔斷方法 callTaxiFallback 。規則是返回值一定要與被熔斷方法一致
  2. 在需要熔斷的類上加上註解 @DefaultProperties(defaultFallback = "passengerFallback") , defaultFallback 指定熔斷方法。
  3. 需要熔斷的方法上加上註解 @HystrixCommand,不在需要指定熔斷方法,當然如果需要也可以指定,與類上的註解不衝突。注意: 如果方法需要熔斷, @HystrixCommand註解不可省略。可以通過 @HystrixCommand來指定方法是否需要降級。加上需要降級,不加則不降級。
  4. 如下: callTaxi方法使用@HystrixCommand註解標註需要熔斷方法,並且沒有指定熔斷方法, 則使用默認的類指定的熔斷方法 passengerFallback()
    callTaxiNoFallBack 方法沒有使用@HystrixCommand註解標註,則表示該方法不需要熔斷
    callTaxiWithFallBack 方法指定了自己的熔斷方法 callTaxiFallBack
    @RequestMapping("passenger")
    @RestController
    @DefaultProperties(defaultFallback = "passengerFallback")     // 在類上指明統一的失敗降級方法
    public class PassengerController {
        @Autowired
        private RestTemplate restTemplate;
    
        /**
         * 使用默認熔斷方法
         * @param msg
         * @return
         */
        @RequestMapping("callTaxi")
        @HystrixCommand  // 標記該方法需要熔斷
        public String callTaxi(String msg) {
            String url = "http://eureka-server-provider/driver/takeOrder";  // 通過 Eureka 服務註冊中心的服務名稱來調用
            // post方式嗲用傳遞參數
            MultiValueMap<String,String> multiValueMap =  new LinkedMultiValueMap<String,String>();
            multiValueMap.add("msg", msg);
            // 訪問服務
            String result = restTemplate.postForObject(url, multiValueMap, String.class);
            System.out.println("乘客收到信息: " + result);
            return result;
        }
    
        /**
         *  沒有熔斷方法
         * @param msg
         * @return
         */
        @RequestMapping("callTaxiNoFallBack")
        public String callTaxiNoFallBack(String msg) {
            String url = "http://eureka-server-provider/driver/takeOrder";  // 通過 Eureka 服務註冊中心的服務名稱來調用
            // post方式嗲用傳遞參數
            MultiValueMap<String,String> multiValueMap =  new LinkedMultiValueMap<String,String>();
            multiValueMap.add("msg", msg);
            // 訪問服務
            String result = restTemplate.postForObject(url, multiValueMap, String.class);
            System.out.println("乘客收到信息: " + result);
            return result;
        }
    
        /**
         * 自定義熔斷方法
         * @param msg
         * @return
         */
        @RequestMapping("callTaxiWithFallBack")
        @HystrixCommand(fallbackMethod = "callTaxiFallBack")
        public String callTaxiWithFallBack(String msg) {
            String url = "http://eureka-server-provider/driver/takeOrder";  // 通過 Eureka 服務註冊中心的服務名稱來調用
            // post方式嗲用傳遞參數
            MultiValueMap<String,String> multiValueMap =  new LinkedMultiValueMap<String,String>();
            multiValueMap.add("msg", msg);
            // 訪問服務
            String result = restTemplate.postForObject(url, multiValueMap, String.class);
            System.out.println("乘客收到信息: " + result);
            return result;
        }
    
        public String callTaxiFallBack(String msg){
            return "callTaxiFallBack : 服務請求失敗,請稍後重試";
        }
    
        /**
         * 熔斷方法
         * 返回值要和被熔斷的方法的返回值一致
         * 熔斷方法不需要參數
         * @return
         */
        public String passengerFallback(){
            return "passengerFallback : 服務請求失敗,請稍後重試";
        }
    }
    
  5. 測試如下:
    callTaxi方法
    在這裏插入圖片描述
    callTaxiNoFallBack 方法
    在這裏插入圖片描述
    callTaxiWithFallBack 方法
    在這裏插入圖片描述

2. 服務熔斷

  1. 改造之前的服務提供者服務(EurekaServerProvider),如下,不接待叫張三的人,目的是爲了在僞造某種請求失敗的狀況來觸發熔斷。也就是說,這裏如果請求參數是 張三,則請求失敗,如果是其它,則請求成功。
    在這裏插入圖片描述
  2. 爲了讓效果更明顯,我們需要配置一些參數
    下面的配置是,當有四次請求時,則將熔斷機制打開。Hystix會對請求情況計數,當一定時間內失敗請求百分比達到閾值,這裏是百分之五十,則觸發熔斷,斷路器會完全打開,隨後進入休眠時間(這裏配置成了10s)。隨後斷路器進入半開狀態,此時會釋放部分請求通過,若這些請求都是健康的,則會完全關閉斷路器,否則繼續保持打開,再次進行休眠計時
    hystrix:
      command:
        default:
          circuitBreaker:
            requestVolumeThreshold: 4   # 觸發熔斷的最小請求次數,默認20
            errorThresholdPercentage: 50  # :觸發熔斷的失敗請求最小佔比,默認50%
            sleepWindowInMilliseconds: 10000  # 休眠時長,默認是5000毫秒
    
  3. 按照我們第一步的修改,如果服務參數是張三時,則會拒絕請求並拋出異常,如果不是張三則通過。我們可以模擬
    我們使用張三三調用兩次服務沒有問題:
    在這裏插入圖片描述
    隨後我們使用張三 調用兩次服務,服務調用失敗,這時候達到4次的閾值,並且失敗次數兩次,也達到大了50% 的比例,此時熔斷器打開:
    在這裏插入圖片描述
    隨後我們使用張三三 再次調用服務,發現張三三也會失敗,驗證熔斷器已經打開:
    在這裏插入圖片描述
    等一段時間後,這裏是10s,再次訪問張三三便可以訪問,因爲10s後熔斷器已閉合
    在這裏插入圖片描述

補: 一些配置參數

hystrix:
  command:
    default:
      circuitBreaker:
        requestVolumeThreshold: 4   # 觸發熔斷的最小請求次數,默認20
        errorThresholdPercentage: 50  # :觸發熔斷的失敗請求最小佔比,默認50%
        sleepWindowInMilliseconds: 10000  # 休眠時長,默認是5000毫秒
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000  # 設置hystrix的超時時間爲6000ms, Hystix的默認超時時長爲1000ms 超過1000ms的請求會返回錯誤方法

以上:內容部分參考
https://www.cnblogs.com/huangjuncong/p/9026949.html
如有侵擾,聯繫刪除。 內容僅用於自我記錄學習使用。如有錯誤,歡迎指正

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章