SpringCloud學習筆記【十】:Hystrix服務降級、熔斷以及DashBoard圖形化監控

本篇要點

  • 簡單瞭解分佈式系統目前面臨的問題。
  • 簡單瞭解Hystrix服務降級、熔斷、限流的概念。
  • 實戰服務降級和服務熔斷。
  • 理解Hystrix的工做流程。
  • 實戰使用Hystrix圖形化監控頁面。

分佈式系統面臨的問題

服務雪崩:多個微服務之間調用的時候,假設微服務A調用微服務B和微服務C,微服務B和微服務C又調用其餘的微服務,這就是所謂的「扇出」。若是扇出的鏈路上某個微服務的調用響應時間過長或不可用,對微服務A的調用就會佔用愈來愈多的系統資源,進行引發系統崩潰。html

對於高流量的應用來講,單一的後端依賴可能會致使全部服務器上的全部資源都在幾秒鐘內飽和。比失敗更糟糕的是,這些應用程序還可能致使服務之間的延遲增長,備份隊列,線程和其餘系統資源緊張,致使整個系統發生更多的級聯故障java

這些都表示須要對故障和延遲進行隔離和管理,以便單個依賴關係的失敗,不能取消整個應用程序或系統。git

Hystrix概述

官網: https://github.com/Netflix/Hystrix/wikigithub

目前已經進入維護模式。web

Hystrix是啥?

Hystrix是一個用於處理分佈式系統的延遲容錯的開源庫,在分佈式系統裏,許多依賴必不可免的會調用失敗,好比超時、異常等,Hystrix可以保證在一個依賴出問題的狀況下,不會致使總體服務失敗,避免級聯鼓掌,以提升分佈式系統的彈性spring

斷路器自己是一種開關裝置,當某個服務單元發生故障以後,經過斷路器的故障監控(相似熔斷保險絲向調用方返回一個符合預期的、可處理的備選響應(Fallback),而不是常見的等待或者拋出調用方沒法處理的異常,這樣就保證了服務調用方的線程不會被長時間、沒必要要地佔用,從而避免了故障在分佈式系統中的蔓延,乃至雪崩。後端

Hystrix能幹啥?

  • 提供保護並控制延遲和失敗。
  • 中止複雜的分佈式系統中的級聯故障。
  • 快速失敗而且快速恢復。
  • 回退,並在可能的狀況下正常降級。
  • 啓用近乎實時的監控,警報和操做控制。

Hystrix重要概念

服務降級fallback

當服務器壓力劇增的狀況下,根據實際業務狀況及流量,對一些服務和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務器資源以保證核心交易正常運做或高效運做。緩存

服務熔斷break

服務熔斷的做用相似於咱們家用的保險絲,當某服務出現不可用或響應超時的狀況時,爲了防止整個系統出現雪崩,暫時中止對該服務的調用springboot

服務限流flowlimit

限流的目的是爲了保護系統不被大量請求沖垮,經過限制請求的速度來保護系統。服務器

Hystrix演示-構建異常環境

Spring Cloud Netflix# Circuit Breaker:Hystrix Clients

咱們先準備環境,準備一個正常的接口方法和一個模擬延遲五秒的接口方法。

建立新模塊,引入依賴

spring-cloud-starter-netflix-hystrix依賴引入。

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

編寫yml

server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment # 服務名稱!

eureka:
  client:
    #表示是否將本身註冊進EurekaServer默認爲true。
    register-with-eureka: true
    #是否從EurekaServer抓取已有的註冊信息,默認爲true。單節點無所謂,集羣必須設置爲true才能配合ribbon使用負載均衡
    fetchRegistry: true
    service-url:
      #      #單機版
      defaultZone: http://localhost:7001/eureka

編寫主啓動類

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
}

編寫service層

@Service
public class PaymentService {

    // 正常訪問
    public String paymentInfoOk(Integer id) {
        return "線程池:  " + Thread.currentThread().getName() + "  paymentInfo_OK,id:  " + id + "\t" + "O(∩_∩)O哈哈~";
    }
	// 延遲3秒
    public String paymentInfoTimeOut(Integer id) {
        //int age = 10/0;
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "線程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗時(秒): ";
    }
}

編寫Controller層

@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;

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

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

    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
        String result = paymentService.paymentInfoTimeOut(id);
        log.info("*****result: " + result);
        return result;
    }
}

測試構建的環境

依次啓動:7001Eureka服務中心,8001服務,分別訪問如下兩個連接:

壓力測試

當併發請求量足夠大的時候,微服務會集中資源去處理響應較慢的服務,致使其餘原本響應較快的服務被拖累。Tomcat默認的工做線程被打滿,沒有多餘的線程來分解壓力和處理。

