Skip to content

微服务容错处理的引入

微服务架构高并发导致系统负载过高存在的问题分析

在微服务架构中,会将业务拆分成一个个的服务,服务与服务之间可以相互调用,由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪。

在 Spring Boot 程序中,默认使用内置 tomcat 作为 web 服务器。单 tomcat 支持最大的并发请求是有限的,如果某一接口阻塞,待执行的任务积压越来越多,那么势必会影响其他接口的调用

搭建模拟高并发并请求响应过慢的示例

复用前面 microservice-no-springcloud 项目代码,模拟高并发请求的案例

模拟服务接口响应慢

  1. 修改shop-service-product服务的ProductController控制类的findById方法,令线程睡眠2秒,模拟响应慢
java
@GetMapping("/{id}")
public Product findById(@PathVariable Long id) {
    // 睡眠2秒,模拟请求响应慢
    try {
        Thread.sleep(2000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return productService.findById(id);
}
  1. 修改shop-service-order服务的application.yml配置,调小tomcat容器最大连接数,模拟并发请求数量超出web容器可承受最大连接数
yml
server:
  port: 9002 # 项目端口
  tomcat:
    max-threads: 10 # 修改web容器最大连接数
  1. 修改shop-service-order服务的OrderController控制类,增加一个普通的请求方法,用于测试其他请求接口阻塞时,此请求的响应时间
java
@GetMapping(value = "/{id}")
public String findOrder(@PathVariable Long id) {
    System.out.println(Thread.currentThread().getName());
    return "根据id查询订单";
}
  1. 启动product与order服务,使用jMeter压力测试工具,创建一个每秒50并发的POST请求一直请求创建订单的接口,然后在浏览器中访问http://localhost:9002/order/1查询订单接口,观察此接口响应时间

单独请求的响应时间

开启并发请求创建订单接口后,再次访问此查询接口的响应时间

基于线程池的形式实现服务隔离

改造spring-cloud-sample-concurrency-isolation工程,实现线程池的隔离

引入依赖

为了方便实现线以线程池的形式完成资源隔离,需要在shop-service-order工程中引入如下依赖

xml
<!-- hystrix 组件的依赖,此示例用于实现基于线程池的形式完成服务的隔离 -->
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-metrics-event-stream</artifactId>
    <version>1.5.12</version>
</dependency>
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-javanica</artifactId>
    <version>1.5.12</version>
</dependency>

分配线程池

配置HystrixCommand接口的实现类,在实现类中可以对线程池资源进行分配

java
package com.moon.order.command;

import com.moon.entity.Product;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import org.springframework.web.client.RestTemplate;

/**
 * HystrixCommand的原生实现方式,对服务进行服务降级限流
 */
public class OrderCommand extends HystrixCommand<Product> {

    private RestTemplate restTemplate;

    private Long id;

    public OrderCommand(RestTemplate restTemplate, Long id) {
        super(setter());
        this.restTemplate = restTemplate;
        this.id = id;
    }

    private static Setter setter() {

        // 服务分组
        HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey("order_product");
        // 服务标识
        HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey("product");
        // 线程池名称
        HystrixThreadPoolKey threadPoolKey = HystrixThreadPoolKey.Factory.asKey("order_product_pool");
        /*
         * 线程池配置
         *     withCoreSize : 线程池大小为10
         *     withKeepAliveTimeMinutes : 线程存活时间15秒
         *     withQueueSizeRejectionThreshold : 队列等待的阈值为100,超过100执行拒绝策略
         */
        // 注:测试案例设置了tomcat最大线程数为10,所以这里设置线程池大小为5,实现此接口的线程数量控制。
        HystrixThreadPoolProperties.Setter threadPoolProperties = HystrixThreadPoolProperties.Setter().withCoreSize(5)
                .withKeepAliveTimeMinutes(15).withQueueSizeRejectionThreshold(100);

        // 命令属性配置Hystrix 开启超时
        HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter()
                // 采用线程池方式实现服务隔离
                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
                // 禁止
                .withExecutionTimeoutEnabled(false);
        return HystrixCommand.Setter.withGroupKey(groupKey).andCommandKey(commandKey).andThreadPoolKey(threadPoolKey)
                .andThreadPoolPropertiesDefaults(threadPoolProperties).andCommandPropertiesDefaults(commandProperties);

    }

    @Override
    protected Product run() throws Exception {
        return restTemplate.getForObject("http://127.0.0.1:9001/product/" + id, Product.class);
    }

    /**
     * 服务降级方法
     *
     * @return
     */
    @Override
    protected Product getFallback() {
        Product product = new Product();
        product.setProductName("服务降级方法返回的数据");
        return product;
    }
}

服务调用的改造

修改OrderController,使用自定义的OrderCommand完成调用

java
@PostMapping("/{id}")
public String createOrder(@PathVariable Long id) {
    // 使用OrderCommand调用远程服务
    OrderCommand orderCommand = new OrderCommand(restTemplate, id);
    Product product = orderCommand.execute();
    LOGGER.info("当前下单的商品是: ${}", product);
    return "创建订单成功";
}

测试

按上面案例测试方式,启动product与order服务,使用jMeter压力测试工具,创建一个每秒50并发的POST请求一直请求创建订单的接口,然后在浏览器中访问http://localhost:9002/order/1查询订单接口,观察此接口响应时间。因为控制了请求product服务的线程数量,所以此请求响应不会被影响

Hystrix 介绍

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

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

Hystrix 对 RestTemplate 实现服务的熔断

示例项目搭建

复用spring-cloud-sample-ribbon工程的代码,创建新的工程spring-cloud-sample-hystrix-resttemplate。整理删除一些无用的依赖与代码

配置 hystrix 依赖

shop-service-order 模块中添加 Hystrix 的相关依赖

xml
<!--引入hystrix依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

开启熔断支持

shop-service-order工程中,在OrderApplication启动类中添加@EnableCircuitBreaker注解,代表开启对熔断的支持

java
@SpringBootApplication(scanBasePackages = "com.moon.order")
@EntityScan("com.moon.entity") // 指定扫描实体类的包路径
@EnableCircuitBreaker // 开启hystrix熔断支持
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}

注:随着功能的增多,微服务的启动类中的注解也越来越多。所以SpringCloud提供了一个组合注解注解:@SpringCloudApplication。此注解相关于@SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker,注解源码如下:

java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}

