1. 程式人生 > >Spring Cloud Hystrix - 服務容錯

Spring Cloud Hystrix - 服務容錯

無法 bug 圖片 pre 保持 ram cep 服務 one

服務容錯和Hystrix

在微服務架構中,由於某個服務的不可用導致一系列的服務崩潰,被稱之為雪崩效應。所以防禦服務的雪崩效應是必不可少的,在Spring Cloud中防雪崩的利器就是Hystrix,Spring Cloud Hystri是基於Netflix Hystrix實現的。Hystrix的目標在於通過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix 具備服務降級、服務容錯、服務熔斷、線程和信號隔離、請求緩存、請求合並以及服務監控等強大功能。

Hystrix中的資源隔離:

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

關於服務降級:

  • 優先核心服務,非核心服務不可用或弱可用
  • 在Hystrix中可通過HystrixCommand註解指定可降級的服務
  • 在fallbackMethod(回退函數)中具體實現降級邏輯。對於查詢操作, 我們可以實現一個fallback方法, 當請求後端服務出現異常的時候, 可以使用fallback方法返回的值. fallback方法的返回值一般是設置的默認值或者來自緩存

觸發降級

本小節我們來模擬一下觸發服務降級的情況,首先在訂單服務項目的pom.xml文件中,加入Spring Cloud Hystrix依賴。如下:

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

添加好依賴後修改一下啟動類的註解。修改後代碼如下:

package org.zero.springcloud.order.server;

import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringCloudApplication
@EnableFeignClients(basePackages = "org.zero.springcloud.product.client")
public class OrderApplication {

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

在controller包中,新建一個 HystrixController ,我們在這個類裏做實驗。在這個類裏,我們調用了商品服務中的查詢商品信息接口。為了模擬服務宕機觸發降級,所以此時我已經把商品服務關閉了。具體代碼如下:

package org.zero.springcloud.order.server.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;

/**
 * @program: sell_order
 * @description: Hystrix Demo
 * @author: 01
 * @create: 2018-08-28 20:10
 **/
@Slf4j
@RestController
@RequestMapping("/hystrix/demo")
public class HystrixController {

    /**
     * 通過@HystrixCommand註解指定可降級的服務,fallbackMethod參數指向的是回調函數,函數名稱可自定義
     *
     * @return String
     */
    @HystrixCommand(fallbackMethod = "fallback")
    @GetMapping("/getProductInfoList")
    public String getProductInfoList() {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder",
                Collections.singletonList("157875196366160022"), String.class);
    }

    /**
     * 觸發降級後的回調函數
     *
     * @return String
     */
    public String fallback() {
        return "太擁擠了, 請稍後重試~";
    }
}

啟動項目,訪問結果如下:
技術分享圖片

從測試結果可以看到,由於商品服務關閉了,導致無法調用相應的接口。觸發了服務降級後,調用了註解中指定的回調函數,並返回了相應的提示。

觸發服務降級不一定是服務調用失敗,因為服務降級的主要觸發原因是拋出了異常,所以只要這個方法中拋出了未被捕獲的異常都會觸發服務降級。如下示例:

@HystrixCommand(fallbackMethod = "fallback")
@GetMapping("/getProductInfoList")
public String getProductInfoList() {
    throw new RuntimeException("發生了異常");
}

在某些情況下,我們可能只需要定義一個默認的回調處理函數即可,那麽我們就可以使用@DefaultProperties註解來定義默認的回調函數,這樣就不需要每個 @HystrixCommand 註解都指定一個回調函數了。如下示例:

package org.zero.springcloud.order.server.controller;

import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;

/**
 * @program: sell_order
 * @description: Hystrix Demo
 * @author: 01
 * @create: 2018-08-28 20:10
 **/
@Slf4j
@RestController
@RequestMapping("/hystrix/demo")
@DefaultProperties(defaultFallback = "defaultFallback")
public class HystrixController {

    /**
     * 定義了@DefaultProperties後,只需通過@HystrixCommand註解指定可降級的服務即可
     *
     * @return String
     */
    @HystrixCommand
    @GetMapping("/getProductInfoList")
    public String getProductInfoList() {
        RestTemplate restTemplate = new RestTemplate();
        return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder",
                Collections.singletonList("157875196366160022"), String.class);
    }

    /**
     * 觸發降級後的回調函數
     *
     * @return String
     */
    public String defaultFallback() {
        return "太擁擠了, 請稍後重試~";
    }
}

