Spring Cloud 熔斷器/斷路器 Hystrix

Java EE 目錄:https://blog.csdn.net/dkbnull/article/details/87932809
Spring Cloud 專欄:https://blog.csdn.net/dkbnull/column/info/36820
Spring Boot 專欄:https://blog.csdn.net/dkbnull/column/info/26341


在微服務架構中,業務會被拆分成一個個服務,服務間可以彼此調用。爲了保證服務的高可用性,單個服務通常會被集羣部署,但是由於網絡等原因,服務並不能保證100%可用。如果某個服務出現了問題,那麼調用這個服務就會出現線程阻塞,如果此時又有大量的請求涌入,Servlet容器的線程資源就會被迅速消耗殆盡,最終導致服務癱瘓。服務與服務之間的依賴,導致了故障會傳播,以致於會對整個微服務系統造成影響,導致整個服務癱瘓,這種現象叫做雪崩現象。

如何保證在一個服務出現問題的情況下,不會導致整個服務癱瘓,這就是Hystrix需要做的事情。

0. 開發環境

  • IDE:IntelliJ IDEA 2017.1 x64

  • jdk:1.8.0_91

  • Spring Boot:2.0.9.RELEASE

  • Spring Cloud:Finchley.RELEASE

1. Hystrix簡介

Hystrix是一個實現了斷路器模式的庫,提供了熔斷、隔離、Fallback、cache、監控等功能,能夠在一個或多個依賴出現問題時保證系統依然可用。

我們可以把Hystrix想象成一個保險絲。在我們家庭的電路系統中,外部電路入戶時通常都會加上一個保險絲,當家庭電路系統中某一處發生意外,外部電壓過高,達到保險絲熔點的時候,保險絲就會被熔斷,切斷家庭與外部電路的聯通,進而保障家庭用電系統不會受到損壞。

Hystrix提供的斷路器就有類似功能,當在一定時間段內,服務消費者調用服務提供者的服務,次數達到設定的閾值,並且出錯的次數也達到設置的出錯閾值時,就會進行服務降級,讓服務消費者直接執行本地設置的降級策略,不再調用服務消費者的服務。

Hystrix提供的斷路器具有自我反饋和自我恢復的功能,Hystrix會根據接口調用的情況,讓斷路器在closed,open,half-open三種狀態之間自動切換。

  • open狀態表示打開熔斷,即服務消費者執行本地設置的降級策略,不再調用服務消費者。
  • closed狀態表示關閉熔斷,此時服務消費者直接調用服務提供者。
  • half-open狀態,這是一箇中間狀態,當斷路器處於該狀態時,服務消費者直接調用服務提供者。

2. 新建服務消費者

2.1 新建服務消費者

在這裏插入圖片描述

2.2 引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-consumer-hystrix</artifactId>
    <packaging>jar</packaging>

    <parent>
        <groupId>cn.wbnull</groupId>
        <artifactId>spring-cloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>
</project>

2.3 新建application.yml

server:
  port: 8085
  servlet:
    context-path: /springbootconsumer

spring:
  application:
    name: spring-boot-consumer-hystrix

eureka:
  client:
    register-with-eureka: false
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8090/springcloudeureka/eureka/

2.4 新建啓動類

新建Spring Boot啓動類SpringBootConsumerHystrixApplication,增加如下註解,其中 @EnableHystrix註解表示開啓Hystrix

package cn.wbnull.springbootconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@EnableHystrix
public class SpringBootConsumerHystrixApplication {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

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

2.5新建控制器類

這裏我們在接口方法users()上增加了@HystrixCommand(fallbackMethod = “usersFallback”),@HystrixCommand註解表示對該方法開啓斷路器功能,指定了熔斷方法是usersFallback。

package cn.wbnull.springbootconsumer.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.Map;

@RestController
@Scope("prototype")
public class GatewayController {

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping(value = "/users")
    @HystrixCommand(fallbackMethod = "usersFallback")
    public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
        ResponseEntity<Map> responseEntity = restTemplate.postForEntity("http://spring-boot-provider/springbootprovider/users", request, Map.class);

