Hystrix熔断器属于一种容错机制。

微服务中的雪崩效应

微服务中,一个请求可能需要多个微服务接口才能实现,会形成复杂的调用链路。
假设存在一个微服务,那上游微服务对它的调用叫做“扇入”,它对下游微服务的调用叫做“扇出”。
扇入:代表着该微服务被调用的次数,扇入大,说明该模块复用性好。
扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂。
扇入大是一个好事,扇出大不一定是好事。
在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过⻓或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
如果最下游微服务响应时间过⻓,大量请求阻塞,大量线程不会释放,会导致服务器资源耗尽,最终导致上游服务甚至整个系统瘫痪。

雪崩效应解决方案

下面,介绍三种技术手段应对微服务中的雪崩效应,这三种手段都是从系统可用性、可靠性⻆度出发,尽量防止系统整体缓慢甚至瘫痪。
服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。我们在各种场景下都会接触到熔断这两个字。高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护。股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易。同样,在微服务架构中,熔断机制也是起着类似的作用。
当扇出链路的某个微服务不可用或者响应时间太⻓时,熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。
注意:

  1. 服务熔断重点在“”,切断对下游服务的调用
  2. 服务熔断和服务降级往往是一起使用的,Hystrix就是这样。

服务降级
通俗讲就是整体资源不够用了,先将一些不关紧的服务停掉(调用我的时候,给你返回一个预留的值,也叫做兜底数据),待渡过难关高峰过去,再把那些服务打开。
服务降级一般是从整体考虑,就是当某个服务熔断之后,服务器将不再被调用,此刻客户端可以自己准备一个本地的fallback回调,返回一个缺省值,这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强。
服务限流
服务降级是当服务出问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰或者问题解决后再打开;但是有些场景并不能用服务降级来解决,比如秒杀业务这样的核心功能,这个时候可以结合服务限流来限制这些场景的并发/请求量。
限流措施也很多,比如:

  • 限制总并发数(比如数据库连接池、线程池)
  • 限制瞬时并发数(如nginx限制瞬时并发连接数)
  • 限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
  • 限制远程接口调用速率、限制MQ的消费速率等

    Hystrix简介

    [来自官网]Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。Hystrix主要通过以下几点实现延迟和容错。

  • 包裹请求:使用HystrixCommand包裹对依赖的调用逻辑。
  • 跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间。
  • 资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)(或者信号量)。如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定。
  • 监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝的请求等。
  • 回退机制:当请求失败、超时、被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发人员自行提供,例如返回一个缺省值。
  • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。

    Hystrix熔断应用

    目的:服务提供者⻓时间没有响应,服务消费者快速失败给用户提示。

  • 服务消费者工程中引入Hystrix依赖坐标(也可以添加在父工程中)

    <!--熔断器Hystrix-->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
  • 服务消费者工程的启动类中添加熔断器开启注解@EnableCircuitBreaker
  • 定义服务降级处理方法,并在业务方法上使用@HystrixCommand的fallbackMethod属性关联到服务降级处理方法

    /*
     * 1)服务提供者处理超时,熔断,返回错误信息
     * 2)有可能服务提供者出现异常直接抛出异常信息
     *
     * 以上信息,都会返回到消费者这里,很多时候消费者服务不希望把收到异常/错误信息再抛到它的上游去
     * 用户微服务 — 注册微服务 — 优惠券微服务
     * 1 登记注册
     * 2 分发优惠券(并不是核心步骤),这里如果调用优惠券微服务返回了异
    常信息或者是熔断后的错误信息,这些信息如果抛给用户很不友好
     * 此时,我们可以返回一个兜底数据,预设的默认值(服务降级)
     */
    
    @GetMapping("/findOrderTimeoutFallback/{userId}")
    @HystrixCommand(
    // 线程池标识,要保持唯一,不唯一的话就共用了
    threadPoolKey = "findOrderTimeoutFallback",
    // 线程池细节属性配置
    threadPoolProperties = {
      @HystrixProperty(name="coreSize",value = "2"), // 线程数
      @HystrixProperty(name="maxQueueSize",value="20") // 等待队列⻓度
    },
    // commandProperties熔断的一些细节属性配置
    commandProperties = {
      // 每一个属性都是一个HystrixProperty
      @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000"),
      // hystrix高级配置,定制工作过程细节
    
      // 统计时间窗口定义
      @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
      // 统计时间窗口内的最小请求数
      @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
      // 统计时间窗口内的错误数量百分比阈值
      @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
      // 自我修复时的活动窗口⻓度
      @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
    },
    fallbackMethod = "myFallBack" // 回退方法
    )
    public Integer findOrderTimeoutFallback(@PathVariable Long userId) {
    // 使用ribbon不需要我们自己获取服务实例然后选择一个那么去访问了(自己的负载均衡)
    String url = "http://cloud-eureka-server/shop/order/" + userId; // 指定服务名
    Integer forObject = restTemplate.getForObject(url, Integer.class);
    return forObject;
    }
    
    /*
     * 定义回退方法,返回预设默认值
     * 注意:该方法形参和返回值与原始方法保持一致
     */
    public Integer myFallBack(Long userId) {
    return -1; // 兜底数据
    }

    注意:降级(兜底)方法必须和被降级方法有相同的方法签名(相同参数列表、相同返回值)

  • 可以在类上使用@DefaultProperties注解统一指定整个类中共用的降级(兜底)方法(优先级没方法高)
  • 修改一个服务提供者的实例模拟请求超时(在方法中添加线程休眠的方法)并修改返回结果为端口号,另一个实例只修改返回结果为端口号,方便对比观察

