【你好Hystrix】五:Hystrix断路器模块代码解析-HystrixCircuitBreaker

前言

我们前面对Hystrix的指标统计方式做了详细的介绍。1.5.0版本之前使用的是HystrixRollingNumber环形数组来作为指标的收集的模块,而1.5.0版本之后使用的是更灵活的响应式收集模式。有了各项请求的指标那么这一篇文章我们就来看看 Hystrix的断路器是如何使用这些指标来作为依据的。

断路器

Hystrix的断路器有三种状态:打开、半开、关闭。

  • 关闭状态:请求正常进入
  • 打开状态:拒绝所有的请求,如果这时有降级的逻辑走降级逻辑
  • 半开状态:打开状态的默认过5s状态会自动变成半开状态,该状态下允许一个请求进入,如果请求成功关闭断路器 如果请求失败重新进入打开状态。

如下借用官网的一张图:
在这里插入图片描述
我们下面主要是来看Hystrix是如何实现这三种状态的转换的。

HystrixCircuitBreaker

该接口定义了如下方法:

    /**
     * 每次请求都会判断是否允许请求 该方法具有幂等性
     */
    boolean allowRequest();

    /**
     * 断路器是否处于打开的状态
     */
    boolean isOpen();

    /**
     * 标记请求成功
     */
    void markSuccess();

    /**
     * 标记请求失败
     */
    void markNonSuccess();

    /**
     * 尝试执行
     */
    boolean attemptExecution();

上面方法都很直接 并且我们可以认为HystrixCircuitBreaker 有且仅有一个实现HystrixCircuitBreakerImpl (NoOpCircuitBreaker也是一种实现 但是没有太大意义)

HystrixCircuitBreakerImpl

作为HystrixCircuitBreaker的唯一的实现 HystrixCircuitBreakerImpl重要程度不言而喻。(注意看源码的注释)

属性

//配置
private final HystrixCommandProperties properties;
//指标统计器  这个我们前面没有说到 但是你可以简单的理解它是一个聚合(对各种统计流的聚合)
 //特别是聚合了HealthCountsStream 
 private final HystrixCommandMetrics metrics;
 //断路器的三种状态
 enum Status {
     CLOSED, OPEN, HALF_OPEN;
 }
 //保存当前断路器的状态默认是关闭的
 private final AtomicReference<Status> status = new AtomicReference<Status>(Status.CLOSED);
 //熔断器关闭的记录为-1
 private final AtomicLong circuitOpened = new AtomicLong(-1);
 //保存对HealthCountsStream的订阅结果 用于重订阅。那可能会问 为什么要重订阅
 //其实就是为了初始化统计数据 例如从打开状态的进入关闭状态 这个时候需要重新统计指标来为下一次判定做准备
 private final AtomicReference<Subscription> activeSubscription = new AtomicReference<Subscription>(null);

熔断器打开

//这个方法很重要 通过订阅HealthCountsStream来实时判断是否要打开熔断开关
  private Subscription subscribeToStream() {
       return metrics.getHealthCountsStream()
               .observe()
               .subscribe(new Subscriber<HealthCounts>() {
                   @Override
                   public void onCompleted() {
                   }
                   @Override
                   public void onError(Throwable e) {
                   }
                   @Override
                   public void onNext(HealthCounts hc) {
                   //判断当前的请求总量是否超过我们设置的请求量的阈值
                   //可以通过hystrix.command.default.circuitBreaker.requestVolumeThreshold来配置请求阈值
                   //默认是 20个
                       if (hc.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
                       } else {
                   //如果失败率大于我们配置的失败率 就把熔断状态改为打开的状态并记录打开的时间
                   //失败率默认是50% 10s 20个请求 50%都失败了 才会打开熔断的开关
                           if (hc.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
                           } else {
                               if (status.compareAndSet(Status.CLOSED, Status.OPEN)) {                               circuitOpened.set(System.currentTimeMillis());
                               }
                           }
                       }
                   }
               });
   }