        return responseEntity.getBody();
    }

    public Map<String, String> usersFallback(Map<String, String> request) throws Exception {
        request.put("fallback", "spring-boot-consumer-hystrix");

        return request;
    }
}

2.6 測試

依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-hystrix。然後打開Postman,配置如下,不斷點擊Send按鈕,可以看到返回信息正常,且兩組返回信息交替出現。
在這裏插入圖片描述
然後我們關閉spring-boot-provider,spring-boot-provider-v2服務,再在Postman中測試,可以看到返回正常,返回信息如下,正是我們剛纔usersFallback()方法中的處理。
在這裏插入圖片描述
這裏如果我們沒有使用Hystrix的話,正常應該是會等待超時,然後返回超時信息。現在啓用Hystrix,當spring-boot-provider和spring-boot-provider-v2服務不可用時,spring-boot-consumer-hystrix調用spring-boot-provider的接口不通,會執行快速失敗,直接返回usersFallback()方法的處理結果,而不是等待超時,防止了線程阻塞。

2.7 修改Hystrix默認超時時間

2.7.1 修改控制器類

修改GatewayController類中users()方法,增加上休眠2秒。

    @PostMapping(value = "/users")
    @HystrixCommand(fallbackMethod = "usersFallback")
    public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
        Thread.sleep(2000);

        ResponseEntity<Map> responseEntity = restTemplate.postForEntity("http://spring-boot-provider/springbootprovider/users", request, Map.class);

        return responseEntity.getBody();
    }

然後我們再依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-hystrix。然後繼續使用Postman測試,發現依舊返回熔斷方法中參數。
在這裏插入圖片描述
這是因爲Hystrix默認的超時時間是1秒,也就是服務消費者在等待服務提供者返回信息時,如果超過1秒沒有得到返回信息的話,就認爲服務提供者故障,直接執行本地設置的降級策略,也就是usersFallback()方法。

我們可以通過配置文件修改Hystrix超時時間

2.7.2 修改Hystrix超時時間

application.yml文件增加如下配置

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

然後我們重啓spring-boot-consumer-hystrix,再次使用Postman測試,返回信息正常。
在這裏插入圖片描述

3. Feign中使用斷路器

Feign中已經默認集成了Hystrix,但是Spring Cloud在D版本之後沒有默認打開。需要在配置文件中配置打開它。

feign.hystrix.enabled=true

3.1 新建Feign服務消費者

在這裏插入圖片描述

3.2 引入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-boot-consumer-feign-hystrix</artifactId>
    <packaging>jar</packaging>

    <parent>
        <groupId>cn.wbnull</groupId>
        <artifactId>spring-cloud-demo</artifactId>
        <version>1.0.0</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
</project>

3.3 新建application.yml

server:
  port: 8086
  servlet:
    context-path: /springbootconsumer

spring:
  application:
    name: spring-boot-consumer-feign-hystrix

eureka:
  client:
    register-with-eureka: false
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8090/springcloudeureka/eureka/

feign:
  hystrix:
    enabled: true

3.4 新建啓動類

