五. SpringCloud服務降級與熔斷

MPolaris發表於2021-02-28

1. Hystrix斷路器概述

1.1 分散式系統面臨的問題

複雜分散式體系結構中的應用程式有數十個依賴關係,每個依賴關係在某些時候將不可避免地失敗。這就造成有可能會發生服務雪崩。那麼什麼是服務雪崩呢?

多個微服務之間呼叫的時候,假設微服務A呼叫微服務B和微服務C,微服務B和微服務C又呼叫其他的微服務,這就是所謂的“扇出”(像一把開啟的摺扇)。如果扇出的鏈路上某個微服務的呼叫響應時間過長或者不可用,對微服務A的呼叫就會佔用越來越多的系統資源,進而引起系統崩潰,這就是所謂的”雪崩效應“。也就是系統的 高可用 受到了破壞。

對於高流量的應用來說,單一的後端依賴可能會導致所有伺服器上的所有資源在幾秒內飽和。比失敗更糟的是,這些應用程式還可能導致 服務之間的延遲增加,備份佇列,執行緒和其他系統資源緊張,導致整個系統傳送更過的級聯故障。這些都表示需要對故障和延遲進行隔離和管理,以便 單個依賴關係的失敗,不能取消整個應用程式或系統

所以,通常當發現一個模組下的某個例項失敗後,這時候這個模組依然還會接受流量,然後這個有問題的模組還呼叫了其他的模組,這樣就會發生級聯故障,或者叫做雪崩。而面對這種糟糕的問題,我們就應該採取 服務降級、服務熔斷 等方式來解決。

1.2 Hystrix是什麼

Hystrix是一個用於處理分散式系統的 延遲容錯 的開源庫,在分散式系統裡,許多依賴不可避免的會呼叫失敗,比如超時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導致整個服務失敗,避免級聯故障,以提高分散式系統的彈性

“斷路器”本身是一種開關裝置,當某個服務單元發生故障之後,通過斷路器的故障監控(類似於物理的熔斷保險絲),向呼叫方返回一個符合預期的、可處理的備選響應(FallBack),而不是長時間的等待或者丟擲呼叫方無法處理的異常,這樣就保證了服務呼叫方的執行緒不會被長時間、不必要的佔用,從而避免了故障在分散式系統中的蔓延,乃至雪崩。

1.3 Hystrix作用

主要有服務降級、服務熔斷、接近實時的監控、限流、隔離等等,其官方文件參考。當然Hystrix現在已經停更了,雖然有一些替代品,但是學習Hystrix及其裡面的思想還是非常重要的!

1.4 Hystrix重要概念

服務降級(Fall Back) 假設微服務A要呼叫的服務B不可用了,需要服務B提供一個兜底的解決方法,而不是讓服務A在那裡傻等,耗死。不讓客戶端等待並立刻返回一個友好提示,比如像客戶端提示伺服器忙,請稍後再試等。哪些情況會觸發服務降級呢?比如 程式執行異常超時服務熔斷觸發服務降級執行緒池/訊號量打滿也會導致服務降級。

服務熔斷(Break) 服務熔斷就相當於物理上的熔斷保險絲。類比保險絲達到最大服務訪問後,直接拒絕訪問,拉閘斷電,然後呼叫服務降級的方法並返回友好提示。

**服務限流(Flow Limit) ** 秒殺高併發等操作,嚴禁一窩蜂的過來擁擠,大家排隊,一秒鐘N個,有序進行。

2. Hystrix案例

2.1 服務提供者8003模組

建Module

cloud-provider-hystrix-payment8003

pom.xml

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

yml配置檔案 / 主啟動類

server:
  port: 8003

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      # 單機版
      defaultZone: http://localhost:7001/eureka # 入駐的服務註冊中心地址
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8003 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8003.class,args);
    }
}

業務類

  • service
@Service
public class PaymentService {
    /**能夠正常訪問的方法*/
    public String paymentInfo_OK(Integer id) {
        return "執行緒池: " + Thread.currentThread().getName()
                + " paymentInfo_OK,id: " + id + "\t" + "哈~";
    }

    /**模擬出錯的方法*/
    public String paymentInfo_FAIL(Integer id) {
        int timeNumber = 3;
        //暫停幾秒鐘執行緒,程式本身沒有錯誤,就是模擬超時
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "執行緒池: " + Thread.currentThread().getName()
                + " paymentInfo_FAIL,id: " + id + "\t" + "哈~ "
                + "耗時" + timeNumber + "s";
    }
}
  • controller