若是新建消費者客戶端80,8001同一層次的其餘接口服務被困死,80此時調用8001,客戶端的響應也會變得很慢。

Hystrix演示-服務降級

服務端服務降級

cloud-provider-hystrix-payment8001:在paymentInfoTimeOut方法上使用@HystrixCommand,並定義fallbackMethod方法。

@HystrixCommand(fallbackMethod = "paymentInfoTimeOutHandler", commandProperties = {
    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
})

public String paymentInfoTimeOut(Integer id) {
    //int age = 10/0;
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "線程池:  " + Thread.currentThread().getName() + " id:  " + id + "\t" + "O(∩_∩)O哈哈~" + "  耗時(秒): ";
}

public String paymentInfoTimeOutHandler(Integer id) {
    return "線程池:  " + Thread.currentThread().getName() + "  8001系統繁忙或者運行報錯,請稍後再試,id:  " + id + "\t" + "o(╥﹏╥)o";
}

一旦調用服務方法失敗並拋出了錯誤信息後,會自動調用@HystrixCommand標註好的fallbackMethod調用類中指定的指定方法。

@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")設置了服務自身的上線時間爲3s,超過3s將會調用paymentInfoTimeOutHandler方法。

主啓動類加上激活註解:

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
}

訪問:http://localhost:8001/payment/hystrix/timeout/1接口進行測試,3s以後頁面返回fallbackMethod設置方法的結果。既然超時情況能夠進行兜底處理,那異常狀況呢?

咱們不妨打開int age = 10/0;的註釋,此時會拋出一個算數異常,進行測試,一旦拋出異常,也會fallback。

消費端服務降級

cloud-consumer-feign-hystrix-order80:爲消費者端也配置服務降級。首先配置yml,開啓feign.hystrix.enabled

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

feign:
  hystrix:
    enabled: true

使用@HystrixCommand註解設置服務降級處理方法,和等待上限時間屬性。

@RestController
@Slf4j
public class OrderHystirxController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
        //int age = 10 / 0;
        return paymentHystrixService.paymentInfTimeOut(id);
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消費者80,對方支付系統繁忙請10秒鐘後再試或者本身運行出錯請檢查本身,o(╥﹏╥)o";
    }

}

在主啓動類上加上註解:

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

這時候的情景是:

  • 客戶端的降級策略爲:將要調用服務端接口,且但願1.5s之內就能獲得迴應,不然就會執行paymentTimeOutFallbackMethod方法。
  • 服務端的降級策略爲:服務若是超過5s中才執行fallbackMethod中的方法。

暴露的問題

  • 每一個業務方法都須要一個兜底方法,代碼膨脹!
  • 處理方法和業務邏輯混疊在一塊兒,混亂!

定義默認的fallbackMethod

咱們應當按照全局異常處理的思想,定義一套全局通用的處理降級方法,再此基礎之上,再分別針對不一樣的業務,進行定製降級。

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")
public class OrderHystirxController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

    @HystrixCommand
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfoTimeOut(@PathVariable("id") Integer id) {
        //int age = 10 / 0;
        return paymentHystrixService.paymentInfTimeOut(id);
    }

    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id) {
        return "我是消費者80,對方支付系統繁忙請10秒鐘後再試或者本身運行出錯請檢查本身,o(╥﹏╥)o";
    }

    /**
     * 下面是全局fallback方法
     */
    public String paymentGlobalFallbackMethod() {
        return "Global異常處理信息,請稍後再試,/(ㄒoㄒ)/~~";
    }
}
  • 在Controller層上加上@DefaultProperties(defaultFallback = "paymentGlobalFallbackMethod")。定義默認的fallback處理方法。
  • 若是@HystrixCommand不指定fallbackMethod,將會調用defaultFallback 。

The @HystrixCommand is provided by a Netflix contrib library called 「javanica」. Spring Cloud automatically wraps Spring beans with that annotation in a proxy that is connected to the Hystrix circuit breaker. The circuit breaker calculates when to open and close the circuit and what to do in case of a failure.

To configure the @HystrixCommand you can use the commandProperties attribute with a list of @HystrixProperty annotations.

解決Controller層耦合

咱們首先須要知道,咱們全部須要調用服務端的接口的方法,其實都定義在FeignClient中。那麼,咱們能夠@FeignClient註解中經過fallback指定同一處理降級的服務:PaymentFallbackService,咱們讓這個service實現FeignClient,針對每一個方法提供不一樣的降級策略。

首先確保開啓:

feign:
  hystrix:
    enabled: true