上面的方法主要是一个订阅的逻辑 然后根据订阅的数据来判定是否需要打开 断路器

  • 先判断10s内的总请求数量是否大于默认值20 可通过 hystrix.command.default.circuitBreaker.requestVolumeThreshold 来配置
  • 其次判断失败率是否超过默认值 50% 可通过hystrix.command.default.circuitBreaker.errorThresholdPercentage来配置
  • 如果上面条件都满足就打开断路器 并记录时间

markSuccess

 @Override
 public void markSuccess() {
      //通过CAS关闭断路器
     if (status.compareAndSet(Status.HALF_OPEN, Status.CLOSED)) {
         //重置统计流
         metrics.resetStream();
         //如果当前activeSubscription也有订阅 那么重新订阅
         Subscription previousSubscription = activeSubscription.get();
         if (previousSubscription != null) {
             previousSubscription.unsubscribe();
         }
         Subscription newSubscription = subscribeToStream();
         activeSubscription.set(newSubscription);
         circuitOpened.set(-1L);
     }
 }

markNonSuccess

 @Override
 public void markNonSuccess() {
     if (status.compareAndSet(Status.HALF_OPEN, Status.OPEN)) {
         circuitOpened.set(System.currentTimeMillis());
     }
 }

标记不成功 将断路器从半开状态变为打开状态。circuitOpened记录打开的时间戳

allowRequest

//isAfterSleepWindow 这个方法的逻辑主要判断在断路器打开的状态下 默认5s 要尝试去发送一笔请求
//判断打开的时间是否超过5s
 @Override
 private boolean isAfterSleepWindow() {
   //circuitOpened 这个变量 在断路器打开的状态下存储的是时间戳 
   final long circuitOpenTime = circuitOpened.get();
    final long currentTime = System.currentTimeMillis();
    //可以通过hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds来配置
    final long sleepWindowTime = properties.circuitBreakerSleepWindowInMilliseconds().get();
    return currentTime > circuitOpenTime + sleepWindowTime;
}

public boolean allowRequest() {
 //判断是否配置断路器强制打开
  if (properties.circuitBreakerForceOpen().get()) {
      return false;
  }
  //判断是否配置断路器强制关闭
  if (properties.circuitBreakerForceClosed().get()) {
      return true;
  }
  //如果断路器是关闭的状态 返回true
  if (circuitOpened.get() == -1) {
      return true;
  } else {
     //进到这里 说明断路器是打开的状态(可能是半开 可能是打开)
      //如果断路器是半开的状态返回false
      if (status.get().equals(Status.HALF_OPEN)) {
          return false;
      } else {
          //如果是打开的状态 要检查当前状态停留的时间是否超过了阈值
          return isAfterSleepWindow();
      }
  }
}    

尝试执行-attemptExecution

@Override
public boolean attemptExecution() {	
   if (properties.circuitBreakerForceOpen().get()) {
       return false;
   }
   if (properties.circuitBreakerForceClosed().get()) {
       return true;
   }
   if (circuitOpened.get() == -1) {
       return true;
   } else {
       //如果断路器处于打开的状态 并且如果打开状态超过阈值 就将状态设置成半开状态 
       //设置成功之后 true 让当前请求可以执行
       if (isAfterSleepWindow()) {
           if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) {
               return true;
           } else {
               return false;
           }
       } else {
           return false;
       }
   }
}

上面的代码设置if (status.compareAndSet(Status.OPEN, Status.HALF_OPEN)) 这个分支很关键。将打开状态下的断路器设置成半打开状态 设置成功之后立马返回true 允许一笔请求通过。设置失败说明当前状态是半开状态,所以保证了只有一笔请求能通过。

总结

断路器的所有的逻辑都是在HystrixCircuitBreakerImpl中,所以它是断路器的核心。幸运的是它的实现并不复杂 理解指标获取这一块是关键。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值