vlambda博客
学习文章列表

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


往期推荐




什么是服务雪崩?


在分布式架构中,很常见的一个情形就是某一个请求需要调用多个服务。


如客户端访问 user 服务,而 user 服务需要调用 order 服务,order 服务需要调用 goods 服务,由于网络原因或者自身的原因,如果 order 服务或者 goods 服务不能及时响应,user 服务将处于阻塞状态,直到 order 服务 goods 服务响应。


此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。


服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。



1.如图所示,此时的系统正在愉快的运行中。


2.突然这个时候,goods服务节点的网络发生了故障。goods服务节点瘫痪,goods服务不可用。


3.由于good服务瘫痪导致order服务向goods服务发送的请求得不到返回,一直处于阻塞,此时user服务仍然一直 向order服务发送请求,最终导致order服务节点的资源耗尽,order服务节点瘫痪,order服务不可用。


4.由于good服务瘫痪导致order服务向goods服务发送的请求得不到返回,一直处于阻塞,此时user服务仍然一直 向order服务发送请求,最终导致order服务节点的资源耗尽,也瘫痪掉。此时user服务向order服务发送的请求同样也得不到返回,而客户端依然源源不断的向user服务节点发送请求,最终user服务节点和order服务节点一样,由于资源耗尽导致服务器瘫痪,user服务也不可用。


如上所述,一个服务节点的瘫痪,导致整条链路的服务节点都瘫痪的情形,我们称之为服务雪崩。


为什么会产生服务雪崩?


流量激增:比如异常流量、用户重试导致系统负载升高;


缓存穿透:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;


程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;


硬件故障:比如宕机,机房断电,光纤被挖断等。


数据库严重瓶颈,比如:长事务、慢sql等。


线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩。



有什么办法解决服务雪崩?


当发生突发流量激增的情况下,我们可以使用自动扩容,或者是在负载均衡器中添加服务限流功能


对于缓存穿透造成的服务雪崩问题,可以通过缓存预加载、缓存异步加载等方式来解决。


对于程序bug,emmm...,只能改bug了。


对于数据库查询时间过长导致的服务雪崩可以进行sql优化,硬件升级等。


对于硬件故障造成的服务雪崩, ,跨机房路由,异地多活等方式。


对于不同的造成服务雪崩的场景,有着很多不同的解决方案,但是没有一个通用的解决方案可以解决所有的问题。


在通过大量的实践证明,线程同步等待是最常见引发的雪崩效应的场景,此刻,本章节的主人公Hystrix将粉墨登场,我们将详细介绍如何使用Hystrix做故障隔离,熔断器机制等可以解决依赖服务不可用的问题。



Hystrix整体认知


Hystrix是一个用于处理微服务架构中的服务之间调用故障和容错的开源库。


在微服务架构里,各个服务之间的调用都可能会失败,比如超时、异常等。


Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务链路全线崩溃,提高微服务架构的可用性。


Hystrix,我们又称“断路器”,其本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。



Hystrix设计目标,实现方式


设计目标


(1)对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护。


(2)阻止某一个依赖服务的故障在整个系统中蔓延,服务A->服务B->服务C,服务C故障了,服务B也故障了,服务A故障了,整个系统全部故障,整体宕机。


(3)提供fail-fast(快速失败)和快速恢复的支持。


(4)提供fallback优雅降级的支持。


(5)支持近实时的监控、报警以及运维操作。


实现方式


  1. 通过hystrixCommand或者HystrixObservableCommand来封装对外部依赖的访问请求,这个访问请求一般会运行在独立的线程中。

  2. 对于超出我们设定的阈(yu)值服务调用,直接进行超时返回,不允许它长时间的阻塞。

  3. 对每一个依赖服务进行资源隔离。通过线程池或者是semaphore这两种方式。

  4. 对依赖服务被调用的成功次数,失败次数,拒绝次数,超时次数进行统计。

  5. 如果对某一个依赖服务的调用失败次数超过了一点的阈值,Hystrix自动进行熔断,并在一段时间内对该服务的调用直接进行降级,一段时间后再自动尝试恢复

  6. 当对一个服务调用出现失败、被拒绝、超时、短路等异常情况时,自动调用fallback降级机制。

  7. 对属性和配置的修改提供近实时的支持



Hystrix工作流程

首先我们看一下管网的Hystrix工作流程图:


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


下面我们针对这张图详细解读下Hystrix的工作流程。


1.每次调用都会创建HystrixCommand或者HystrixObservableCommand对象


2.执行execute(observe)或queue(toObservable)做同步\异步调用


3.检查请求结果是否被缓存,如果缓存直接返回