因为我们已经使用了Ribbon负载(轮询),所以我们在请求的时候,一次熔断降级,一次正常返回
熔断降级
图片.png
正常返回
图片.png

Hystrix舱壁模式(线程池隔离策略)

图片.png
如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程),那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的,如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用。
图片.png
图片.png
为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数,而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段。
我们可以使用一些手段查看线程情况

  • 在终端使用jps命令查看Java进程
  • 使用jstack查看指定进程中的线程信息,过滤出和hystrix有关的线程信息
    jstack 进程ID | grep hystrix

发起请求,可以使用PostMan模拟批量请求
图片.png
复制一个方法,并修改两个方法注解中的threadPoolKey和coreSize值,发起请求后再次使用jstack命令查看线程情况,和我们程序设置相符合。

// 线程池标识,要保持唯一,不唯一的话就共用了
  threadPoolKey = "findOrderTimeoutFallback",
  // 线程池细节属性配置
  threadPoolProperties = {
    @HystrixProperty(name="coreSize",value = "2"), // 线程数
    @HystrixProperty(name="maxQueueSize",value="20") // 等待队列⻓度
  }

Hystrix工作流程与高级应用

图片.png

  1. 当调用出现问题时,开启一个时间窗(10s)
  2. 在这个时间窗内,统计调用次数是否达到最小请求数?
    如果没有达到,则重置统计信息,回到第1步
    如果达到了,则统计失败的请求数占所有请求数的百分比,是否达到阈值?
    如果达到,则跳闸(不再请求对应服务)
    如果没有达到,则重置统计信息,回到第1步
  3. 如果跳闸,则会开启一个活动窗口(默认5s),每隔5s,Hystrix会让一个请求通过,到达那个问题服务,看是否调用成功,如果成功,重置断路器回到第1步,如果失败,回到第3步
/*
 * 8秒钟内,请求次数达到2个,并且失败率在50%以上,就跳闸
 * 跳闸后活动窗口设置为3s
 */
@HystrixCommand(
  commandProperties = {
    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds",value = "8000"),
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "2"),
    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"),
    @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "3000")
  }
)

上述通过注解进行的配置也可以配置在配置文件中(优先级没注解高)