@RestController
@Slf4j
@RequestMapping("/payment/hystrix")
public class PaymentController {
    @Resource
    private PaymentService paymentService;

    @Value("$server.port")
    private String serverPort;

    @GetMapping("ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id) {
        String result = paymentService.paymentInfo_OK(id);
        log.info("===> result: " + result);
        return result;
    }

    @GetMapping("timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id) {
        String result = paymentService.paymentInfo_FAIL(id);
        log.info("===> result: " + result);
        return result;
    }
}

也就是說cloud-provider-hystrix-payment服務提供了兩個方法,paymentInfo_OK 方法可以很快訪問,paymentInfo_TimeOut 方法我們模擬了一個複雜的業物邏輯,通過執行緒休眠的方式使其模擬一個需要執行3秒的服務方法。

在啟動了註冊中心和8003服務後,我們對服務的 paymentInfo_OK(下面用OK代替) 和 paymentInfo_TimeOut(下面用TO代替)分別進行訪問,我們發現http://localhost:8003/payment/hystrix/ok/1 可以很快的訪問,而http://localhost:8003/payment/hystrix/timeout/1 每次訪問大約需要3秒的時間。

上述為根基平臺,開始演示:正確 => 錯誤 => 降級熔斷 => 正確

2.2 8003高併發測試

服務提供方自測壓力測試:

需要3秒的複雜業務邏輯 TO 訪問時,需要時間很少的 OK 是完全可以正常訪問的,但是在高併發的情況下,也就是說 TO 有很多訪問量的時候,OK 還能夠這麼正常的訪問嗎?下面我們用 Jmeter 進行高併發壓力測試,用20000個請求都去訪問 TO 服務。

在 Jmeter 中新建一個執行緒組:測試Hystrix用來模擬高併發訪問 TO 服務,執行緒組配置引數如下:

image-20210227003830057

然後我們用該執行緒組傳送HTTP請求給 TO 服務,建立如下的HTTP請求進行壓力測試:

image-20210227004313624

我們觀察8001服務的後端控制檯,可以看到對 TO 服務進行了大量的訪問。而此時我們再去訪問 OK 服務時什麼樣的呢?=> 可以看到,OK 服務無法像之前一樣很快能夠得到訪問,這裡我們模擬的是20000的訪問量(沒敢模擬數字太大的訪問量,怕把系統直接搞死),實際中可能會有遠大於20000的訪問量,當訪問量更多的時候,甚至可能卡死服務,原因就是 Tomcat的預設的工作執行緒數被打滿了,沒有多餘的執行緒來分解壓力和處理

而剛才做的壓力測試還只是服務提供方8001自己實現的測試,如果此時是外部的服務消費方80來訪問該服務,那麼服務消費方只能夠進行乾等,消費方顯然會對這樣的等待時間不滿意,服務提供方很有可能直接被拖死。我們發現8001自測都會出現問題,那如果我們再用服務消費方測試呢?

2.3 服務消費者80模組進行壓力測試

新建一個Module:cloud-consumer-feign-hystrix-order80作為服務消費方,服務消費方利用feign訪問提供方的服務,編寫對應的service介面如下:

@Component
@FeignClient("CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_FAIL(@PathVariable("id") Integer id);
}

然後編寫其Controller:

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_FAIL(id);
        return result;
    }
}

將80服務啟動,用http://localhost/consumer/payment/hystrix/ok/1 對服務提供方的 OK 服務進行訪問,再進行壓力測試,和之前一樣,無法迅速訪問該服務,如果壓力測試中的執行緒數更多的時候,很可能會造成超時錯誤,出現以下錯誤提示:

image-20210227202938314

故障原因:8003同一層次的其他介面服務被困死,因為Tomcat執行緒池裡面的工作執行緒已經被擠佔完畢,80此時再呼叫8003,必然導致客戶端訪問響應緩慢。正是因為出現了這種現象,所以我們才需要 服務降級、容錯、服務限流 等技術。

2.4 發現問題

要解決的問題:

  • 超時導致伺服器變慢(轉圈) => 超時不再等待
  • 出錯(當機或程式執行出錯) => 出錯要有兜底