4.检查是否开启了断路器,如果开启直接跳到步骤8


5.检查线程池/信号量是否跑满,如果跑满进入步骤8


6.执行 HystrixObservableCommand.construct() or HystrixCommand.run(),如果执行异常或者调用超时直接跳到步骤8


7.计算断路器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给断路器,用于统计从而判断断路器状态


8.调用fallback降级机制,通过上述步骤会有(熔断器打开,线程池/信号量跑满,调用超时,调用失败)四种情况会进行降级处理


9.返回依赖请求的真正结果


工作流程详解


第一步,创建HystrixCommand或者HystrixObservableCommand对象


HytrixCommand和HystrixObservableCommand包装了对外部依赖访问的逻辑。

整个流程的第一个步骤就是实例化HystrixCommand或者HystrixObservableCommand对象。


在构造这两个Command对象时,可以通过构造方法传递任何执行过程中需要的参数。


如果对外部依赖调用只返回一个结果值,那么可以实例化一个HystrixCommand对象。


HystrixCommand command = newHystrixCommand(arg1, arg2);


如果在调用外部依赖时需要返回多个结果值时,可以实例化一个HystrixObservableCommand对象


HystrixObservableCommand command = newHystrixObservableCommand(arg1, arg2);

第二步,执行execute(observe)或queue(toObservable)做同步\异步调用


HystrixCommand主要是使用以下两个命令


execute():同步执行,从依赖的服务返回一个单一的结果对象,或者是在发生错误的时候抛出异常。


queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。


HystrixObservableCommand使用以下两个命令


observe():返回Observable对象,返回 Observable 对象,立即发出请求,在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果,它是一个Hot Observable。


toObservable():返回Observable对象,但只有在订阅该对象时,才会发出请求,然后在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果,它是一个Cold Observable。


第三步,检查请求结果是否被缓存,如果缓存直接返回


若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。


这个结果缓存的好处为:

1、在同一个请求上下文中,可以减少使用相同参数请求原始服务的开销。
2、请求缓存在步骤5执行之前生效,所以可以有效减少不必要的线程开销。


第四步,检查是否开启了断路器


在缓存没有被命中时,Hystrix会在执行步骤5之前先检查断路器是否被打开。如果打开了,Hystrix不会执行任何命令执行跳转到步骤8


断路器开关控制条件:
1.对外部依赖调用的次数满足配置的阈值

2.对外部依赖调用发生错误的比率满足配置的阈值


在满足以上两个条件时,断路器打开熔断开关,之后所有对外部依赖调用都将被直接断开。


在开关打开时长超过试探窗口期后,断路器将尝试放行部分外部依赖的调用,

根据试探的结果决定重新开启或者关闭熔断开关。


第五步,检查线程池/信号量是否跑满


我们知道,Hystrix引入了线程池和信号量两种方式实现资源隔离机制。如果此时命令对应的线程池或队列或信号量已经满了,直接跳转到步骤8。


第六步,执行 HystrixObservableCommand.construct() or HystrixCommand.run()


Hystrix会根据我们编写的方法来决定采取什么方式去请求依赖服务。


1.HystrixCommand.run()——返回单个响应或抛出异常。


2.HystrixObservableCommand.construct()——返回 Observable 对象来发射多个结果,或通过onError发送错误通知。


如果run()或construct()方法执行时长超过了命令的超时阀值,其线程将抛出一个TimeoutException(或者在一个单独的线程抛出,如果命令没有运行在它自己的线程)。


这种情况下 Hystrix转接到fallback处理逻辑(第8步)。


并且如果该命令没有取消或中断,它将放弃run()或construct()方法最终的返回值。


如果命令没有抛出异常并且返回了响应,Hystrix 将会在执行一些日志记录和度量报告之后返回结果给调用者。


如果是通过run()运行,Hystrix 将返回 Observable 发射单个结果,然后发送一个onCompleted的通知;如果是通过construct()运行,Hystrix 直接返回该方法产生的Observable对象。


第七步,计算断路器状态


Hystrix会将每一个依赖服务的调用成功,失败,拒绝,超时,等事件,都会发送给circuit breaker断路器。


HystrixCircuitBreaker通过维护一系列的counter记录外部依赖请求的执行情况。


断路器根据维护的这些信息,在符合触发条件下开启断路功能,在条件合适的时候关闭断路开关。


如果打开了断路器,那么在一段时间内就会直接短路,然后如果在之后第一次检查发现调用成功了,就关闭断路器。


第八步,调用fallback降级机制


通过对上述步骤的详细解读,我们发现有以下几种情况是会调用fallback降级机制的。
1.断路器打开 2.线程池或者信号量已经满了 3.command执行异常 4.执行超时


