【springcloud】斷路器-Hystrix

爲什麼需要 Hystrix?

在微服務架構中,我們將業務拆分成一個個的服務,服務與服務之間可以相互調用(RPC)。爲了保證其高可用,單個服務又必須集羣部署。由於網絡原因或者自身的原因,服務並不能保證服務的100%可用,如果單個服務出現問題,調用這個服務就會出現網絡延遲,此時若有大量的網絡涌入,會形成任務累計,導致服務癱瘓,甚至導致服務“雪崩”。爲了解決這個問題,就出現斷路器模型。

Hystrix 是一個幫助解決分佈式系統交互時超時處理和容錯的類庫, 它同樣擁有保護系統的能力.

什麼是服務雪崩

分佈式系統中經常會出現某個基礎服務不可用造成整個系統不可用的情況, 這種現象被稱爲服務雪崩效應. 爲了應對服務雪崩, 一種常見的做法是手動服務降級. 而Hystrix的出現,給我們提供了另一種選擇.

服務雪崩應對策略

針對造成服務雪崩的不同原因, 可以使用不同的應對策略:

  1. 流量控制
  2. 改進緩存模式
  3. 服務自動擴容
  4. 服務調用者降級服務

流量控制 的具體措施包括:

  • 網關限流
  • 用戶交互限流
  • 關閉重試

服務雪崩解決辦法

1.服務雪崩的原因

(1)某幾個機器故障:例如機器的硬驅動引起的錯誤,或者一些特定的機器上出現一些的bug(如,內存中斷或者死鎖)。

(2)服務器負載發生變化:某些時候服務會因爲用戶行爲造成請求無法及時處理從而導致雪崩,例如阿里的雙十一活動,若沒有提前增加機器預估流量則會造服務器壓力會驟然增大二掛掉。

(3)人爲因素:比如代碼中的路徑在某個時候出現bug

2.解決或緩解服務雪崩的方案

一般情況對於服務依賴的保護主要有3中解決方案:

(1)熔斷模式:這種模式主要是參考電路熔斷,如果一條線路電壓過高,保險絲會熔斷,防止火災。放到我們的系統中,如果某個目標服務調用慢或者有大量超時,此時,熔斷該服務的調用,對於後續調用請求,不在繼續調用目標服務,直接返回,快速釋放資源。如果目標服務情況好轉則恢復調用。

(2)隔離模式:這種模式就像對系統請求按類型劃分成一個個小島的一樣,當某個小島被火少光了,不會影響到其他的小島。例如可以對不同類型的請求使用線程池來資源隔離,每種類型的請求互不影響,如果一種類型的請求線程資源耗盡,則對後續的該類型請求直接返回,不再調用後續資源。這種模式使用場景非常多,例如將一個服務拆開,對於重要的服務使用單獨服務器來部署,再或者公司最近推廣的多中心。

(3)限流模式:上述的熔斷模式和隔離模式都屬於出錯後的容錯處理機制,而限流模式則可以稱爲預防模式。限流模式主要是提前對各個類型的請求設置最高的QPS閾值,若高於設置的閾值則對該請求直接返回,不再調用後續資源。這種模式不能解決服務依賴的問題,只能解決系統整體資源分配問題,因爲沒有被限流的請求依然有可能造成雪崩效應。

3.熔斷設計

在熔斷的設計主要參考了hystrix的做法。其中最重要的是三個模塊:熔斷請求判斷算法、熔斷恢復機制、熔斷報警

(1)熔斷請求判斷機制算法:使用無鎖循環隊列計數,每個熔斷器默認維護10個bucket,每1秒一個bucket,每個blucket記錄請求的成功、失敗、超時、拒絕的狀態,默認錯誤超過50%且10秒內超過20個請求進行中斷攔截。

(2)熔斷恢復:對於被熔斷的請求,每隔5s允許部分請求通過,若請求都是健康的(RT<250ms)則對請求健康恢復。

(3)熔斷報警:對於熔斷的請求打日誌,異常請求超過某些設定則報警

4.隔離設計

隔離的方式一般使用兩種