对工程的启动类进行以下的改造,其效果以上面配置一致

java
@EntityScan("com.moon.entity") // 指定扫描实体类的包路径
@SpringCloudApplication // 此组合注解相当于 @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class OrderApplication {
}

配置熔断降级业务逻辑

改造shop-service-order工程的OrderController控制类,增加熔断部分的逻辑

开启熔断与配置单个降级方法

  1. 编写一个服务降级(回退)方法orderFallBack方法。有以下注意点
    • 此降级方法要与相应的熔断方法具有相同的参数列表
    • 此降级方法要与相应的熔断方法具有相同的返回值类型
  2. 需要熔断的方法上,标识注解@HystrixCommand,代表此方法需要熔断,然后在注解的fallbackMethod属性,指定熔断触发的降级方法名称
java
@RestController
@RequestMapping("order")
public class OrderController {
    /* 日志对象 */
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);

    // 注入HTTP请求工具类RestTemplate
    @Autowired
    private RestTemplate restTemplate;

    /*
     * 标识 @HystrixCommand 注解代表此方法配置熔断保护
     *      fallbackMethod属性:指定熔断之后的降级方法
     */
    @HystrixCommand(fallbackMethod = "orderFallBack")
    @PostMapping("/{id}")
    public String createOrder(@PathVariable Long id) {
        // 调用服务
        Product product = restTemplate.getForObject("http://shop-service-product/product/" + id, Product.class);
        LOGGER.info("当前下单的商品是: ${}", product);
        return "创建订单成功";
    }

    /*
     * 降级方法
     *  和需要收到保护的方法的返回值一致
     *  方法参数一致
     */
    public String orderFallBack(Long id) {
        LOGGER.info("当前下单商品的id是: " + id + ",触发createOrder熔断的降级方法");
        return "当前下单商品的id是: " + id + ",触发createOrder熔断的降级方法";
    }
}
  1. 测试,当shop-service-product微服务正常时,浏览器访问http://127.0.0.1:9002/order/1可以正常调用服务提供者获取数据。当将商品微服务停止时继续访问,

开启熔断与配置统一降级方法

