七、Spring Cloud Netflix Hystrix 服务熔断、限流

官网地址 Spring-cloud-Netflix-Eureka-clients
Spring Cloud Netflix Hystrix: 作为微服务的注册中心,动态感知服务的上下线
spring-cloud-starter-netflix-hystrix
spring-cloud-starter-netflix-eureka-client、 spring-cloud-starter-netflix-eureka-server

一、Hystrix简介及使用

1. 场景

服务熔断:和A股沪深两市的熔断机制;家用电器跳闸类似,目的是为了避免造成更大的风险,起到保护作用;
以微服务的几种场景为例:
场景一:我们的一个请求可能是依赖于多个服务来形成完整链路,如下图依赖 A、H、I、P

场景二:如果其中一个节点出现故障,发起用户请求的线程因为这个节点没有返回结果一直处于阻塞状态的话,线程就无法被释放

场景三:这一时点,如果突然出现大量请求,会造成大量请求堆积,占用掉服务器的所有线程资源,服务资源被消耗殆尽。造成雪崩的效应。
如果服务直接相互多层依赖,也会造成级联失败。

此种场景就可以引入熔断机制,为服务降级。比如熔断直接返回兜底数据、或者进行服务的资源隔离,每个服务分配固定的资源,指定线程池分配固定线程等;

2. 降级策略

熔断也是一种降级策略

  • 熔断的目的是为了起到保护作用
    比如分布式中,A服务调用B服务,当B服务宕机导致A服务请求失败,此时A服务可以选择直接将异常抛给用户,或者返回一个默认的数据或系统繁忙来进行兜底。
  • 降级也可以分为 主动降级;被动降级
      主动降级:618或者双11大促时,可以将评论、点赞等非核心功能关闭。全力满足大促需要
      被动降级:熔断降级、限流降级

3. Hystrix作用

  • 1.对通过第三方客户端库访问的依赖项(通常是通过网络)的延迟和故障进行保护和控制

  • 2.在复杂的分布式系统中阻止级联故障

  • 3.快速失败、快速恢复
    如果某个节点已经出现故障,之后的多次正常请求尝试肯定是不会成功的,那么后续的请求是不是可以不发过去而是直接返回默认数据;此时处于熔断状态
    如果服务处于熔断状态,什么时候恢复呢?是人工恢复还是自动恢复?

  • 4.回退,尽可能优雅的降级
    上一步熔断之后,怎么降级

  • 5.启用近实时监控、警报和操作控制

4. 基于Hystrix实现三种降级策略

服务熔断触发降级:
接口响应超时触发降级:
资源隔离触发降级:

a. 服务熔断触发降级

目的: 用户服务调用订单服务,当订单服务宕机,用户服务后续的请求调用其实一直不通的,此时用户服务可以再尝试调用N次之后,熔断M秒,在这M秒内不再请求订单服务而是直接返回一个兜底数据,

  1. 创建SpringBoot项目,添加Hystrix依赖
    <!--版本号由parent统一配置-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
  1. 创建请求接口
    接口传入参数num,当为偶数时不访问服务直接返回,当为奇数时访问一个端口号不存在的服务,方法上启用熔断配置

import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class HystrixController {

    @Autowired
    RestTemplate restTemplate;

    /**
     * 3种降级方案:
     * a. fallback -> 回退方案(降级处理方案) {@link HystrixCommandProperties}内有所有属性
     *
     * @param num
     * @return
     */
    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 开启熔断
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "5"), //最小请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"), //时间窗口,熔断后这段时间直接返回 callback
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50") // 百分比
    }, fallbackMethod = "myFallBack") // 接口异常返回兜底数据
    @GetMapping("/hystrix/order/{num}")
    public String queryOrder(@PathVariable("num") int num) {
        // 服务宕机,没有启动
        if (num % 2 == 0) {
            return "正常访问";
        }
        return restTemplate.getForObject("http://127.0.0.1:8182/order", String.class);
    }

    // 熔断后调用的方法,参数必须和queryOrder一致;方法名由@HystrixCommand注解的`fallbackMethod`指定
    public String myFallBack(int num) {
        // 返回兜底数据
        return "系统繁忙";
    }

}

  1. 确保SpringBoot项目开启了熔断
    @EnableCircuitBreaker
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;

@EnableCircuitBreaker
@ComponentScan(basePackages = {"com.bigshen.springcloud"})
@EnableFeignClients(basePackages = "com.bigshen.springcloud.demo.client") // 扫描API包提供的FeignClient
@SpringBootApplication
public class UserServiceProviderApplication {

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

}