(1)線程池隔離模式:使用一個線程池來存儲當前的請求,線程池對請求作處理,設置任務返回處理超時時間,堆積的請求堆積入線程池隊列。這種方式需要爲每個依賴的服務申請線程池,有一定的資源消耗,好處是可以應對突發流量(流量洪峯來臨時,處理不完可將數據存儲到線程池隊裏慢慢處理)

(2)信號量隔離模式:使用一個原子計數器(或信號量)來記錄當前有多少個線程在運行,請求來先判斷計數器的數值,若超過設置的最大線程個數則丟棄該類型的新請求,若不超過則執行計數操作請求來計數器+1,請求返回計數器-1。這種方式是嚴格的控制線程且立即返回模式,無法應對突發流量(流量洪峯來臨時,處理的線程超過數量,其他的請求會直接返回,不繼續去請求依賴的服務)

5 超時機制設計

超時分兩種,一種是請求的等待超時,一種是請求運行超時。

等待超時:在任務入隊列時設置任務入隊列時間,並判斷隊頭的任務入隊列時間是否大於超時時間,超過則丟棄任務。

運行超時:直接可使用線程池提供的get方法

 

Hystrix的功能

1. 服務降級

所有的RPC技術裏面服務降級是一個最爲重要的話題,所謂的降級指的是當服務的提供方不可使用的時候,程序不會出現異常,而會出現本地的操作調。

2. 依賴隔離

在Hystrix中, 主要通過線程池來實現資源隔離. 通常在使用的時候我們會根據調用的遠程服務劃分出多個線程池. 例如調用產品服務的Command放入A線程池, 調用賬戶服務的Command放入B線程池. 這樣做的主要優點是運行環境被隔離開了. 這樣就算調用服務的代碼存在bug或者由於其他原因導致自己所在線程池被耗盡時, 不會對系統的其他服務造成影響. 但是帶來的代價就是維護多個線程池會對系統帶來額外的性能開銷. 如果是對性能有嚴格要求而且確信自己調用服務的客戶端代碼不會出問題的話, 可以使用Hystrix的信號模式(Semaphores)來隔離資源.

3. 服務熔斷

什麼是熔斷機制

熔斷機制,就是下游服務出現問題後,爲保證整個系統正常運行下去,而提供一種降級服務的機制,通過返回緩存數據或者既定數據,避免出現系統整體雪崩效應。在springcloud中,該功能可通過配置的方式加入到項目中。

斷路器(CircuitBreaker)設計模式

當Hystrix Command請求後端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會發送到後端服務. 斷路器保持在開路狀態一段時間後(默認5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN). Hystrix的斷路器就像我們家庭電路中的保險絲, 一旦後端服務不可用, 斷路器會直接切斷請求鏈, 避免發送大量無效請求影響系統吞吐量, 並且斷路器有自我檢測並恢復的能力。

Hystrix的應用

還只用之前的兩個工程,order和product,order調用product的服務

1. 在order服務中加入依賴

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

2.在order服務的啓動類中加入@EnableCircuitBreaker或@EnableHystrix

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker// 同@EnableHystrix
public class SellOrderApplication {

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

2.1 @EnableHystrix中使用了@EnableCircuitBreaker,所以兩個註解都可以

2.2 應用同時使用eureka client和hystrix,可以使用@SpringCloudApplication,簡化註解的使用

3. 參數配置

3.1註解式配置

3.1.1 局部參數配置

      在調用product應用接口的方法上使用@HystrixCommand註解

    @HystrixCommand (fallbackMethod = "fallbackMethod",//回調方法
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),//超時時間
                    //熔斷設置
                    @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
                    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
            }
    )
    @HystrixCommand
    @RequestMapping("getList")
    public List<ProductInfoOutPut> getProducts(Integer i) {

        return productClient.findListByIds(Arrays.asList("123456"));
    }

    public  List<ProductInfoOutPut> fallbackMethod() {
        return new ArrayList<>();
    }

fallbackMethod:調用後端服務失敗時調用的方法來返回結果