# 配置熔断策略:
hystrix:
  command:
    default:
      circuitBreaker:
        # 强制打开熔断器,如果该属性设置为true,强制断路器进入打开状态,将会拒绝所有的请求。 默认false关闭的
        forceOpen: false
        # 触发熔断错误比例阈值,默认值50%
        errorThresholdPercentage: 50
        # 熔断后休眠时⻓,默认值5秒
        sleepWindowInMilliseconds: 3000
        # 熔断触发最小请求次数,默认值是20
        requestVolumeThreshold: 2
      execution:
        isolation:
          thread:
            # 熔断超时设置,默认为1秒
            timeoutInMilliseconds: 2000

基于springboot的健康检查观察跳闸状态(自动投递微服务暴露健康检查细节)

# springboot中暴露健康检查等断点接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
  # 暴露健康接口的细节
  endpoint:
    health:
      show-details: always

访问健康检查接口:http://localhost:8090/actuator/health
hystrix正常工作状态
图片.png
跳闸状态
图片.png
活动窗口内自我修复
图片.png

Hystrix Dashboard断路监控仪表盘

正常状态是UP,跳闸是一种状态CIRCUIT_OPEN,可以通过/health查看,前提是工程中需要引入SpringBoot的actuator(健康监控),它提供了很多监控所需的接口,可以对应用系统进行配置查看、相关功能统计等。
已经统一添加在父工程中

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