如果当前类中每个方法的降级方法逻辑都一样,可以在当前类上标识@DefaultProperties注解,并在defaultFallback属性中指定公共的降级方法名称。

注:如果过在@DefaultProperties指定了公共的降级方法,则相应配置@HystrixCommand熔断保护的方法不需要单独指定了降级方法

java
@RestController
@RequestMapping("order")
/*
 * @DefaultProperties注解用于指定此接口中公共的熔断设置,
 *  defaultFallback属性:用于公共的降级方法名称
 *  如果过在@DefaultProperties指定了公共的降级方法,在相应的熔断保护@HystrixCommand中不需要单独指定降级方法
 */
@DefaultProperties(defaultFallback = "defaultFallBack")
public class OrderController {
    /* 日志对象 */
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);

    // 注入HTTP请求工具类RestTemplate
    @Autowired
    private RestTemplate restTemplate;

    /*
     * 标识 @HystrixCommand 注解代表此方法配置熔断保护
     *      fallbackMethod属性:指定熔断之后的降级方法
     */
    @HystrixCommand // 使用统一降级方法,则不需要再指定fallbackMethod属性
    @PostMapping("/{id}")
    public String createOrder(@PathVariable Long id) {
        // 调用服务
        Product product = restTemplate.getForObject("http://shop-service-product/product/" + id, Product.class);
        LOGGER.info("当前下单的商品是: ${}", product);
        return "创建订单成功";
    }

    /*
     * 公共的降级方法
     *  注意: 1.此方法不能有形参
     *       2.如果使用统一的降级方法,则最好统一所有需要熔断保护的方法的返回类型
     */
    public String defaultFallBack() {
        LOGGER.info("触发熔断公共降级方法");
        return "触发熔断公共降级方法";
    }
}

超时设置

使用Hystrix组件,请求在超过规定的时间没有获取到微服务的数据,此时会自动触发熔断降级方法,默认值是1000(1秒)。可以在application.yml项目配置文件中,修改timeoutInMilliseconds属性来设置地超时处理时长

yml
# hystrix 配置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000 # 配置连接超时时长,默认的连接超时时间1秒,即若1秒没有返回数据,自动的触发降级逻辑

Hystrix 对 Feign 实现服务的熔断

SpringCloud Fegin 默认已整合了 hystrix,所以添加 Feign 依赖后就不用在添加 hystrix 依赖

示例项目搭建

复用06-springcloud-feign工程的代码,创建新的工程09-springcloud-hystrix-feign。整理删除一些无用的依赖与代码

开启 hystrix

在 Feign 中已经内置了 hystrix,但是默认是关闭的。修改项目的 application.yml 配置文件开启对 hystrix 的支持

yml
feign:
  hystrix:
    enabled: true # 开启对hystrix的支持,默认值是false(关闭)

配置 FeignClient 接口的实现类

基于Feign实现熔断降级,需要创建FeignClient接口的实现类,在实现类中定义降级方法

java
/**
 * ProductFeignClient接口实现,此类中实现的方法为相应的降级方法
 */
// Hystrix组件会在spring容器中查找FeignClient相应的实现类,调用其降级方法,所在需要标识注解注册到spring容器中
@Component
public class ProductFeignClientCallBack implements ProductFeignClient {
    /* 日志对象 */
    private static final Logger LOGGER = LoggerFactory.getLogger(ProductFeignClientCallBack.class);

    /**
     * 此方法为ProductFeignClient接口中相应方法的降级实现
     */
    @Override
    public Product findById(Long id) {
        LOGGER.info("当前下单商品的id是: " + id + ",触发ProductFeignClientCallBack类中熔断的findById降级方法");
        Product product = new Product();
        product.setProductName("feign调用触发熔断降级方法");
        return product;
    }
}

FeignClient 接口指定 hystrix 熔断方法

在接口上的@FeignClient注解,添加fallback属性,声明降级方法(接口的实现类)

java
/*
 * @FeignClient 注解,用于标识当前接口为Feign调用微服务的核心接口
 *  value/name属性:指定需要调用的服务提供者的名称
 *  fallback属性:配置熔断时降级方法(实现类)
 */
@FeignClient(name = "shop-service-product", fallback = ProductFeignClientCallBack.class)
public interface ProductFeignClient {
    @GetMapping("/product/{id}")
    Product findById(@PathVariable("id") Long id);
}