@Component
@FeignClient(value = "cloud-provider-hystrix-payment",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfOk(@PathVariable("id") Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    String paymentInfTimeOut(@PathVariable("id") Integer id);
}
@Component
public class PaymentFallbackService implements PaymentHystrixService {

    @Override
    public String paymentInfOk(Integer id) {
        return "PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
    }

    @Override
    public String paymentInfTimeOut(Integer id) {
        return "PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
    }
}

如何測試

  1. 前後啓動7001,8001。
  2. 訪問:http://localhost/consumer/payment/hystrix/ok/1結果正常。
  3. 此時將8001強制關閉,此時客戶端要想調用服務端必然調用失敗,這時客戶端的服務降級就會效果顯現。

哪些狀況會發出降級

  1. 程序運行異常
  2. 超時
  3. 服務熔斷觸發服務降級
  4. 線程池/信號量打滿也會致使服務降級

Hystrix演示-服務熔斷

熔斷機制概述

熔斷機制是應對雪崩效應的一種微服務鏈路保護機制,當扇出鏈路的某個微服務出錯或不可用或響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速返回錯誤的響應信息。當檢測到該節點微服務調用響應正常後,恢復調用鏈路

SpringCloud中能夠經過Hystrix實現熔斷,Hystrix會監控微服務之間調用的情況,當失敗的調用到必定閾值,缺省是5秒內20次調用失敗,就會啓動熔斷機制。

https://martinfowler.com/bliki/CircuitBreaker.html

熔斷演示,配置參數

在Service層配置熔斷策略:

//=====服務熔斷 10s以內 10次請求有6次失敗 就會開啓斷路器
    @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(Integer id) {
        if (id < 0) {
            throw new RuntimeException("******id 不能負數");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName() + "\t" + "調用成功,流水號: " + serialNumber;
    }

@HystrixProperty中的配置項定義在:com.netflix.hystrix.HystrixCommandProperties

更加詳細的配置介紹:https://github.com/Netflix/Hystrix/wiki/Configuration

在Controller層調用:

//====服務熔斷
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: " + result);
        return result;
    }

測試

熔斷類型

  1. 熔斷打開Open:請求再也不進行調用當前服務,內部設置時鐘通常爲MTTR【平均故障處理時間】,當打開時長達到所設始終則進入半熔斷狀態。
  2. 熔斷關閉Closed:熔斷關閉不會對服務進行熔斷。
  3. 熔斷半開Half Open:部分請求根據規則調用當前服務,若是請求成功且符合規則則認爲當前服務恢復正常,關閉熔斷。

熔斷啥時候起做用

涉及到斷路器的三個重要參數:快照時間窗請求總數閥值錯誤百分比閥值

  1. 快照時間窗sleepWindowInMilliseconds:斷路器肯定是否打開須要統計一些請求和錯誤數據,而統計的時間範圍就是快照時間窗,默認爲最近的10秒。
  2. 請求總數閥值requestVolumeThreshold:在快照時間窗內,必須知足請求總數閥值纔有資格熔斷。默認爲20,意味着在10秒內若是該hystrix命令的調用次數不足20次,即便全部的請求都超時或其它緣由失敗,斷路器都不會打開。
  3. 錯誤百分比閥值errorThresholdPercentage:當請求總數在快照時間窗內超過了閥值,好比發生了30次調用,若是在這30次調用中,有超過15次發生了超時異常,也就是超過50%的錯誤百分比(默認錯誤閥值50%),斷路器會打開。

斷路器開啓或關閉的條件

  1. 當知足必定閾值時(默認10秒內超過20個請求次數)
  2. 當失敗率達到必定時(默認10秒內超過50%的請求失敗)
  3. 到達以上閾值,斷路器將開啓
  4. 當開啓時,全部請求將不會轉發
  5. 一段時間後(默認是5秒),這個時候斷路器是半開狀態,會讓其中一個進行轉發。若是成功,斷路器會關閉,若失敗,繼續開啓。重複4~5

斷路器打開以後,再有請求調用的時候,將不會再調用主邏輯,而是直接調用降級fallback,經過斷路器,實現了自動地發現錯誤並將降級邏輯切換爲主邏輯,減小響應延遲的效果

原來的邏輯如何恢復?

當斷路器打開,對主邏輯進行熔斷以後,hystrix會啓動一個休眠時間窗,在這個時間窗內,降級邏輯臨時的成爲主邏輯,當休眠時間窗到期,斷路器將進入半開狀態,釋放一次請求到原來的主邏輯上,若是這次請求正常返回,那麼斷路器將閉合,主邏輯恢復,若是此次請求依然有問題,斷路器繼續進入打開狀態,休眠時間窗從新計時。

Hystrix工做流程總結

  1. 構建 HystrixCommand【依賴的服務返回單個的結果】 or HystrixObservableCommand【依賴的服務返回多個操做結果】對象【1】。

  2. 執行命令,如下四種方法中的一種【前兩種方法僅適用於簡單的HystrixCommand對象,而不適用於HystrixObservableCommand】【2】:

    1. execute(),同步,從依賴的服務返回一個單一的響應【或在發生錯誤的時候拋出異常】。
    2. queue(),返回Future對象,包含了服務執行結束時要返回的單一結果對象。
    3. observe():返回Observable對象。
    4. toObservable():返回Observable對象,也表明了操做的多個結果。
    K             value   = command.execute();
    Future<K>     fValue  = command.queue();
    Observable<K> ohValue = command.observe();         //hot observable
    Observable<K> ocValue = command.toObservable();    //cold observable
  3. 檢查緩存【3】,若當前命令的請求緩存功能是被啓用的,而且該命令緩存命中,那麼這個緩存的響應將當即以Observable形式返回。

  4. 檢查斷路器是不是打開狀態【4】,若是斷路器打開,則Hystrix不會執行命令,而是處理fallback邏輯【8】,不然檢查是否有可用資源來執行命令【5】。

  5. 線程池/請求隊列/信號量是否已滿【5】,若是命令依賴服務的專有線程池和請求隊列,或信號量已經被佔滿,那麼Hystrix也不會執行命令,而是轉到第【8】步。

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

    1. HystrixCommand.run():返回一個單一的結果,或者拋出異常。
    2. HystrixObservableCommand.constract():返回一個Observable對象來發送多個結果或經過onError發送錯誤通知。
  7. Hystrix向斷路器報告成功、失敗、拒絕和超時等信息,斷路器經過維護一組計數器來統計這些數據,經過這些數據來決定是否要打開斷路器【7】。

  8. 當命令執行失敗時,Hystrix會進入fallback嘗試回退處理,咱們一般稱爲:服務降級。可以引發服務降級處理的狀況:

    1. 熔斷器打開。【4】
    2. 當前命令的線程池、請求隊列、信號量被佔滿的時候。【5】
    3. HystrixCommand.run()或HystrixObservableCommand.constract()拋出異常時。【6】
  9. 當Hystrix命令執行成功以後,它會將處理結果直接返回或是以Observable的形式返回。

Hystrix圖形化DashBoard監控實戰

引入依賴

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

設置監聽9001端口

server:
  port: 9001

添加註解@EnableHystrixDashboard

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

測試

訪問:http://localhost:9001/hystrix,出現如下界面說明配置成功:

實戰監控8001服務熔斷

保證8001的依賴中存在spring-boot-starter-actuator

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

特殊配置

/**
     * 此配置是爲了服務監控而配置,與服務容錯自己無關,spring cloud升級後的坑
     * ServletRegistrationBean由於springboot的默認路徑不是"/hystrix.stream",
     * 只要在本身的項目裏配置上下面的servlet就能夠了
     */
    @Bean
    public ServletRegistrationBean<HystrixMetricsStreamServlet> getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream"); //訪問路徑
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

這時,在Hystrix界面監控框輸入監控的url:http://localhost:8001/hystrix.stream,可能會出現錯誤:

Origin parameter: http://localhost:8001/hystrix.stream is not in the allowed list of proxy host names.  If it should be allowed add it to hystrix.dashboard.proxyStreamAllowList.

提醒咱們設置hystrix.dashboard.proxyStreamAllowList,在yml中設置唄:

server:
  port: 9001

hystrix:
  dashboard:
    proxy-stream-allow-list:
      - "localhost"

啓動8001,按照咱們測試服務熔斷的流程,監控正常運行的效果:

  • 請求示意圖的圓圈越大,表示請求量越多。
  • 曲線表示過去2分鐘的請求變化率。
  • Host、Cluster表示每秒的併發請求數。
  • Hosts表示節點個數,只有一個示例,則爲1。
  • ThreadPools表示監控Hystrix的線程狀態。

源碼下載

本系列文章爲《尚硅谷SpringCloud教程》的學習筆記【版本稍微有些不一樣,後續遇到bug再作相關說明】,主要作一個長期的記錄,爲之後學習的同窗提供示例,代碼同步更新到Gitee:https://gitee.com/tqbx/spring-cloud-learning,而且以標籤的形式詳細區分每一個步驟,這個系列文章也會同步更新。

相關文章
相關標籤/搜索