写在前面

思考的过程往往比直接得到结论更加重要

1、为什么需要熔断器

服务雪崩

在分布式环境下,不可避免的就是服务之间的调用。A 调 B,B 可能会失败,如果此时 B 服务挂掉,那么会导致服务 A 因为服务 B 的失败而失败。从而导致 客户端认为 A 也是失败的。 简单说就是,牵一发而动全身

这也是,我们需要熔断器的原因。我们需要有保护服务调用的组件。当服务 B 挂掉时,服务 B 需要能够快速失败。

2、如果自己写一个断路器,你会怎么做呢?

隔离策略

思考一下,什么是隔离?
我对他的理解,大概就是,服务 A 调用服务 B,服务 C。不能因为调用服务 B 出现问题,而导致调用服务 C 也出现问题。也就是,服务B、服务C 的调用应该放在不同的环境下。

常见的资源隔离,有线程池隔离,信号量隔离

线程池隔离 信号量隔离
线程 请求线程和调用 provider 不是同一个线程 请求线程和调用 provider 是同一个线程
开销 排队、调度、上下文开销等 无线程切换,开销低
异步 支持 不支持
并发支持 支持(线程池大小) 支持(信号量上限)
传递 Header 无法传递 Http Header 可以传递 Http Header
快速失败

当 provider 提供的服务不可用,或出现异常时,应该有可供回调的降级方法。

  • provider 调用失败抛出异常,可能针对某种异常,不想执行快速失败策略,而是需要直接抛出异常
  • provider 调用失败,可能不想执行快速失败策略,也不想抛出任何异常。
限流

当 provider 被大量调用时,为了保护链路,需要做限流。那么其实核心的问题是,如何进行统计。

统计

每个 provider 的调用情况需要统计。这样可以更好的监控到 provider 提供的服务的情况。统计的算法,应该是基于滑动窗口进行统计。

熔断

因为有对每个 provider 调用情况统计,在调用之前,失败次数达到某个阈值时,可以认为该 provider 已经是有问题的,可以直接快速失败,以防止服务雪崩情况。

系统自适应保护

熔断机制是针对每个 provider 的。但是,有可能服务系统已经要达到极限了,不能再接收任何请求了,那么此时,出于系统的保护,也应该快速失败。

扩展

作为一个组件,支持扩展那是必须。
常见的扩展方式:(其实就是面向接口编程,在某些执行流程中,暴露出部分接口。剩下的就看你怎么封装了)

  • 观察者设计模式 + 策略设计模式。 例如
public interface SayListener {
  String say();
}

public class App {
    List<SayListener> sayListener = new ArrayList<>();
    public void doSomething() {
      // ...
       
      sayAction();
       // 触发了 say 的动作
      if (null != sayListener && sayListener.size() > 0) {
          sayListener.forEach(listener -> {
            listener.say();
          });
      }
       
       
      // ...
    }
    
    public void sayAction() {}
    
    public void registerSayListener(SayListener listener) {
       sayListener.add(listener);
    }
}

3、现有类似功能组件对比

sentinel hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件形式 接口形式
基于注解的支持 支持 支持 支持
限流 基于 QPS, 支持基于调用关系的限流 基于线程池个数有限支持 Rate Limiter
系统自适应保护 支持 不支持 不支持
控制台 丰富 简单 不提供控制台,可对接其他监控系统

hystrix wiki 介绍实在太全了,这里就没必要在介绍了,主要是从个人的角度去思考如果让自己也实现一个断路器中间件,你会怎么做?(鉴于 hystrix 停止维护,就没看源码了,以及 sentinel 的活跃,重心会放在 sentinel 上)

4、使用注意

在使用 hystrix 需要注意的几个地方。

hystrix 超时设置

设置 hystrix 超时时间时,需要大于等于 ribbon 的超时时间。 例如 ribbon 的配置如下

ReadTimeout=1000 
ConnectTimeout=1000
MaxAutoRetries=1
MaxAutoRetriesNextServer=1

那么 hystrix 的超时时间公式为:

    execution.isolation.thread.timeoutInMilliseconds >= (MaxAutoRetriesNextServer + 1) * (MaxAutoRetries + 1) * (ReadTimeout + ConnectTimeout)
hystrix 使用线程池隔离时,无法传递绑定在 tomcat 线程上下文的值

因为使用线程池隔离时(execution.isolation.strategy=THREAD),会新创建一个线程,因此 tomcat 线程的变量则无法传递给新的线程。那么此时可以使用 信号量隔离(execution.isolation.strategy=SEMAPHORE)。因为使用信号量隔离时,会使用 tomcat 线程调用远程服务。
其实 hystrix 也想到了会有这样的需求, 因此在 wiki 中,推荐我们继承 HystrixConcurrencyStrategy,重写 wrapCallable() 方法。 具体代码参考 线程池隔离传递 ThreadLocal 值
该作者是基于 spi 实现的(牛逼),其实 hystrix 提供了可配置的参数供我们配置

hystrix:
  plugin:
    HystrixConcurrencyStrategy:
      implementation: com.csp.hystrix.MyHystrixConcurrencyStrategy

心无私天地宽
513 声望22 粉丝