Hystrix 的监控平台

Hystrix 的设置实时监控

注意细节:当请求失败,被拒绝,超时的时候,都会进入到降级方法中。但进入降级方法并不意味着断路器已经被打开。

除了实现容错功能,Hystrix 还提供了近乎实时的监控,HystrixCommandHystrixObservableCommand 在执行时,会生成执行结果和运行指标。比如每秒的请求数量,成功数量等。

这些状态会暴露在 Actuator 提供的 /health 端点中。只需为项目添加 spring-boot-actuator 依赖,重启项目,访问 http://localhost:9002/actuator/hystrix.stream,即可看到实时的监控数据。

引入依赖

09-springcloud-hystrix-feign 示例中,修改 shop-service-order 工程 maven 依赖

xml
<!-- 引入hystrix的监控信息 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix 核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

配置开启 Hystrix 支持

shop-service-order 工程启动类中,标识 @EnableCircuitBreaker 开启 Hystrix 支持

java
@SpringBootApplication(scanBasePackages = "com.moon.order")
@EntityScan("com.moon.entity") // 指定扫描实体类的包路径
@EnableFeignClients // 开启Feign的支持
@EnableCircuitBreaker // 开启Hystrix支持
public class OrderApplication {
    // ....省略
}

注:想开启Hystrix实现监控的功能,必须标识@EnableCircuitBreaker注解

设置暴露所有监控接口

默认Hystrix只暴露Actuator部分接口,访问http://localhost:9001/actuator可以查询到默认暴露的接口

配置暴露所有Actuator监控端点,需要修改项目的application.yml文件,添加以下配置

yml
# 配置暴露所有Actuator监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'

访问实时监控页面

启动项目,访问http://localhost:9002/actuator/hystrix.stream,可以看到实时的监控数据。

Hystrix DashBoard 监控平台

Hystrix 的实时监控,访问/hystrix.stream接口获取的都是已文字形式展示的信息。但通过文字的方式很难直观的展示系统的运行状态,所以Hystrix官方还提供了基于图形化的DashBoard(仪表板)监控平台。Hystrix仪表板可以显示每个断路器(被@HystrixCommand注解的方法)的状态。

引入依赖

搭建Hystrix DashBoard监控,配置工程的maven引入相关依赖

xml
<!-- 引入hystrix的监控信息 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix 核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- Hystrix DashBoard 监控平台依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

开启监控平台

在项目的启动类中添加@EnableHystrixDashboard注解,表示激活监控平台(仪表盘)

java
@SpringBootApplication(scanBasePackages = "com.moon.order")
@EntityScan("com.moon.entity") // 指定扫描实体类的包路径
@EnableFeignClients // 开启Feign的支持
@EnableCircuitBreaker // 开启Hystrix支持
@EnableHystrixDashboard // 激活监控平台(仪表盘)
public class OrderApplication {
}

测试访问监控平台

访问相应项目的url,http://localhost:9002/hystrix,进去平台初始化页面。

输入实时监控断点url:http://localhost:9002/actuator/hystrix.stream,以图形化方式展示监控的详细数据

断路器聚合监控 Turbine

在微服务架构体系中,每个服务都需要配置Hystrix DashBoard监控。如果每次只能查看单个实例的监控数据,就需要不断切换监控地址,这显然很不方便。要想看这个系统的 Hystrix Dashboard 数据就需要用到 Hystrix Turbine。Turbine 是一个聚合 Hystrix 监控数据的工具,他可以将所有相关微服务的 Hystrix 监控数据聚合到一起,方便使用。引入 Turbine 后,整个监控系统架构如下:

注:每个需要监控的微服务,都需要配置相应Hystrix DashBoard监控。只是访问时通过聚合监控项目可以查询所有节点的情况

搭建聚合监控工程

创建工程shop-server-hystrix-turbine,引入相关依赖坐标

xml
<!-- Hystrix Turbine 聚合监控核心依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

配置多个微服务的hystrix监控

修改项目的application.yml配置文件,开启turbine并进行相关配置

yml
server:
  port: 8031
spring:
  application:
    name: shop-server-hystrix-turbine
# 配置eureka服务,用于获取相关监控的微服务地址列表
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8001/eureka/
  instance:
    prefer-ip-address: true