在服务降级逻辑中,需要实现一个通用的响应结果,并且该结果的处理逻辑应当是从缓存或是根据一些静态逻辑来获取,而不是依赖网络请求获取。


如果一定要在服务降级逻辑中包含网络请求,那么该请求也必须包装在HystrixCommand或HystrixObservableCommand中,从而形成级联的降级策略。


而最终的降级逻辑一定不是一个依赖网络请求的处理,而是一个能够稳定的返回结果的处理逻辑。


1.在 HystrixCommand 中,在 HystrixCommand.getFallback()方法中提供自定义的回调逻辑,方法返回单个回调值。 


2.在 HystrixObservableCommand 中,在HystrixObservableCommand.resumeWithFallback() 方法中提供自定义的回调逻辑,方法返回一个Observable对象来发射一个或多个降级结果


如果fallback返回了结果,那么Hystrix就会返回这个结果。


对于HystrixCommand,会返回一个Observable对象,其中会发返回对应的结果;


对于HystrixObservableCommand,会返回一个原始的Observable对象。


如果没有实现fallback,或者是fallback抛出了异常,Hystrix会返回一个Observable,但是不会返回任何数据。


不同的command执行方式,其fallback为空或者异常时的返回结果不同


1.对于execute(),直接抛出异常


2.对于queue(),返回一个Future,调用get()时抛出异常


3.对于observe(),返回一个Observable对象,但是调用subscribe()方法订阅它时,抛出调用者的onError方法


4.对于toObservable(),返回一个Observable对象,但是调用subscribe()方法订阅它时,抛出调用者的onError方法


第九步,返回依赖请求的真正结果


如果Hystrix命令执行成功,它将以Observable形式返回响应给调用者。根据你在步骤2的调用方式不同,在返回Observablez之前可能会做一些转换。


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


execute():通过调用queue()来得到一个Future对象,然后调用get()方法来获取Future中包含的值。


queue():将Observable转换成BlockingObservable,在将BlockingObservable转换成一个Future。


observe():订阅返回的Observable,并且立即开始执行命令的逻辑。


toObservable():返回一个没有改变的Observable,你必须订阅它,它才能够开始执行命令的逻辑。


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!

上述就是整个Hystrix的工作流程,当然没有很深入的讲解,但是还是建议多看几遍,我面试的时候碰到好几次让我简述Hystrix工作流程,多看几遍,记在心里,面试不慌。


Hystrix使用框架搭建

当然了,Hystrix也能和Feign 和 Zuul 的集成使用,这些在这里就不赘述了,后续介绍Feign和Zuul的文章中会详细说明。


本文主要介绍HystrixCommand 注解方式的使用。


首先我们搭建一个HystrixClient项目。


添加配置文件application.properties

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


新建RestConfiguration类,用来全局配置RestTemplate

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


新建HystrixController

然后在启动类的上添加@EnableHystrix注解。

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


改造上一篇中提供的OrderService,让代码休眠5秒后在返回。


在HystrixController中的三个方法中分别配置了2000ms,10000ms,10000ms如果没有返回结果,那么将直接回调用我们指定的fallback。


OrderService

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


上述三步,基本的Hystrix使用框架就搭建完成了,然后我们启动上一篇中提到的Eureka-Server,并按照上篇文章的启动方式,分别启动OrderServeice的7777,8888,9999三个端口,此时我们打开 http://localhost:8761/ 页面,我们发现已经有三个服务名为order-service,端口号分别7777,8888,9999的服务注册了进来。


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


最后我们启动HystrixClient启动类,然后我们先访问设置超时时间为10000ms的 localhost:8088/test2 ,因为我们在OrderService中设置的休眠时间为3000ms所以能在超时时间内返回请求,所以不用调用fallback。



Hystrix核心配置详解

Execution相关的属性的配置


hystrix.command.default.execution.isolation.strategy 


隔离策略,默认是Thread, 可选Thread,Semaphore


hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 


命令执行超时时间,默认1000ms。


hystrix.command.default.execution.timeout.enabled 


执行是否启用超时,默认启用true。


hystrix.command.default.execution.isolation.thread.interruptOnTimeout


发生超时是是否中断,默认true。


hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 


理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。semaphore应该占整个容器(tomcat)的线程池的一小部分。


Fallback相关配置


hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 


如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10。


hystrix.command.default.fallback.enabled 


当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true。



Metrics相关属性配置


hystrix.command.default.metrics.rollingStats.timeInMilliseconds


设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000。


hystrix.command.default.metrics.rollingStats.numBuckets 


设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10。