解決方案:

  • 對方服務(8003)超時了,呼叫者(80)不能一直卡死等待,必須有服務降級
  • 對方服務(8003)當機了,呼叫者(80)不能一直卡死等待,必須有服務降級
  • 對方服務(8003)OK,呼叫者(80)自己出故障或有自我要求(自己的等待時間小於服務提供者),自己處理降級

3. Hystrix服務降級Fall Back

3.1 服務端服務提供方的服務降級

降級的配置用 @HystrixCommand 註解,在服務提供方自身找問題,設定自身呼叫超時時間的峰值,在峰值內可以正常執行,超過了峰值需要有兜底的方法處理,用作服務降級。

首先在服務提供方的業務類上啟用 @HystrixCommand實現報異常後如何處理,也就是一旦呼叫服務方法失敗並丟擲了錯誤資訊後,會自動呼叫 @HystrixCommand 標註好的fallbackMethod服務降級方法。在服務提供方的service中我們修改 TO 服務:

@Service
public class PaymentService {
    /**能夠正常訪問的方法*/
    public String paymentInfo_OK(Integer id) {
        return "執行緒池: " + Thread.currentThread().getName()
                + " paymentInfo_OK,id: " + id + "\t" + "哈~";
    }

    /**模擬出錯的方法*/
    @HystrixCommand(fallbackMethod = "paymentInfo_FailHandler",
                    commandProperties = {
            @HystrixProperty(
                    name = "execution.isolation.thread.timeoutInMilliseconds",
                    value = "3000")
    })
    public String paymentInfo_FAIL(Integer id) {
        //int age = 10/0; //模擬異常,也會兜底
        int timeNumber = 5;
        
        //暫停幾秒鐘執行緒,程式本身沒有錯誤,就是模擬超時
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "執行緒池: " + Thread.currentThread().getName()
                + " paymentInfo_FAIL,id: " + id + "\t" + "哈~ "
                + "耗時" + timeNumber + "s";
    }

    /** 定製服務降級方法*/
    public String paymentInfo_FailHandler(Integer id) {
        return "系統忙,請稍後再試";
    }
}

然後在主啟動類上新增 @EnableCircuitBreaker 註解對熔斷器進行啟用,TO服務的訪問時間5秒,而我們用Hystrix配置的時間峰值為3秒,也就是當服務超時或服務出錯時,會訪問我們設定的fallbackMethod服務降級方法,再次訪問TO服務,我們發現其執行的方法確實為服務降級方法:

image-20210227234449381

注意:我們自己配置過的熱部署方式對Java程式碼的改動明顯,但對 @HystrixCommand內屬性的修改建議重啟微服務(有時不能及時生效)

3.2 客戶端服務消費方的服務降級

既然服務的提供方可以進行降級保護,那麼服務的消費方,也可以更好的保護自己,也可以對自己進行降級保護,也就是說Hystrix服務降級既可以放在服務端(服務提供方),也可以放在客戶端(服務消費方),但是!通常是用客戶端做服務降級,下面在服務消費方即客戶端配置自己的服務降級保護,修改80消費方的配置檔案,新增如下配置已使其支援Hystrix:

feign:
  hystrix:
    enabled: true

在80消費方的主啟動類上新增 @EnableHystrix 啟用Hystrix服務。然後在80的Controller中同樣加入@HystrixCommand註解已實現服務降級:

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }


    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_FailHandler",
            commandProperties = {
                    @HystrixProperty(
                            name = "execution.isolation.thread.timeoutInMilliseconds",
                            value = "1500")
            })
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_FAIL(id);
        return result;
    }

    /** 定製服務降級方法*/
    public String paymentInfo_FailHandler(Integer id) {
        return "我是消費者80,系統忙,請稍後再試";
    }
}

也就是說如果消費方訪問服務提供方的時間超過了1.5秒,那麼就會訪問自己的降級服務方法。

3.3 統一全域性服務降級方法

而當前的這種處理方式是有問題的,也就是每個業務方法都對應了一個服務降級方法,這會導致程式碼膨脹,所以我們應該定義一個統一的服務降級方法,統一的方法和自定義的方法分開。而且我們將服務降級方法和業務邏輯混合在了一起,這會導致程式碼混亂,業務邏輯不清晰。