# hystrix turbine 配置
turbine:
  # 配置需要监控的微服务列表,多个服务之前使用,分隔
  appConfig: shop-service-order
  clusterNameExpression: "'default'"
  • eureka相关配置:指定注册中心地址
  • turbine相关配置:指定需要监控的微服务列表

turbine会自动的从注册中心中获取需要监控的微服务,并聚合所有微服务中的 /hystrix.stream 数据

配置启动类

作为一个独立的监控项目,需要配置启动类,使用@EnableHystrixDashboard注解开启Hystrix Dashboard监控平台;并使用@EnableTurbine注解激活Turbine

java
@SpringBootApplication
@EnableTurbine // 开启 Turbine
@EnableHystrixDashboard // 开启Hystrix Dashboard监控平台
public class TurbineServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(TurbineServerApplication.class, args);
    }
}

测试访问

浏览器访问http://localhost:8031/hystrix展示Hystrix Dashboard。并在url位置输入 http://localhost:8031/turbine.stream,动态根据turbine.stream数据展示多个微服务的监控数据

熔断器的状态

简介

熔断器有三个状态:CLOSEDOPENHALF_OPEN,熔断器默认是关闭状态。

当触发熔断后状态变更为 OPEN,在等待到指定的时间,Hystrix 会放开部分请求用于检测服务是否开启,这期间熔断器会变为 HALF_OPEN 半开启状态,当熔断探测服务可用时,则会将状态变更为 CLOSED,关闭熔断器

  • Closed:关闭状态(断路器关闭),所有请求都正常访问。代理类维护了最近调用失败的次数,如果某次调用失败,则使失败次数加1。如果最近失败次数超过了在给定时间内允许失败的阈值,则代理类切换到断开(Open)状态。此时代理开启了一个超时时钟,当该时钟超过了该时间,则切换到半断开(Half-Open)状态。该超时时间的设定是给了系统一次机会来修正导致调用失败的错误。
  • Open:打开状态(断路器打开),所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全关闭。默认失败比例的阈值是50%,10秒内请求次数最少不低于20次。
  • Half Open:半开状态,open 状态不是永久的,打开后会进入休眠时间(默认是5s)。随后断路器会自动进入半开状态。此时会释放1次请求通过,若这个请求是健康的,则会关闭断路器,否则继续保持打开,再次进行5秒休眠计时。

状态的测试

spring-cloud-sample-hystrix-resttemplate示例工程中进行熔断状态的测试。

测试的准备

为了能够精确控制请求的成功或失败,在shop-service-product工程的ProductController类的findById方法中调用业务中加入一段逻辑:

java
@GetMapping("/{id}")
public Product findById(@PathVariable Long id) {
    // 为了能够精确控制请求的成功或失败,当id不为1时,抛出异常
    if (id != 1) {
        throw new RuntimeException("服务器异常");
    }
    // ....省略
}

当id为1时,正常请求;id不为1时,请求异常

为了更好观察测试结果,在shop-service-order引入 Hystrix DashBoard 监控平台,修改项目配置文件application.yml,修改熔断器相应的默认配置:

yml
# hystrix 配置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000 # 配置连接超时时长,默认的连接超时时间1秒,即若1秒没有返回数据,自动的触发降级逻辑
      circuitBreaker:
        requestVolumeThreshold: 5 # 配置触发熔断的最小请求次数,默认20
        sleepWindowInMilliseconds: 10000 # 配置熔断多少秒后去尝试请求 默认5秒   打开状态的时间
        errorThresholdPercentage: 50 # 触发熔断的失败请求最小占比,默认50%

配置说明:

  • requestVolumeThreshold:触发熔断的最小请求次数,默认20
  • errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
  • sleepWindowInMilliseconds:熔断多少秒后去尝试请求,默认5秒

测试结果分析

当连续访问id为2的请求时(超过10次),就会触发熔断。断路器会端口,一切请求都会被降级处理。此时访问id为1的请求,会发现返回的也是失败,而且失败时间很短,只有20毫秒左右:

熔断器的隔离策略

两种隔离策略

微服务使用 Hystrix 熔断器实现了服务的自动降级,让微服务具备自我保护的能力,提升了系统的稳定性,也较好的解决雪崩效应。其使用方式目前支持两种策略

  • 线程池隔离策略:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
  • 信号量隔离策略:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)

