Hystrix使用及原理

623 阅读10分钟

Hystrix使用及原理

1.Hystrix的基本使用

1.1 为什么需要Hystrix?

在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单个服务又必须集群部署。由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪,甚至导致服务“雪崩”。

1.2 Hystrix能解决什么问题?

  1. 服务降级

    在高并发情况下,防止用户一直等待(返回一个友好的提示,直接给客户端,不会去处理请求,调用fallBack本地方法),目的是为了用户体验。 秒杀----当前请求人数过多,请稍后重试

  2. 服务熔断

    熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用

  3. 服务隔离机制

    因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat 线程池默认极限,可能会导致其他服务无法访问。 解决服务雪崩效应:使用服务隔离机制(线程池方式和信号量),使用线程池方式实現

    隔离的原理:

    相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。

    • 线程池隔离:每个服务接口,都有自己独立的线程池,每个线程池互不影响。
    • 信号量隔离:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返 回成功后计数器-1。
  4. 限流

    高并发的流量涌入进来,比如说突然间一秒钟100万QPS,废掉了,10万QPS进入系统,其他90万QPS被拒绝了

  5. 运维监控

    监控+报警+优化,各种异常的情况,有问题就及时报警,优化一些系统的配置和参数,或者代码

1.3 如何使用Hystrix?

Hystrix把执行都包装成一个HystrixCommand,并启用线程池实现多个依赖执行的隔离。Hystrix每个command都有对应的commandKey可以认为是command的名字,默认是当前类的名字getClass().getSimpleName(),每个command也都一个归属的分组,这两个东西主要方便Hystrix进行监控、报警等。HystrixCommand使用的线程池也有线程池key,以及对应线程相关的配置。

  • 自定义command key HystrixCommandKey.Factory.asKey("testKey")

  • 自定义command groupHystrixCommandGroupKey.Factory.asKey("testGroup")

  • 自定义线程池HystrixThreadPoolKey.Factory.asKey("testPool")

HystrixCommand和分组、线程池三者的关系

commandKey分组内唯一,HystrixCommand和分组、线程池是多对1的关系。分组和线程池没关系。

fallback

1.单个fallback

fallback就是当HystrixCommand 执行失败的时候走的后备逻辑,只要实现HystrixCommand 的fallback方法即可

2.多级fallback

当我们执行业务的时候,有时候会有备用方案1、备用方案2,当备用方案1失败的时候启用备用方案2,所以可以使用多级fallback。 多级fallback没有名字那么神秘,说到底其实就是HystrixCommand1执行fallback1, fallback1的执行嵌入HystrixCommand2,当HystrixCommand2执行失败的时候,触发HystrixCommand2的fallback2,以此循环下去实现多级fallback,暂未上限,只要你的方法栈撑的起。

3.主次多HystrixCommand fallback

这里探讨的是 主Command里串行执行 多个Command时的fallback执行逻辑

不管任何一个执行失败都认为主command的run执行失败,进而进入主command的fallback

2 Hystrix 的工作原理与高级使用

2.1 Hystrix 如何隔离服务调用?

采用舱壁模式,Docker就是舱壁模式的一种,在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池.比如说,一个服务调用两外两个服务,你如果调用两个服务都用一个线程池,那么如果一个服务卡在哪里,资源没被释放

后面的请求又来了,导致后面的请求都卡在哪里等待,导致你依赖的A服务把你卡在哪里,耗尽了资源,也导致了你另外一个B服务也不可用了。这时如果依赖隔离,某一个服务调用A B两个服务,如果这时我有100个线程可用,我给A服务分配50个,给B服务分配50个,这样就算A服务挂了,我的B服务依然可以用。

  1. Hystrix使用命令模式HystrixCommand(Command)包装依赖调用逻辑,每个命令在单独线程中/信号授权下执行。
  2. 可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可.当调用超时时,直接返回或执行fallback逻辑。
  3. 为每个依赖提供一个小的线程池(或信号),如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
  4. 依赖调用结果分:成功,失败(抛出异常),超时,线程拒绝,短路。 请求失败(异常,拒绝,超时,短路)时执行fallback(降级)逻辑。
  5. 提供熔断器组件,可以自动运行或手动调用,停止当前依赖一段时间(10秒),熔断器默认错误率阈值为50%,超过将自动运行。
  6. 提供近实时依赖的统计和监控

2.2 Hystrix 是如何工作的?

2.2.1 hystrix的熔断设计
  • 熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。

  • 熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复。

  • 熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警

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

  • 运行超时:直接可使用线程池提供的get方法

2.3.1 Hystrix采用的命令模式(Command Pattern)。

在面对对象编程中,命令模式是一种行为模式,其中对象用于封装执行动作或稍后触发事件所需的所有信息。这些信息包括方法名称,拥有该方法的对象以及方法参数的值。(来自维基百科)

命令模式包括4个角色:

Command:定义命令的统一接口

ConcreteCommand:Command接口的实现者,用来执行具体的命令,某些情况下可以直接用来充当Receiver。