只启动用户服务,不启动订单服务,这样的话在调用订单服务时就会存在服务熔断。
启动服务后:以入参为1和2的两个接口 http://localhost:8081/hystrix/order/1 http://localhost:8081/hystrix/order/2 为例;
先调用 /hystrix/order/2 接口时,可以正常返回 "正常访问";
之后调用5次 /hystrix/order/1接口,因为rest请求无法访问,尝试调用失败后会调用我们制定的myFallBack方法返回"系统繁忙",且我们指定了5次请求失败后熔断5秒;
然后再调用 /hystrix/order/2时,会发现返回的也是"系统繁忙";
5秒之后再次尝试调用 /hystrix/order/2时,会发现又可以正常返回 "正常访问";

** Hystrix熔断触发降级规则:**

A service failure in the lower level of services can cause cascading failure all the way up to the user. When calls to a particular service exceed 
circuitBreaker.requestVolumeThreshold (default: 20 requests) and the failure percentage is greater than circuitBreaker.errorThresholdPercentage (default: >50%) in
 a rolling window defined by metrics.rollingStats.timeInMilliseconds (default: 10 seconds), the circuit opens and the call is not made. In cases of error and an 
open circuit, a fallback can be provided by the developer.

在同一个滑动窗口10秒内,如果有20次请求,且请求失败率在50%以上,会打开断路器,熔断,后续请求不在处理。
  • 熔断开启之后,后续的正常请求也无法发送过去;
  • 触发熔断的条件,
    "阈值":10秒钟之内,发起了20次请求,失败率超过50%。
    熔断恢复时间:5秒,从熔断开启到后续5秒之内的请求,都不会发起到远程服务端。
  • 熔断会有一个自动恢复。
	@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), // 开启熔断
	@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "5"), //最小请求次数
	@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "5000"), //时间窗口,熔断后5s这段时间直接返回 callback
	@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50") // 百分比,异常率超过50%
	这个配置就是10秒钟之内,发起了5次请求以上,且失败率超过50%,则熔断5秒

正常状态下,断路器处于关闭状态(Closed),
如果调用持续出错或者超时,断路器被打开进入熔断状态(Open),后续一段时间内(5s)的所有调用都会被拒绝(Fail Fast),
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,如果调用仍然失败,则回到熔断状态,如果调用成功,则回到断路器闭合状态;

b. 服务超时触发降级

分别模拟restful接口调用时,正常和超时两种场景

  • 正常响应
    Hystrix设置超时时间为3s,接口响应时间为2s:
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class HystrixController {

    @Autowired
    RestTemplate restTemplate;
    @HystrixCommand(
            commandProperties = {
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")  // 超时时间
            },
            fallbackMethod = "myTimeOutFallBack")
    @GetMapping("/hystrix/timeout")
    public String queryOrderTimeOut() {
        return restTemplate.getForObject("http://127.0.0.1:8082/order", String.class);
    }


    public String myTimeOutFallBack() {
        return "系统请求超时";
    }

}

启动订单服务,order接口逻辑休眠2s:


    @Override
    public String getAllOrders() {
        System.out.println(1);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Shen all orders";
    }

在浏览器Network可以看到,接口正常返回,响应时长为2.+秒

  • 超时响应
    Hystrix设置超时时间为3s,接口响应时间为5s:

重启订单服务,order接口逻辑休眠5s:


    @Override
    public String getAllOrders() {
        System.out.println(1);
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Shen all orders";
    }

可以看到接口请求返回的数据为callback方法的数据,触发了超时熔断,Network里也可以看到接口响应时长为3s

c. 资源隔离触发降级

在微服务的架构中,肯定是不允许出现当某一个节点出现故障,导致整个服务体系不可用的情况,所以需要去隔离每个服务。
隔离也可以从多个方面进行:平台隔离、部署隔离、业务隔离、服务隔离、资源隔离;
CPU、内存、线程池;

这个概念就像是现在的船舱设计一样,如果某个船舱进水,除非达到了这个船舱阈值,一般不能渗入其它船舱去。

结合我们之前上面的图:
容器调用多个服务时,如果某一服务响应故障,同一时点高并发的进行调用,可能会将容器的线程全部消耗。(这里的容器是具体到某一台服务的容器)

为此也可以针对每个服务设置单独的线程池,进行资源隔离。
比如以下A、B、C三块,为A.B为线程池隔离,C块为信号量Semaphore隔离,
DependencyA Server调用异常,超过10个并发量的请求对应线程就会进行阻塞状态,在线程池的阻塞队列中进行排队,而不是直接创建新的线程进行调用,这其实也是利用了 J.U.C 中的线程池AQS原理;
DependencyI Server调用异常,超过5个线程时,其余请求对应线程会进入阻塞对应排队;
DependencyX Server使用的是信号量Semaphore隔离,同一时点10个并发量,底层也是J.U.C的Semaphore及AQS(和停车场类似,停满之后出一辆才能进一辆)