线程池和型号量两种策略功能支持对比

切换不同策略的配置

  • hystrix.command.default.execution.isolation.strategy:配置隔离策略
    • ExecutionIsolationStrategy.SEMAPHORE:信号量隔离
    • ExecutionIsolationStrategy.THREAD:线程池隔离
  • hystrix.command.default.execution.isolation.maxConcurrentRequests:最大信号量上限
yml
hystrix:
  command:
    default:
      execution:
        isolation:
          # 配置隔离策略,取值:ExecutionIsolationStrategy.SEMAPHORE(信号量隔离)、ExecutionIsolationStrategy.THREAD(线程池隔离)
          strategy: ExecutionIsolationStrategy.SEMAPHORE # 信号量隔离
          maxConcurrentRequests: 20 # 配置最大信号量上限

Hystrix 核心源码分析

Hystrix 底层基于 RxJava,RxJava 是响应式编程开发库,因此 Hystrix 的整个实现策略简单说即:把一个 HystrixCommand 封装成一个 Observable(待观察者),针对自身要实现的核心功能,对 Observable 进行各种装饰,并在订阅各步装饰的 Observable,以便在指定事件到达时,添加自己的业务。

Hystrix主要有4种调用方式

  • toObservable():未做订阅,只是返回一个 Observable
  • observe():调用 toObservable() 方法,并向 Observable 注册 rx.subjects.ReplaySubject 发起订阅
  • queue():调用 toObservable() 方法的基础上,调用:Observable.toBlocking()BlockingObservable.toFuture() 返回 Future 对象
  • execute():调用 queue() 方法的基础上,调用 Future.get() 方法,同步返回 run() 方法的执行结果。

主要的执行逻辑

  1. 每次调用创建一个新的 HystrixCommand,把依赖调用封装在run()方法中
  2. 执行execute()/queue()做同步或异步调用.
  3. 判断熔断器(circuit-breaker)是否打开,如果打开跳到步骤8,进行降级策略,如果关闭进入步骤
  4. 判断线程池/队列/信号量是否跑满,如果跑满进入降级步骤8,否则继续后续步骤
  5. 调用HystrixCommandrun方法。运行依赖逻辑,依赖逻辑调用超时,进入步骤8
  6. 判断逻辑是否调用成功。返回成功调用结果;调用出错,进入步骤8
  7. 计算熔断器状态,所有的运行状态(成功,失败,拒绝,超时)上报给熔断器,用于统计从而判断熔断器状态
  8. getFallback()降级逻辑。以下四种情况将触发getFallback的调用:
    1. run()方法抛出非HystrixBadRequestException异常
    2. run()方法调用超时
    3. 熔断器开启拦截调用
    4. 线程池/队列/信号量是否跑满
    5. 没有实现getFallbackCommand将直接抛出异常,fallback降级逻辑调用成功直接返回,降级逻辑调用失败抛出异常
  9. 返回执行成功结果

HystrixCommand 注解

通过 @HystrixCommand 注解能够更加简单快速的实现 Hystrix 的应用,查看 @HystrixCommand 注解源码,其中包含了诸多参数配置,如执行隔离策略,线程池定义等

java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
    String groupKey() default "";

    String commandKey() default "";

    String threadPoolKey() default "";

    String fallbackMethod() default "";

    HystrixProperty[] commandProperties() default {};

    HystrixProperty[] threadPoolProperties() default {};

    Class<? extends Throwable>[] ignoreExceptions() default {};

    ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;

    HystrixException[] raiseHystrixExceptions() default {};

    String defaultFallback() default "";
}

其定义了 fallbackMethod 属性,其作用是提供了一个定义回退方法映射,在异常触发时此方法名对应的 method 将被触发执行,从而实现服务的降级。被 @HystrixCommand 注解的方法将会执行切面处理。其切面类是 HystrixCommandAspect.java,其切点定义如下

java
@Aspect
public class HystrixCommandAspect {
    private static final Map<HystrixPointcutType, MetaHolderFactory> META_HOLDER_FACTORY_MAP;