Receiver:命令的实际执行者

Invoker:命令的请求者,是命令模式中最重要的角色。这个角色用来对各个命令进行控制。

命令模式

命令模式有如下优点:

  1. 降低对象之间的耦合度(将发出请求的对象和执行请求的对象解耦,即将调用者和执行者进行解耦)

  2. 新的命令可以很容易地加入到系统中。

  3. 可以比较容易地设计一个组合命令。

  4. 调用同一方法实现不同的功能

Hystrix命令模式封装了命令运行逻辑(run)和服务调用失败时回退逻辑(getFallback)。其command抽象类是hystrixcommand,用于包装执行具有潜在风险功能的代码(通常指通过网络进行的服务调用),具备容错和延时,统计和性能指标捕获,断路器和舱壁功能。

该命令本质上是一个阻塞命令,但如果与observe()一起使用,则提供一个可观察的facade。

2.3.2 Hystrix请求命令:HystrixCommand和HystrixObservableCommand
  1. HystrixCommand

    HystrixCommand用在依赖服务返回单个操作结果的时候

  2. HystrixObservableCommand

    用在依赖服务返回多个操作结果的时候,其实可以认为返回的是个事件发射器,可以持续发射数据

2.3.3 Hystrix的四种调用方法有何异同?

众所周知,Hystrix一共提供了4种调用方法供以使用:

  • toObservable() :未做订阅,只是返回一个Observable
  • observe():调用 #toObservable() 方法,并向 Observable 注册,rx.subjects.ReplaySubject发起订阅,因此它具有回放的能力 observe() 方法使用了ReplaySubject缓存了toObservable的消息,使得执行后再监听也可以收到所有消息。新订阅者连历史数据也能够监听到(1分钟内)
  • queue():调用toObservable().toBlocking().toFuture()返回 Future 对象
  • execute():调用#queue() 方法的基础上,马上调用 Future#get() 方法,同步返回 #run() 的执行结果。
2.3.4 observe() vs toObservable()

四种调用方法中,最难区分的当属observe() 和toObservable()了,这里做进一步的解释说明和对比。

observe()和toObservable()虽然都返回了Observable对象,但是observe()返回的是Hot Observable,该命令会在observe()调用的时候立即执行,当Observable每次被订阅的时候会重放他的行为; 而toObservable()返回的是Cold Observable,toObservable()执行之后,命令不会被立即执行,只有当所有订阅者都订阅它之后才会执行。

execute()、queue()也都使用了RxJava来实现,并且queue()是通过toObservable()来获得一个Cold Observable(不会立马执行),并且通过toBlocking()将该Observable转换成BlockingObservable,它可以把数据以阻塞的方式发出来,而toFuture方法则是把BlockingObservable转换成一个Future,该方法只是创建一个Future返回,并不会阻塞,这使得消费者可以自己决定如何处理异步操作

说明:Future实例出来后,目标方法是立马执行的,只是它不会阻塞主线程,并且执行结果你可以在其它地方get获取(若还没执行完成时去get,会阻碍)

2.3.4 Hystrix工作流程
  1. 当调用出现错误时,开启一个时间窗(默认 10秒)

    1. 在这个时间窗内,统计调用次数是否达到最小请求数?
      • 如果没有达到,则重置统计信息,回到第1步
        • 如果没有达到最小请求数,即使请求全部失败,也会回到第1步
        • 如果达到了,则统计失败的请求数占所有请求数的百分比,是否达到阈值?
          • 如果达到,则跳闸(不再 请求对应的服务)
          • 如果没有达到,则重置统计信息,回到第1步
  2. 如果跳闸,则会开启一个活动窗口(默认5秒),每隔5秒,Hystrix 会让一个请求通过,到达那个正在苦苦挣扎的服务,看是否调用成功 如果成功,重置断路器,回到第1步 如果失败,回到第3步

    image-20220117163435768

3 Hystrix 与Feign结合使用

通过@FeignClient定义Fegin方法,并定义name和fallbackFactory。name属性可用于定义Feign方法的Hystrix的个性化配置,而不使用全局默认配置。fallbackFactory设置带有异常信息的回调方法。

@FeignClient(name="service",fallbackFactory = HystrixClientFallbackFactory.class )
public interface IHystrixClient {
    @RequestMapping(value = "/hystrix/test", method = RequestMethod.POST,consumes="application/json;charset=UTF-8")
    String simpleHystrixClientCall(@RequestParam("time") long time);
}
@Component
public class HystrixClientFallbackFactory implements FallbackFactory<IHystrixClient> {
    private static final Logger log = LoggerFactory.getLogger(MyHystrixClientFallbackFactory.class);
    @Override
    public IMyHystrixClient create(Throwable throwable) {
        return new IHystrixClient() {
            @Override
            public String simpleHystrixClientCall(long time) {
                log.error("异常处理={}", throwable);
                return "Execute raw fallback: access service fail , req= " + time + " reason = " + throwable;
            }
        };
    }
}