实现线程隔离
其实也是添加相应配置:
用户服务内application.yml:

feign:
  hystrix:
    enabled: true

hystrix:
  command:
    default : #全局配置 feignClient#method(params)
      execution:
        timeout:
          enable: true
        isolation: # execution.isolation.thread.timeoutInMilliseconds配置超时时间,设为3s
          thread:
            timeoutInMilliseconds: 3000
#   除了default默认设置,也可以为每个方法进行个性化设置
    OrderServiceFeignClient#getAllOrders(): #针对具体某个方法进行配置
      execution:
        isolation:
          strategy: SEMAPHORE   # execution.isolation.strategy 隔离策略 信号量; com.netflix.hystrix.HystrixCommandProperties.default_executionIsolationStrategy 枚举值
          semaphore:
            maxConcurrentRequests: 10 # execution.isolation.semaphore.maxConcurrentRequests  最大并发请求数

    OrderServiceFeignClient#insertOrder() :
      execution:
        isolation:
          strategy: THREAD   # execution.isolation.strategy 隔离策略 线程池
  threadpool:
    shen-order-service:
      coreSize : 2
      maxQueueSize : 1000 # 队列长度
      queueSizeRejectionThreshold : 800 # 超过800开始拒绝

ribbon: # 因为openFeign内部是使用ribbon进行负载均衡调用,需要设置ribbon的超时时间
  ReadTimeout: 8000
  ConnectTimeout : 8000

测试场景:可以使用压力测试工具,1s内1000个线程进行访问,然后使用下面的Dashboard工具进行监控,查看正常请求,异常请求,失败率,断路器打开关闭的状态等。

d. 资源隔离的监控,Hystrix-dashboard

在上一步骤中进行资源隔离的配置后,怎么查看hystrix管理的各服务资源消耗呢,其实Hystrix提供了spring-cloud-starter-netflix-hystrix-dashboard组件进行监控。

  • 新建Hastrix监控组件
    a. 新建SpringBoot项目,引入Hystrix-dashboard 依赖:指定端口号为9092
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

<!--可以在Hystrix-Dashboard内进行监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

b. 启用Hystrix Dashboard

@EnableHystrixDashboard
@SpringBootApplication 
...
...

c. 启动服务访问 http://localhost:9092/hystrix
会有一个略显粗糙的监控页面:

在这个页面输入对应使用了Hystrix的服务地址,可以为每个服务进行监控

  • 对应的,要想用户服务可以在Hystrix Dashboard内监控到,需要做相应更改:
  1. pom.xml中添加监控依赖

        <!--可以在Hystrix-Dashboard内进行监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
  1. application.yml 添加配置
management:
  endpoints:
    web:
      exposure:
        include: refresh,hystrix.stream # 配置hystrix.stream,提供 hystrix.stream 接口获取服务的状态信息

此时,重启用户服务,访问 http://localhost:8081/actuator/hystrix.stream,可以看到用户服务已经提供了一个 /hystrix.stream 接口,可以供Dashboard获取服务状态信息

之后访问用户服务我们定义的Feign的接口 http://localhost:8081/hystrix/feign/order,在 /hystrix.stream 的ping页面会出现相应服务信息

e.结合Hystrix Dashboard与Apache Jmeter展示压力测试结果

此时我们可以使用压力测试工具Apache Jmeter对接口进行压测,观察Dashboard面板的统计信息:

压力测试接口: http://localhost:8081/hystrix/feign/order

    OrderServiceFeignClient#getAllOrders(): #针对具体某个方法进行配置
      execution:
        isolation:
          strategy: SEMAPHORE   # execution.isolation.strategy 隔离策略 信号量; com.netflix.hystrix.HystrixCommandProperties.default_executionIsolationStrategy 枚举值
          semaphore:
            maxConcurrentRequests: 10 # execution.isolation.semaphore.maxConcurrentRequests  最大并发请求数

我们配置了信号量隔离策略,1秒钟10个请求的并发量。
压测配置 1秒内启动1000个线程
先后创建线程组Http请求查看结果数

线程组配置: 1秒内启动1000个线程

Http请求:http://localhost:8081/actuator/hystrix.stream

配置好后,启动,观察Dashboard面板统计信息;

1000个线程访问过程中的截图之一:

可以看到此时请求成功数量 20, 失败数量 137,断路器Circuit是Open打开状态-后续请求会失败,