如果我们想看到Hystrix相关数据,比如有多少请求、多少成功、多少失败、多少降级等,那么引入SpringBoot健康监控之后,访问/actuator/hystrix.stream接口可以获取到监控的文字信息,但是不直观,所以Hystrix官方还提供了基于图形化的DashBoard(仪表板)监控平 台。Hystrix仪表板可以显示每个断路器(被@HystrixCommand注解的方法)的状态。
图片.png

  1. 新建一个监控服务工程,导入依赖

    <!--hystrix-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    <!--hystrix 仪表盘-->
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  2. 启动类添加@EnableHystrixDashboard激活仪表盘
  3. application.yml

    server:
      port: 9000
    Spring:
      application:
     name: cloud-hystrix-dashboard
    eureka:
      client:
     serviceUrl: # eureka server的路径
       defaultZone: http://cloudeurekaservera:8761/eureka/,http://cloudeurekaserverb:8762/eureka/
      instance:
     prefer-ip-address: true
     instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
  4. 在被监测的微服务中注册监控servlet(监控数据就是来自于这个微服务)

    @Bean
    public ServletRegistrationBean getServlet(){
      HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
      ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
      registrationBean.setLoadOnStartup(1);
      registrationBean.addUrlMappings("/actuator/hystrix.stream");
      registrationBean.setName("HystrixMetricsStreamServlet");
      return registrationBean;
    }

    被监控微服务发布之后,可以直接访问监控servlet(http://localhost:8090/actuator/hystrix.stream),但是得到的数据并不直观,后期可以结合仪表盘更友好的展示

  5. 访问测试http://localhost:9000/hystrix
    图片.png
    输入监控的微服务端点地址,展示监控的详细数据,比如监控服务消费者http://localhost:8090/actuator/hystrix.stream
    图片.png
    百分比,10s内错误请求百分比
    实心圆:
  6. 大小:代表请求流量的大小,流量越大球越大
  7. 颜色:代表请求处理的健康状态,从绿色到红色递减,绿色代表健康,红色就代表很不健康

曲线波动图:
记录了2分钟内该方法上流量的变化波动图,判断流量上升或者下降的趋势

Hystrix Turbine聚合监控

之前,我们针对的是一个微服务实例的Hystrix数据查询分析,在微服务架构下,一个微服务的实例往往是多个(集群化)
实例1(hystrix) ip1:port1/actuator/hystrix.stream
实例2(hystrix) ip2:port2/actuator/hystrix.stream
实例3(hystrix) ip3:port3/actuator/hystrix.stream
按照已有的方法,我们就可以结合dashboard仪表盘每次输入一个监控数据流url,进去查看
手工操作能否被自动功能替代?Hystrix Turbine聚合(聚合各个实例上的hystrix监控数据)监控Turbine(涡轮)
微服务架构下,一个微服务往往部署多个实例,如果每次只能查看单个实例的监控,就需要经常切换很不方便,在这样的场景下,我们可以使用 Hystrix Turbine 进行聚合监控,它可以把相关微服务的监控数据聚合在一起,便于查看。
图片.png
Turbine服务搭建

  1. 新建项目cloud-hystrix-turbine-9001,引入依赖坐标

    <dependencies>
      <!--hystrix turbine聚合监控-->
      <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
      </dependency>
      <!--
     引入eureka客户端的两个原因
     1、老师说过,微服务架构下的服务都尽量注册到服务中心去,便于统一管理
     2、后续在当前turbine项目中我们需要配置turbine聚合的服务,比如,我们希望聚合service-consumer这个服务的各个实例的hystrix数据流,那随后我们就需要在application.yml文件中配置这个服务名,那么turbine获取服务下具体实例的数据流的时候需要ip和端口等实例信息,根据服务名称从eureka服务注册中心获取到这些信息
      -->
      <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eurekaclient</artifactId>
      </dependency>
    </dependencies>
  2. 将需要进行Hystrix监控的多个微服务配置起来,在工程application.yml中开启Turbine及进行相关配置

    server:
      port: 9001
    Spring:
      application:
     name: cloud-hystrix-turbine
    eureka:
      client:
     serviceUrl: # eureka server的路径
       defaultZone: http://cloudeurekaservera:8761/eureka/,http://cloudeurekaserverb:8762/eureka/ 
      instance:
     prefer-ip-address: true
     instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
    #turbine配置
    turbine:
      # appCofing配置需要聚合的服务名称,比如这里聚合自动投递微服务的hystrix监控数据
      # 如果要聚合多个微服务的监控数据,那么可以使用英文逗号拼接,比如 a,b,c
      appConfig: service-consumer
      clusterNameExpression: "'default'" # 集群默认名称
  3. 在当前项目启动类上添加注解@EnableTurbine,开启仪表盘以及Turbine聚合
  4. 浏览器访问Turbine项目,http://localhost:9001/turbine.stream,就可以看到监控数据了
  5. 我们通过dashboard的⻚面查看数据更直观,把刚才的地址输入dashboard地址栏
    图片.png
    监控⻚面
    图片.png

    Hystrix核心源码剖析

    源码涉及到springboot装配、面向切面编程、RxJava响应式编程(很复杂,这里不深入探索)的知识等等,这里只剖析下主体脉络。
    分析入口:@EnableCircuitBreaker注解激活了熔断功能,那么该注解就是Hystrix源码追踪的入口。

  6. @EnableCircuitBreaker注解激活熔断器
    图片.png
  7. 查看EnableCircuitBreakerImportSelector类
    图片.png
  8. 继续关注父类 SpringFactoryImportSelector
    图片.png
    图片.png
    spring.factories文件内容如下
    图片.png
    会注入org.springframework.cloud.netflix.hystrix.HystrixCircuitBreakerConfiguration
    图片.png
    关注切面:com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect
    图片.png
    重点分析环绕通知方法
    图片.png
    GenericCommand中根据元数据信息重写了两个很核心的方法,一个是run方法封装了对原始目标方法的调用,另外一个是getFallBack方法,它封装了对回退方法的调用。
    另外,在GenericCommand的上层类构造函数中会完成资源的初始化,比如线程池。
    GenericCommand —>AbstractHystrixCommand—>HystrixCommand—>AbstractCommand
    图片.png
    图片.png
    图片.png
    接下来回到环绕通知方法那张截图
    图片.png
    进入execute执行这里
    图片.png
    图片.png
    图片.png
    另外,我们观察,GenericCommand方法中根据元数据信息等重写了run方法(对目标方法的调用),getFallback方法(对回退方法的调用),在RxJava处理过程中会完成对这两个方法的调用。
    图片.png
    图片.png

标签: Hystrix, Spring Cloud

评论已关闭