超時設置

使用 @HystrixCommand 註解的接口是有一個默認超時時間的,當調用某個服務的耗時超過這個時間也會觸發服務降級,默認的超時時間是1秒。我們也可以去自定義這個超時時間,如下示例:

@HystrixCommand(commandProperties = {
        // 設置超時時間為3秒
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
@GetMapping("/getProductInfoList")
public String getProductInfoList() {
    RestTemplate restTemplate = new RestTemplate();
    return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder",
            Collections.singletonList("157875196366160022"), String.class);
}

Hystrix斷路器

斷路器就像電路中的斷路器一樣,當短路發生時,它第一時刻熔斷,切斷了故障電路,保護其他用電單元。

在分布式架構中,斷路器的作用類似,當某個服務單元發生了故障,通過斷路器的故障監控,直接切斷原來的主邏輯調用,強迫以後的多個服務調用不再訪問遠程服務器,防止應用程序繼續執行或等待超時。熔斷器也可以監控服務單元的錯誤是否已經修正,如果已經修正,應用程序會再次嘗試調用操作。

在微服務架構中,系統被拆分成了一個個小的服務單元,各自運行在自己的線程中,各單元之間通過註冊與訂閱的方式互相遠程調用,此時若網絡故障或是某一服務掛掉則會出現調用延遲,進一步導致調用方的對外服務也出現延遲,如果調用方的請求不斷增加,服務單元線程資源無法釋放,隊列裝滿,最終導致故障的蔓延,故斷路器就是解決這種問題的。

斷路器模式:
技術分享圖片

當Hystrix Command請求後端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會發送到後端服務. 斷路器保持在開路狀態一段時間後(默認5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN). 即有自我檢測並恢復的能力.

代碼示例:

@HystrixCommand(commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 開啟熔斷機制
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 設置當請求失敗的數量達到10個後,打開斷路器,默認值為20
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 設置打開斷路器多久以後開始嘗試恢復,默認為5s
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),  // 設置出錯百分比閾值,當達到此閾值後,打開斷路器,默認50%
})
@GetMapping("/getProductInfoList")
public String getProductInfoList(@RequestParam("number") Integer number) {
    if (number % 2 == 0) {
        return "success";
    }

    RestTemplate restTemplate = new RestTemplate();
    return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder",
            Collections.singletonList("157875196366160022"), String.class);
}

使用配置項

在代碼裏寫配置可能不太方便維護,我們也可以在配置文件中使用配置項進行配置。例如超時時間配置如下:

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

若指定配置某一個方法的超時時間,將default換成相應方法名即可。如下示例:

hystrix:
  command:
    getProductInfoList:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000

斷路器的的配置方式也是一樣的,如下示例:

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
      circuitBreaker:
        enabled: true
        requestVolumeThreshold: 10
        sleepWindowInMilliseconds: 10000
        errorThresholdPercentage: 60

feign-hystrix的使用

我們在訂單服務中,使用了feign組件去調用商品服務實現服務間的通信。而feign內部已包含了hystrix,所以也可以實現服務降級。首先在訂單服務項目的配置文件中,增加如下配置:

feign:
  hystrix:
    enabled: true  # 開啟hystrix

到商品服務項目的client模塊中,新增一個 ProductClientFallback 類,並實現ProductClient接口。代碼如下:

package org.zero.springcloud.product.client;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.zero.springcloud.product.common.DecreaseStockInput;
import org.zero.springcloud.product.common.ProductInfoOutput;

import java.util.List;

/**
 * @program: sell_product
 * @description: 觸發服務降級時會調用相應的方法
 * @author: 01
 * @create: 2018-08-29 21:39
 **/
@Slf4j
@Component
public class ProductClientFallback implements ProductClient {

    @Override
    public List<ProductInfoOutput> productInfoList(List<String> productIdList) {
        log.info("productInfoList() 觸發了服務降級");
        return null;
    }

    @Override
    public void decreaseStock(List<DecreaseStockInput> cartDTOList) {
        log.info("decreaseStock() 觸發了服務降級");
    }
}

然後在 ProductClient 接口的@FeignClient註解裏增加 fallback 屬性,並指定以上編寫的實現類。當某個接口觸發降級時,就會調用實現類裏的方法。代碼如下:

package org.zero.springcloud.product.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.zero.springcloud.product.common.DecreaseStockInput;
import org.zero.springcloud.product.common.ProductInfoOutput;