對於第一個問題,我們可以用feign介面中的 @DefaultProperties(defaultFallback = "") 註解來配置全域性的服務降級方法,也就是說 自己配置過@HystrixCommand(fallbackMethod = "")fallbackMethod方法的採用自己配置的服務降級方法,而沒有配置過的就採用@DefaultProperties(defaultFallback = "") 配置的全域性的服務降級方法。這樣的話通用的服務降級方法和獨享的服務降級方法分開,避免了程式碼膨脹,合理減少了程式碼量,修改服務消費方80的Controller入下:

@RestController
@Slf4j
//沒有特別指明就用這個統一的
@DefaultProperties(defaultFallback = "payment_Global_FailHandler")
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }


    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    //特別指明使用哪一個兜底方法
//    @HystrixCommand(fallbackMethod = "paymentInfo_FailHandler",
//            commandProperties = {
//                    @HystrixProperty(
//                            name = "execution.isolation.thread.timeoutInMilliseconds",
//                            value = "1500")
//    })
    @HystrixCommand //沒有具體指明就使用全域性的
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_FAIL(id);
        return result;
    }

    /** 定製服務降級方法*/
    public String paymentInfo_FailHandler(Integer id) {
        return "我是消費者80,系統忙,請稍後再試";
    }

    /**
     * 全域性服務降級方法
     * @return
     */
    public String payment_Global_FailHandler() {
        return "全域性異常處理資訊";
    }
}

這裡需要注意的是,無論是否配置了定製服務降級方法,都要在其服務上加入註解 @HystrixCommand, 否則服務降級和該服務沒關係。

而對於第二個問題, 我們可以為Feign客戶端定義的介面新增一個服務降級處理的實現類即可實現解耦,我們的80客戶端已經有了PaymentHystrixService介面,我們新建一個類PaymentFallbackService實現該介面,並重寫介面中的方法,為介面裡的方法進行異常處理,並且我們在PaymentHystrixService@FeignClient註解中宣告其服務降級方法所在的類:

@Service
//當出現錯誤是到PaymentFallbackService類中找服務降級方法
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
              fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_FAIL(@PathVariable("id") Integer id);
}
@Service
public class PaymentFallbackService implements PaymentHystrixService{
    @Override
    public String paymentInfo_OK(Integer id) {
        return "PaymentHystrixService paymentInfo_OK出現異常";
    }

    @Override
    public String paymentInfo_FAIL(Integer id) {
        return "PaymentHystrixService paymentInfo_Fail出現異常";
    }
}

然後我們將Controller中耦合的程式碼都取消:

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_OK(id);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_FAIL(id);
        return result;
    }
}

然後我們關閉8001服務提供方服務,模擬伺服器當機,在服務訪問出現錯誤時,訪問了我們配置的PaymentFallbackService類中的服務降級方法,這樣就實現了程式碼的解耦,使業務邏輯不再混亂。

4. Hystrix服務熔斷Break

4.1 熔斷機制概述

服務的降級 => 進而熔斷 => 恢復呼叫鏈路

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,當扇出鏈路的某個微服務出錯不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的呼叫,也就是說服務熔斷會導致服務降級,快速返回錯誤的響應資訊。當檢測到該節點微服務呼叫響應正常後,恢復呼叫鏈路。也就是說,服務熔斷在服務好了之後會重新允許訪問服務。

在SpringCloud框架中,熔斷機制通過Hystrix實現。Hystrix會監控微服務間的呼叫狀況,當失敗的呼叫到一定閾值,預設是5秒內20次呼叫失敗,就會啟動熔斷機制。熔斷機制的註解是 @HystrixCommand。關於熔斷機制,具體可以參考論文CircuitBreaker

image-20210228140850191
4.2 例項

在8003服務提供方的Service中新增如下程式碼:

/**
 * 服務熔斷
 * fallbackMethod                               服務降級方法
 * circuitBreaker.enabled                       是否開啟斷路器
 * circuitBreaker.requestVolumeThreshold        請求次數
 * circuitBreaker.sleepWindowInMilliseconds     時間視窗期
 * circuitBreaker.errorThresholdPercentage      失敗率達到多少後跳閘
 *
 * 以下配置意思是在10秒時間內請求10次,如果有6次是失敗的,就觸發熔斷器
 *
 * 註解@HystrixProperty中的屬性在com.netflix.hystrix.HystrixCommandProperties類中檢視
 */
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", 
       commandProperties = {
             @HystrixProperty(name = "circuitBreaker.enabled", 
                           value = "true"),
             @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", 
                           value = "10"),
             @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", 
                           value = "10000"),
             @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",
                           value = "60")
       })
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    if (id < 0) {
        throw new RuntimeException("===> id 不能為負數");
    }
    //hutool工具類的使用,等價於UUID.randomUUID().toString()
    String serialNumber = IdUtil.simpleUUID();
    return Thread.currentThread().getName() + " 呼叫成功,流水號: " + serialNumber;
}