HystrixProperty:進行Hystrix的參數配置

    詳細的參數配置可在hystrix-core包下的com.netflix.hystrix.HystrixCommandProperties包中查看

  • execution.isolation.thread.timeoutInMilliseconds:該屬性用來配置 HystrixCommand 執行的超時時間,單位爲毫秒,默認值 1000 ,超出此時間配置,Hystrix 會將該執行命令爲 TIMEOUT 並進入服務降級處理邏輯
  • circuitBreaker.enabled:該屬性用來確定當服務請求命令失敗時,是否使用斷路器來跟蹤其健康指標和熔斷請求,默認值 true
  • circuitBreaker.requestVolumeThreshold:該屬性用來設置在滾動時間窗中,斷路器的最小請求數。例如:默認值 20 的情況下,如果滾動時間窗(默認值 10秒)內僅收到19個請求,即使這19個請求都失敗了,斷路器也不會打開。
  • circuitBreaker.sleepWindowInMilliseconds:該屬性用來設置當斷路器打開之後的休眠時間窗。默認值 5000 毫秒,休眠時間窗結束之後,會將斷路器設置爲"半開"狀態,嘗試熔斷的請求命令,如果依然失敗就將斷路器繼續設置爲"打開"狀態,如果成功就設置爲"關閉"狀態。
  • circuitBreaker.errorThresholdPercentage:該屬性用來設置斷路器打開的錯誤百分比條件。例如,默認值爲 50 的情況下,表示在滾動時間窗中,在請求數量超過 circuitBreaker.requestVolumeThreshold 閾值的請求下,如果錯誤請求數的百分比超過50,就把斷路器設置爲"打開"狀態,否則就設置爲"關閉"狀態。
  • circuitBreaker.forceOpen:該屬性用來設置斷路器強制進入"打開"狀態,會拒絕所有請求,該屬性優先於 circuitBreaker.forceClosed
  • circuitBreaker.forceClosed:該屬性用來設置斷路器強制進入"關閉"狀態,會接收所有請求。

 

3.1.2 全局參數配置

    在類上添加@DefaultProperties註解

方法上加註解的方式,fallback方法的返回值和參數類型必須與該方法一致,否則會報錯,全局的fallback方法則沒有限制

3.2 配置文件配置

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 33000

配置以hystrix.command開頭,後面跟default,代表爲全局配置,如果是針對某個方法的配置,則跟方法名,再後面是配置項

配置後還需要在方法或類上加入@HystrixCommand或@DefaultProperties註解

    @HystrixCommand
    @RequestMapping("getList2")
    public List<ProductInfoOutPut> getProducts2(String i) {
    
        return productClient.findListByIds(Arrays.asList(i));
    }

4. Feign+Hystrix

4.1 在order服務的yml中加入下面配置

feign:
  hystrix:
    enabled: true

4.2在product應用中用於暴露接口的controller中做如下配置

   a:FeignClient註解增加fallback,值爲創建的專門用於處理fallback的類

   b:在該接口中創建fallback內部類,實現該接口,注意添加@Component註解

 

package cn.aaralyn.sellproduct.client;

import cn.aaralyn.sellproduct.common.DecreaseStockInput;
import cn.aaralyn.sellproduct.common.ProductInfoOutPut;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.Arrays;
import java.util.List;

/**
 * @Auther: aaralyn
 * @Date: 2018/7/27 13:42
 * @Description:
 */
@FeignClient(name = "product",//name爲eureka中註冊的服務名稱
        fallback = ProductClient.ProductClientFallback.class)
public interface ProductClient {

    @GetMapping("msg")
    String getProductMsg();

    @PostMapping("product/findListByIds")
    List<ProductInfoOutPut> findListByIds(@RequestBody List<String> productIds);

    @PostMapping("product/decreaseStock")
    void decreaseStock(@RequestBody List<DecreaseStockInput> decreaseStockInputList);

    @Component
    static class ProductClientFallback implements ProductClient{
        @Override
        public String getProductMsg() {
            return "出錯啦";
        }

        @Override
        public List<ProductInfoOutPut> findListByIds(List<String> productIds) {
            ProductInfoOutPut put = new ProductInfoOutPut();
            put.setProductDesrc("出錯啦");
            return Arrays.asList(put);
        }

        @Override
        public void decreaseStock(List<DecreaseStockInput> decreaseStockInputList) {

        }
    }
}

 

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