同理,压测线程池隔离策略,如下:

处理成功226,下面Thread Pools显示线程池数量,2个运行状态的线程、队列中阻塞的线程196个、线程池大小2、阻塞队列总长度800;和我们之前配置的一致:

    OrderServiceFeignClient#insertOrder() :
      execution:
        isolation:
          strategy: THREAD   # execution.isolation.strategy 隔离策略 线程池
  threadpool:
    shen-order-service:
      coreSize : 2
      maxQueueSize : 1000 # 队列长度
      queueSizeRejectionThreshold : 800 # 超过800开始拒绝

从上面三种降级的配置可以看到,基于注解@HystrixCommand实现的降级都是以具体到方法粒度的,可能对代码的切入度比较高,如果粒度不想这么细的话,那就是在网关层面(Gateway)进行处理了。

二、组件结合使用及原理分析

1. Hystrix结合OpenFeign使用

在前几章进行RestTemplate进行接口调用时,我们使用了OpenFeign组件,那么此时进行Hystrix熔断配置时,又该怎么使用呢?
其实在我们引用的OpenFeign组件spring-cloud-starter-openfeign会引入feign-hystrix依赖,该依赖会对Hystrix进行集成。

之后,在原有订单服务和用户服务基础上分别改造。

基本使用

目的:Openfeign集成Hystrix触发超时降级:

  • a.改造订单服务 api
    OrderServiceFeignClient 指定降级回调方法:fallback

import com.bigshen.springcloud.demo.OrderDTO;
import com.bigshen.springcloud.demo.OrderService;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;

@FeignClient(value = "shen-order-service", fallback = OrderServiceFeignClientFallback.class)
public interface OrderServiceFeignClient extends OrderService {


}

@Component
class OrderServiceFeignClientFallback implements OrderServiceFeignClient {

    @Override
    public String getAllOrders() {
        return "查询订单失败,请稍后重试";
    }

    @Override
    public String insertOrder(OrderDTO order) {
        return "保存订单失败,请稍后重试";
    }
}

  • b. 订单服务service无需更改
    getAllOrders调用逻辑内休眠2s,

// 发布服务
@RestController
public class OrderServiceImpl implements OrderService {

    @Override
    public String getAllOrders() {
        System.out.println(1);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Shen all orders";
    }

    @Override
    public String insertOrder(OrderDTO orderDTO) {
        System.out.println(orderDTO);
        return "Success insert";
    }


}
  • c. 用户服务内打开feign集成hystrix的开关
    application.yml内添加
feign:
  hystrix:
    enabled: true

新增策略降级类HystrixFeignController ,内部Restful接口调用通过OpenFeign实现。


import com.bigshen.springcloud.demo.client.OrderServiceFeignClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HystrixFeignController {

    @Autowired
    OrderServiceFeignClient orderServiceFeignClient;

    @GetMapping("/hystrix/feign/order")
    public String queryOrder() {
        return orderServiceFeignClient.getAllOrders();
    }

}

此时订单服务内超时2秒,因为用户服务请求调用默认超时时间为1秒,此时调用getAllOrders接口会触发超时降级,返回查询订单失败,请稍后重试而非Shen all orders

接口调用结果也验证了我们的预期,触发了OpenFeign指定的降级策略。

OpenFeign结合HystrixCommand进行配置

之前我们直接使用 @HystrixCommand注解进行超时时间配置及超时降级:

    @HystrixCommand(
            commandProperties = {
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")  // 超时时间
            },
            fallbackMethod = "myTimeOutFallBack")
    @GetMapping("/hystrix/timeout")
    public String queryOrderTimeOut() {
        return restTemplate.getForObject("http://127.0.0.1:8082/order", String.class);
    }

此时我们不再是restTemplate直接调用,而是Openfeign,需要在配置文件中配置来达到该目的:
application.yml内添加对应注解:


hystrix:
  command:
    default : #全局配置 feignClient#method(params)
      execution:
        timeout:
          enable: true
        isolation: # execution.isolation.thread.timeoutInMilliseconds配置超时时间,设为3s
          thread:
            timeoutInMilliseconds: 3000

ribbon: # 因为openFeign内部是使用ribbon进行负载均衡调用,需要设置ribbon的超时时间
  ReadTimeout: 8000
  ConnectTimeout : 8000

此时我们将超时时间设置为了3秒,当再次进行请求调用时,因为订单服务内超时时间为2秒,所以不会超时而是返回订单接口的正常数据:

本次案例
地址: Spring-Cloud-Netflix-Hystrix

项目结构:

posted @ 2019-09-07 23:25  BigShen  阅读(976)  评论(0编辑  收藏  举报