天天看点

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

目录

  • 前言
  • 断路器
  • HystrixCircuitBreaker
    • HystrixCircuitBreakerImpl
      • 属性
      • 熔断器打开
      • markSuccess
      • markNonSuccess
      • allowRequest
      • 尝试执行-attemptExecution
  • 总结

前言

我们前面对

Hystrix

的指标统计方式做了详细的介绍。

1.5.0

版本之前使用的是

HystrixRollingNumber

环形数组来作为指标的收集的模块,而

1.5.0

版本之后使用的是更灵活的响应式收集模式。有了各项请求的指标那么这一篇文章我们就来看看

Hystrix

的断路器是如何使用这些指标来作为依据的。

断路器

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

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

如下借用官网的一张图:

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

我们下面主要是来看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

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

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

继续阅读