package cn.wbnull.springbootconsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SpringBootConsumerFeignHystrixApplication {

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

3.5 新建FeignClient類

這裏熔斷之後的降級處理策略不是指定單個熔斷方法,而是指定一個類。@FeignClient(value = “spring-boot-provider”, fallback = GatewayFallback.class)

package cn.wbnull.springbootconsumer.feign;

import cn.wbnull.springbootconsumer.fallback.GatewayFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.Map;

@Component
@FeignClient(value = "spring-boot-provider", fallback = GatewayFallback.class)
public interface GatewayFeignClient {

    @PostMapping(value = "/springbootprovider/users")
    Map<String, String> users(Map<String, String> request) throws Exception;
}

3.6 新建Fallback類

新建熔斷處理類GatewayFallback,GatewayFallback類需要實現上面的GatewayFeignClient接口,並注入到IoC容器中。

package cn.wbnull.springbootconsumer.fallback;

import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class GatewayFallback implements GatewayFeignClient {

    @Override
    public Map<String, String> users(Map<String, String> request) throws Exception {
        request.put("fallback", "spring-boot-consumer-feign-hystrix");

        return request;
    }
}

3.7 新建控制器類

package cn.wbnull.springbootconsumer.controller;

import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

@RestController
@Scope("prototype")
public class GatewayController {

    @Autowired
    private GatewayFeignClient gatewayFeignClient;

    @PostMapping(value = "/users")
    public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
        return gatewayFeignClient.users(request);
    }
}

3.8 測試

依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-feign-hystrix。然後使用Postman測試,可以看到返回信息正常,且兩組返回信息交替出現。

然後我們關閉spring-boot-provider,spring-boot-provider-v2服務,再在Postman中測試,可以看到返回正常,返回信息如下,正是我們在GatewayFallback類users()方法中的處理。
在這裏插入圖片描述

3.9 修改Hystrix默認超時時間

3.9.1 修改控制器類

修改GatewayController類中users()方法,增加上休眠2秒。

    @PostMapping(value = "/users")
    public Map<String, String> users(@RequestBody Map<String, String> request) throws Exception {
        Thread.sleep(2000);

        return gatewayFeignClient.users(request);
    }

然後我們再依次啓動spring-cloud-eureka,spring-boot-provider,spring-boot-provider-v2,spring-boot-consumer-feign-hystrix。然後使用Postman測試,返回熔斷方法中參數。

3.9.2 修改Hystrix超時時間

Feign默認集成了Ribbon,Ribbon也有一個超時時間,超時時間爲1秒,所以這裏不僅要設置Hystrix的超時時間,還要設置Ribbon的超時時間。

ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000

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

然後我們重啓spring-boot-consumer-feign-hystrix,再次使用Postman測試,返回信息正常。

4. 獲取服務異常信息

到現在,我們已經使用Hystrix解決了服務提供者故障的問題,但是我們還是無法確定故障原因,所以我們需要捕捉提供者拋出的異常。恰巧,Hystrix支持該操作。

4.1 新建FallbackFactory

新建GatewayFallbackFactory類,實現FallbackFactory<T>接口,並注入到IoC容器中。

package cn.wbnull.springbootconsumer.fallback;

import cn.wbnull.springbootconsumer.feign.GatewayFeignClient;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class GatewayFallbackFactory implements FallbackFactory<GatewayFeignClient> {

    @Override
    public GatewayFeignClient create(Throwable throwable) {
        return (request) -> {
            request.put("fallback", "spring-boot-consumer-feign-hystrix by GatewayFallbackFactory");
            request.put("throwable", throwable.toString());

            return request;
        };
    }
}

4.2 修改GatewayFeignClient

修改GatewayFeignClient類註解 @FeignClient

@FeignClient(value = "spring-boot-provider", fallbackFactory = GatewayFallbackFactory.class)

4.3 測試

依次啓動spring-cloud-eureka,spring-boot-consumer-feign-hystrix。然後使用Postman測試,可以看到返回信息如下。

{
  "name": "熔斷測試name",
  "fallback": "spring-boot-consumer-feign-hystrix by GatewayFallbackFactory",
  "throwable": "java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: spring-boot-provider"
}

異常信息也捕捉到了,完美。


GitHub:https://github.com/dkbnull/SpringCloudDemo
微信:https://mp.weixin.qq.com/s/VvuyF5JtYmA0dDyGD4uNIw
微博:https://weibo.com/ttarticle/p/show?id=2309404369957537327375
知乎:https://zhuanlan.zhihu.com/p/65133651



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