/**
 * 服務熔斷觸發的服務降級方法
 * @param id
 * @return
 */
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
    return "id 不能為負數,請稍後再試。id:" + id;
}

@HystrixCommand 註解中配置熔斷機制的引數,配置的引數含義如下:

屬性名 含義 預設值
circuitBreaker.enabled 是否開啟斷路器 true
circuitBreaker.requestVolumeThreshold 請求次數 20次
circuitBreaker.sleepWindowInMilliseconds 時間視窗期 5000ms
circuitBreaker.errorThresholdPercentage 失敗率達到多少後跳閘 50%

這些屬性名的具體含義以及其預設值可以在 com.netflix.hystrix.HystrixCommandProperties 類中進行檢視。而我們在service中配置的意思就是在10秒時間內請求10次,如果有6次是失敗的,就觸發熔斷器

在Controller中新增該服務:

@GetMapping("circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Long id) {
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("====> result:" + result);
    return result;
}
4.3 測試

根據我們的業物邏輯,也就是當我們的id為整數時,服務可以正常訪問,而當id為負數時,訪問服務出錯。我們先訪問 http://localhost:8003/payment/hystrix/circuit/11 代表正確的服務請求,可以發現一切正常!

然後我們進行大量的錯誤訪問,強行觸發服務熔斷,然後在進行正確的訪問。我們發現在進行超出我們閾值的錯誤訪問後,觸發了服務熔斷,即使再進行正確的訪問也無法進行,但是一定時間後,正確的服務訪問又可以順利進行,這就是服務熔斷的整體過程:在觸發了服務熔斷後,先進行服務的降級,再逐漸恢復呼叫鏈路

4.4 總結
image-20210228142306949

結合官網中對熔斷機制的描述,其熔斷過程可以如下描述:

熔斷器開啟和關閉的精確方式如下:

  • 假設電路上的訪問達到某個閾值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())…
  • 並假設誤差百分比超過閾值誤差百分比(HystrixCommandProperties.circuitBreakerErrorThresholdPercentage())…
  • 然後,斷路器從CLOSEDOPEN,觸發熔斷機制。
  • 當它斷開時,它會使針對該斷路器的所有請求短路。
  • 經過一段時間(HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds())後,下一個單個請求被允許通過(這是HALF-OPEN狀態)。如果請求失敗,斷路器將OPEN在睡眠視窗期間返回到該狀態。如果請求成功則斷路器切換到CLOSED,並且第一條中的邏輯再次接管。

也就是在熔斷機制中,熔斷器分為三個狀態:

狀態 說明
熔斷器開啟OPEN 請求不再進行呼叫當前服務,內部設定時鐘一般為MTTR(平均故障處理時間),當開啟時長達到所設時鐘則進入半熔斷狀態(HALF-OPEN)。
熔斷器關閉CLOSED 熔斷關閉不會對服務進行熔斷。
熔斷器半開HALF-OPEN 部分請求根據規則呼叫當前服務,如果請求成功且符合規則則認為當前服務恢復正常,關閉熔斷。

下面是官網上的熔斷器流程圖:20200522001425174

那麼熔斷器在什麼情況下開始起作用呢?

涉及到熔斷器的三個重要引數:快照時間窗,請求總數閾值,錯誤百分比閾值

  • 快照時間視窗期circuitBreaker.sleepWindowInMilliseconds:熔斷器是否開啟需要統計一些請求和錯誤資料,而統計的時間範圍就是快找時間窗,預設為最近的10秒;

  • 請求總數閾值circuitBreaker.requestVolumeThreshold:在快照時間窗內,必須滿足請求總數閾值才有資格觸發熔斷,預設為20次,這意味著在快照時間窗規定的時間內,如果該Hystrix命令的呼叫次數不足20次,即使所有請求都超時或其他原因失敗,熔斷器都不會開啟;

  • 錯誤百分比閾值circuitBreaker.errorThresholdPercentage:當請求總數在快照時間窗內超過了閾值,且在這些呼叫中,超過錯誤百分比閾值比例的錯誤呼叫,熔斷器就會開啟。