hystrix.command.default.metrics.rollingPercentile.enabled


执行时是否enable指标的计算和跟踪,默认true。


hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds


设置rolling percentile window的时间,默认60000。


hystrix.command.default.metrics.rollingPercentile.numBuckets


设置rolling percentile window的numberBuckets。逻辑同上。默认6。


hystrix.command.default.metrics.rollingPercentile.bucketSize


如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100。


hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds


记录health 快照(用来统计成功和错误绿)的间隔,默认500ms。


ThreadPool 相关属性配置


hystrix.threadpool.default.coreSize


并发执行的最大线程数,默认10。


hystrix.threadpool.default.maxQueueSize


BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。


hystrix.threadpool.default.queueSizeRejectionThreshold


即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用。


hystrix.threadpool.default.keepAliveTimeMinutes


如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。


hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds


线程池统计指标的时间,默认10000。


hystrix.threadpool.default.metrics.rollingStats.numBuckets


将rolling window划分为n个buckets,默认10。



Hystrix容错机制之回退降级‍‍‍‍‍‍‍‍‍


1.什么是降级?


降级,通常指事务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务,或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保障主体业务不受影响。


Hystrix提供的降级主要是为了容错,保证当前服务不受依赖服务故障的影响,从而提高服务的健壮性。


要支持回退或降级处理,可以重写HystrixCommand的getFallBack方法或HystrixObservableCommand的resumeWithFallback方法。


2.什么情况下才会走降级?


从Hystrix的工作流程图中我们可以看到以下情况会走降级逻辑。


1.断路器打开


2.线程池或者信号量已经满了


3.command执行异常


4.执行超时


3.回退降级有哪些处理方式?


  1. 快速失败:发生故障后直接抛出,不做处理。

  2. 无声失败:发生故障后,返回无意义内容,如null,空Map等,故障会被屏蔽。

  3. 静态失败:这种配置下,发生故障会返回静态的默认值,如返回值是boolean,则结果为默认true。

  4. Stubbed:这种配置适用于返回值是一个复合对象的情形,发生故障时,会手动创建一个复合对象的实例,实例中往往包含了一些默认值或错误信息。

  5. 依赖缓存:这种情况下,当下层服务故障时,会从缓存中取得之前的旧数据供使用。

  6. 主次模式:这是回退降级的一种特殊使用方法。


主次模式解释:

有时候,我们可能会遇到这样的场景。针对某个业务,可能会有两种处理方案,A方案高效,但是没有经过规模化测试,不敢保证可靠性。B方案保守,虽然效率较低,但是不会出现。这时候,我们就可以尝试采用主次模式。主流程基于A方案运行,fallback基于B方案运行。在运行过程中,如不出错,则一直使用A方案,一时出错,可通过回退降级,迅速切换为B方案,以避免问题的不受控扩散。




Hystrix监控系统搭建


什么是Hystrix Dashboard?


Hystrix提供了准实时的调用监控(Hystrix DashBoard),Hystrix会持续的记录通过Hystrix发起的请求的执行信息,以统计报表和图形的形式展示给客户,包括每秒执行多少,请求多少成功,请求失败多少等。


Netflix通过Hystrix-metics-event-stream项目实现了对以上指标的监控,SpringCloud也提供了Hystrix DashBoard的整合,对监控内容转化成可视化的界面,以便于用户能够直接的看到服务和集群的状态,在实际使用中,我们往往还要结合Turbine来使用。


开始搭建Hystrix Dashboard


Hystrix Dashboard的搭建其实很简单,分为三步:


  1. 创建监控Hystrix Dashboard项目模块

  2. 配置application.yml

  3. 配置启动类



首先我们创建一个HystrixDashboard项目,配置pom.xml文件如下

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


配置端口为9001

server.port = 9001

配置启动类,添加@EnableHystrixDashboard注解


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


启动后访问http://localhost:9001/hystrix只要我们能看到一只豪猪就说明启动成功了。


什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!


Hystrix使用总结

本文从介绍服务雪崩引入Hystrix,首先带领大家对Hystrix进行了一个整体认知,并且介绍了Hystrix的设计目标以及实现方式。


然后详细分九步了Hystrix的整个工作流程,并且带领大家实战搭建了Hystrix框架和Hystrix监控系统。


最后详细介绍了Hystrix的核心配置,以及Hystrix的重中之重之回退降级。


Hystrix断路器同样是SpringCloud生态系统中不可缺少的一环,同样也是面试中经常会出现的高频面试题。



什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!

原创不易,如果大家喜欢,赏个分享点赞在看三连吧。和大家一起成为这世界上最优秀的人。

什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!