import java.util.List;

/**
 * @program: sell_order
 * @description: 配置需要調用的接口地址
 * @author: 01
 * @create: 2018-08-19 12:14
 **/
@FeignClient(name = "PRODUCT", fallback = ProductClientFallback.class)
public interface ProductClient {

    /**
     * 調用商品服務-按id查詢商品列表
     * 註意,接口地址需要填寫完整
     *
     * @param productIdList productIdList
     * @return List<ProductInfo>
     */
    @PostMapping("/buyer/product/listForOrder")
    List<ProductInfoOutput> productInfoList(@RequestBody List<String> productIdList);

    /**
     * 調用商品服務-扣庫存
     *
     * @param cartDTOList cartDTOList
     */
    @PostMapping("/buyer/product/decreaseStock")
    void decreaseStock(@RequestBody List<DecreaseStockInput> cartDTOList);
}

編寫完以上的代碼後,不要忘了安裝到maven本地倉庫中,安裝命令如下:

mvn clean -Dmaven.test.skip=true install

回到訂單服務,在啟動類上增加@ComponentScan註解,擴大包掃描範圍:

package org.zero.springcloud.order.server;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@SpringCloudApplication
@ComponentScan(basePackages = "org.zero.springcloud")
@EnableFeignClients(basePackages = "org.zero.springcloud.product.client")
public class OrderApplication {

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

重啟訂單服務項目,訪問創建訂單接口,如下:
技術分享圖片

控制臺輸出如下:
技術分享圖片

註:此時我已關閉了商品服務,所以才會觸發服務降級

如果是超時導致服務降級的話,可以在配置文件中配置feign的超時時間,如下:

feign:
  client:
    config:
      default:
        connectTimeout: 5000
        readTimeout: 5000
        loggerLevel: basic

hystrix-dashboard

hystrix-dashboard是一個可視化的熔斷監視工具,我們本小節來看看如何在項目中使用這個工具。我們以訂單服務項目為例,首先在pom.xml文件中,增加如下依賴:

<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>

在啟動類中,增加@EnableHystrixDashboard註解。代碼如下:

package org.zero.springcloud.order.server;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@EnableHystrixDashboard
@SpringCloudApplication
@ComponentScan(basePackages = "org.zero.springcloud")
@EnableFeignClients(basePackages = "org.zero.springcloud.product.client")
public class OrderApplication {

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

在config包下新建一個 HystrixConfig 配置類,用於配置 HystrixMetricsStreamServlet 。代碼如下:

package org.zero.springcloud.order.server.config;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @program: sell_order
 * @description: 配置HystrixMetricsStreamServlet
 * @author: 01
 * @create: 2018-08-29 22:22
 **/
@Configuration
public class HystrixConfig {
    @Bean
    public HystrixMetricsStreamServlet hystrixMetricsStreamServlet() {
        return new HystrixMetricsStreamServlet();
    }

    @Bean
    public ServletRegistrationBean registration(HystrixMetricsStreamServlet servlet) {
        ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>();
        registrationBean.setServlet(servlet);
        //是否啟用該registrationBean
        registrationBean.setEnabled(true);
        registrationBean.addUrlMappings("/hystrix.stream");
        return registrationBean;
    }
}

完成以上代碼的編寫後,重啟項目,訪問http://localhost:9080/hystrix,會進入到如下頁面中:
技術分享圖片

通過Hystrix Dashboard主頁面的文字介紹,我們可以知道,Hystrix Dashboard共支持三種不同的監控方式:

  • 默認的集群監控:通過URL:http://turbine-hostname:port/turbine.stream開啟,實現對默認集群的監控。
  • 指定的集群監控:通過URL:http://turbine-hostname:port/turbine.stream?cluster=[clusterName]開啟,實現對clusterName集群的監控。
  • 單體應用的監控:通過URL:http://hystrix-app:port/hystrix.stream開啟,實現對具體某個服務實例的監控。
  • Delay:控制服務器上輪詢監控信息的延遲時間,默認為2000毫秒,可以通過配置該屬性來降低客戶端的網絡和CPU消耗。
  • Title:該參數可以展示合適的標題。

我這裏使用的是單體應用的監控,點擊Monitor Stream後,進入到如下頁面,在此頁面可以看到這個項目的請求信息:
技術分享圖片

Spring Cloud Hystrix - 服務容錯