    static {
        META_HOLDER_FACTORY_MAP = ImmutableMap.<HystrixPointcutType, MetaHolderFactory>builder()
                .put(HystrixPointcutType.COMMAND, new CommandMetaHolderFactory())
                .put(HystrixPointcutType.COLLAPSER, new CollapserMetaHolderFactory())
                .build();
    }

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand)")
    public void hystrixCommandAnnotationPointcut() {}

    @Pointcut("@annotation(com.netflix.hystrix.contrib.javanica.annotation.HystrixCollapser)")
    public void hystrixCollapserAnnotationPointcut() {}

    @Around("hystrixCommandAnnotationPointcut() || hystrixCollapserAnnotationPointcut()")
    public Object methodsAnnotatedWithHystrixCommand(final ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = getMethodFromTarget(joinPoint);
        Validate.notNull(method, "failed to get method from joinPoint: %s", joinPoint);
        if (method.isAnnotationPresent(HystrixCommand.class) && method.isAnnotationPresent(HystrixCollapser.class)) {
            throw new IllegalStateException("method cannot be annotated with HystrixCommand and HystrixCollapser " +
                    "annotations at the same time");
        }
        MetaHolderFactory metaHolderFactory = META_HOLDER_FACTORY_MAP.get(HystrixPointcutType.of(method));
        MetaHolder metaHolder = metaHolderFactory.create(joinPoint);
        HystrixInvokable invokable = HystrixCommandFactory.getInstance().create(metaHolder);
        ExecutionType executionType = metaHolder.isCollapserAnnotationPresent() ?
                metaHolder.getCollapserExecutionType() : metaHolder.getExecutionType();

        Object result;
        try {
            if (!metaHolder.isObservable()) {
                result = CommandExecutor.execute(invokable, executionType, metaHolder);
            } else {
                result = executeObservable(invokable, executionType, metaHolder);
            }
        } catch (HystrixBadRequestException e) {
            throw e.getCause();
        } catch (HystrixRuntimeException e) {
            throw hystrixRuntimeExceptionToThrowable(metaHolder, e);
        }
        return result;
    }
    // ....省略
}

HystrixCommandAspectmethodsAnnotatedWithHystrixCommand方法,此方法通过环绕通知的形式对目标方法进行增强,主要作用如下:

  • HystrixInvokable:定义了后续真正执行HystrixCommandGenericCommand实例
  • 定义metaHolder,包含了当前被注解方法的所有相关有效信息
  • 执行方法:在进入执行体前,其有一个判断条件,判断其是否是一个Observable模式(在Hystrix中,其实现大量依赖RXJAVA,会无处不在的看到Observable,其是一种观察者模式的实现,具体可以到RxJava项目官方做更多了解)

Hystrix 服务熔断的替换方案

18年底 Netflix 官方宣布 Hystrix 已经足够稳定并且不再开发,该项目将处于维护模式。就目前来看 Hystrix 是比较稳定的,并且 Hystrix 只是停止开发新的版本,并不是完全停止维护,出现 bug 问题依然会维护的。因此短期内,Hystrix 依然是继续使用的。但从长远来看,Hystrix 总会达到它的生命周期,那么 Spring Cloud 生态中是否有替代产品呢?

替换方案介绍

Alibaba Sentinel

Sentinel 是阿里巴巴开源的一款断路器实现,目前在 Spring Cloud 的孵化器项目 Spring Cloud Alibaba 中的一员 Sentinel 本身在阿里内部已经被大规模采用,非常稳定。因此可以作为一个较好的替代品

Resilience4J

Resilicence4J 一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是 Hystrix 官方推荐的替代产品。不仅如此,Resilicence4j 还原生支持 Spring Boot 1.x/2.x,而且监控也不像 Hystrix 一样弄 Dashboard/Hystrix 等一堆轮子,而是支持和 Micrometer(Pivotal 开源的监控门面,Spring Boot 2.x 中的 Actuator 就是基于 Micrometer)、prometheus(开源监控系统,来自谷歌的论文)、以及 Dropwizard metrics(Spring Boot 曾经的模仿对象)进行整合

Spring Cloud Alibaba Sentinel

Spring Cloud Alibaba Sentinel 组件详细介绍与使用,详见《Spring Cloud Alibaba Sentinel》笔记

Resilience4J(待学习与整理)

待学习与整理!

I know where dry desert ends, green grass grows · MooN