在熔斷器開啟後

熔斷器開啟後再有請求呼叫的時候,將不會呼叫主邏輯,而是直接呼叫服務降級的方法,實現了自動發現錯誤並將降級邏輯切換為主邏輯,減少響應延遲的效果。

原來的主邏輯如何恢復呢? => 當熔斷器開啟後,對主邏輯進行熔斷之後,Hystrix會啟動一個 休眠時間窗 , 在這個時間窗內,降級邏輯是臨時的主邏輯,當休眠時間窗到期,熔斷器會進入半開狀態,釋放一次請求到原來的主邏輯上,如果此次請求能夠正常訪問,則熔斷器會進入閉合狀態,從而恢復主邏輯,如果註冊請求依然有問題,則熔斷器繼續保持開啟狀態,並且休眠時間窗重新計時。

Hystrix所有常用配置

image-20210228161057425 image-20210228161128573

服務限流在Alibaba的Sentinel框架中會詳解

5. Hystrix工作流程

官網說明(Hystrix怎麼實操降級熔斷限流

整體的Hystrix工作流程圖入下:

20200522001442973

步驟說明:

image-20210228160554789

6. Hystrix服務監控Hystrix Dashboard

6.1 理解

除了隔離依賴服務的呼叫以外,Hystrix還提供了準實時的呼叫監控——Hystrix Dashboard,Hystrix會持續的記錄所有通過Hystrix發起的請求的執行資訊,並以統計報表和圖形的形式展示給使用者,包括每秒執行多少請求,多少成功,多少失敗等。SpringCloud也提供了Hystrix Dashboard的整合,對監控內容轉化成視覺化介面。

6.2 使用步驟

新建Module:cloud-consumer-hystrix-dashboard9001作為Hystrix Dashboard服務

新增Hystrix Dashboard的依賴:

<dependencies>
    <!-- Hystrix Dashboard -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <!-- actuator監控 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- 通用配置 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

寫配置檔案application.yml,加個埠即可:

server:
  port: 9001

編寫主啟動類,在主啟動類上新增@EnableHystrixDashboard註解開啟Hystrix Dashboard功能:

@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class);
    }
}

注意:所有的服務提供方微服務(如我們的8001/8002/8003)都需要監控依賴配置:

<!--actuator監控資訊完善-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

訪問 http://localhost:9001/hystrix 我們就可以看見Hystrix Dashboard的圖形化介面

注意:新版本Hystrix需要在主啟動類中指定監控路徑(否則會報錯:Unable to connect to Command Metric Stream)。為了讓服務提供方的服務能被Hystrix Dashboard監控到,需要在提供方服務的主啟動類中新增如下配置:

/**
 * 此配置是為了服務監控而配置,與服務容錯本身無關,springcloud升級後的坑
 * ServletRegistrationBean因為springboot的預設路徑不是"/hystrix.stream",
 * 只要在自己的專案裡配置上下面的servlet就可以了
 */
@Bean
public ServletRegistrationBean getServlet() {
    HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
    ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
    registrationBean.setLoadOnStartup(1);
    registrationBean.addUrlMappings("/hystrix.stream");
    registrationBean.setName("HystrixMetricsStreamServlet");
    return registrationBean;
}

在Hystrix Dashboard的圖形化介面中輸入要監控的服務提供者:

image-20210228154411594

下面就是Dashboard對服務的監控狀態:

image-20210228154635386

如何看?

  • 七色
  • 一圈 實現圓共有兩種含義。它通過顏色的變化代表了例項的健康程度,它的健康度從綠色<黃色<橙色<紅色遞減。該實心圓除了顏色的變化之外,它的大小也會根據例項的請求流量發生變化,流量越大該實心圓也就越大。所以通過該實心圓的展示,就可以在大量的例項中快速的發現 故障例項和高壓力例項。
  • 一線 曲線用來記錄2分鐘內流量的相對變化,可以通過它來觀察到流量的上升和下降趨勢
  • 整圖說明1
image-20210228155330038
  • 整圖說明2
image-20210228155431090
  • 一些複雜的情況
image-20210228155537514

相關文章