由于网络原因或者自身的原因,微服务并不能保证服务百分之百可用。如果单个服务出现问题,则调用该服务时会出现延迟甚至调用失败的情况;若调用失败,用户则会重新刷新页面并尝试再次调用,再加上其他服务的调用,从而增加了服务器的负载,导致某个服务瘫痪,甚至整个服务崩溃。

什么是微服务容错保护

Hystrix库可以解决以下问题:

  • 对第三方接口/依赖服务潜在的调用失败提供保护和控制机制。
  • 在分布式系统中隔离资源,减低耦合,防止服务之间相互调用而导致级连失败。
  • 快速失败及迅速恢复。
  • 在合适的时机对服务进行优雅降级处理。
  • 对服务提供近乎实时的监控、报警和控制操作。

Hystrix是根据”断路器“模式创建的。”断路器“本身是一个开关装置,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个符合预期的服务降级处理,而不是长时间地等待或抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间不必要地占用,从而避免了故障在分布式系统中蔓延乃至崩溃。
image.png
在请求失败频率较低的情况下,Hystrix还会直接把故障返回给客户端。只有当失败次数达到阈值(默认在20s内失败5次)时,断路器才会被打开并且不再进行后续通信,从而直接进行服务降级(ballback)处理。
Hystrix的开发和实现遵守设计理念:

  • 防止由于单个服务的故障,而耗尽整个系统容器(Tomcat,Jetty)的线程资源
  • 快速失败,而不是在队列中挤压服务请求
  • 提供服务降级(ballback)处理机制
  • 使用隔离技术(如舱壁隔离、永道和断路器模式)来隔离服务依赖之间的影响
  • 通过实时的监控和告警,及时发现系统中的潜在问题
  • 通过配置更改可以优化低延迟传播的恢复时间,并且Hystrix支持大多数属性的动态更改,从而允许开发者可以实时对低延迟反馈循序进行修改和优化
  • 提供对整个所依赖客户端在执行过程中的故障保护与隔离,而不仅仅是网络流量

快速启动Hystrix

继续对之前的项目对修改
1、引入Hystrix
引入spring-cloud-starter-netflix-hystrix依赖包。
image.png
2、开启Hystrix支持
在引导类中,增加@EnableCirCuitBreaker注解,开启对服务的荣作保护。
image.png
3、修改UserController实现
使用@HystrixCommand注解,该注解告诉服务调用者在调用失败时可以通过fallbackMethod参数指定的方法来实现服务降级处理。
image.png
4、容错测试
当两个微服务正常注册时,请求返回正常
image.png
image.png
现在把USER-SERVICE服务下掉,继续调用,返回的结果getUserNameFallback的内容。
image.png
image.png
5、服务降级的两种实现方式
1.使用注解完成服务降级实现
使用注解可以最小程度地侵入代码,可以快速让原来的功能支持服务降级。使用时仅需在要需要服务降级处理的方法上增加@HystrixCommand注解即可,并通过fallbackMethod属性设置该方法在降级处理时所使用的方法,然后在降级方法中实现服务降级处理逻辑。
@HystrixCommand注解属性说明:

  • groupKey:设定HystrixCommand分组的名称。
  • commandKey:设定HystrixCommand的名称。
  • threadPoolKey:设定HystrixCommand执行线程池的名称。
  • fallbackMethod:设定HystrixCommand服务降级所使用的方法名称,注意该方法需要与主方法定义在同一个类中,并且方法签名也要一致。
  • commandProperties:设定HystrixComand属性,比如,断路器失败百分比、断路器时间窗口大小等。
  • threadPoilProperties:设定HystrixCommand所执行线程池的属性,比如,线程池的大小,线程池等待队列长度等。
  • ingoreExceptions:设定HystrixCommand执行服务降级处理时需要忽略的异常,当这些异常出现时不会执行服务降级处理。
  • observableExecutionMode:设置HystrixCommand执行的方式。
  • defaultFallback:设置HystrixCommand默认的服务降级处理方法,如同时设定了fallbackMethod,会优先使用fallbackMethod。

2.继承HystrixCommand完成服务降级实现
继承HystrixCommand的降级代码示例
image.png
image.png
继承HystrixObservableCommand的降级代码实例
image.png
image.png
6、在Feign中使用Hystrix回退
@FeignClient增加fallback属性
image.png
创建UserServiceFallback继承UserService
image.png
增加yml文件配置,开启feign中的hystrix功能
image.png

Hystrix容错机制分析

Hystrix实现:

  • Hystrix通过HystrixCommand或HystrixObservableCommand对所有第三方依赖/服务调用进行封装,整个封装对象是运行在一个单独线程之中。
  • 可配置依赖调用超时时间,超时时间一般设为比99.5%平均时间略高即可。当调用超时时,直接返回或进行服务降级处理。
  • 为每个依赖关系/服务调用维护一个小的线程池(或信号量),如果已满,那么依赖服务调用将立即被拒绝,而不是排队等待。
  • 对服务调用的执行状态:成功、失败(客户端抛出异常)、超时或线程拒绝等进行统计。
  • 如果某服务调用的错误百分比高于阈值。则可以通过手动或自动的方式发开断路器,这样在一段时间内停止对该服务调用的所有请求。
  • 当服务请求被拒绝、连接超时或者断路器打开时,可以直接执行服务降级处理。
  • Hystrix提供实时的指标监控和配置变化。

1、Hystrix整体处理流程
image.png

  • 命令封装与执行:Hystrix主要是通过使用命令模式,将用户对业务服务调用请求的操作进行封装,通过该封装实现了调用者与实现者的解耦。Hystrix通过该模式来完成对整个请求的改造处理,从而实现了在不侵入微服务业务逻辑的情况下,为微服务增加了一层服务容错处理功能。
  • 结果缓存是否可用:当Hystrix开启缓存功能时,Hystrix在执行命令时首先会检查是否缓存命中,如果命中则立即将缓存的结果以Observale对象的形式返回。
  • 断路器是否已打开:当结果没有缓存命中之后,判断断路器的状态是否打开,如果打开,说明对应的服务不可用,Hystrix将转入服务降级处理。
  • 是否有资源执行:Hystrix判断该命令相关的线程池和队列是否已满,如果已满,Hystrix将转入服务降级处理。
  • 执行业务逻辑:前面的条件都满足之后,Hystrix将会调用HystrixCommand的run()方法或HystrixObservableCommand的construct()方法,执行具体的业务逻辑。

    • run():返回一个单一的结果,或者抛出一个异常
    • construct():返回一个Observable对象,通过该对象发送一个或多个返回数据,或者发送一个OnError错误通知。

      如果执行时间超时,执行线程将会抛出一个TIM额outException异常,Hystrix会转入服务降级处理。(超时时间配置execution.isolation.thread.timeoutInMilliseconds)
      如果命令执行成功,Hystrix返回结果之前,会记录一些日志和监控信息数据,以便后续对断路器健康状态进行评估。

  • 更新断路器健康数据:在整体过程中,Hystrix会将采集到的”成功“、”失败“、”拒绝“、”超时“等数据提交给断路器,断路器则会把这些统计数据更新到一系列的计数器中,然后根据这些统计数据计算断路器是否需要打开;一旦断路器打开,在恢复结束之前Hystrix都会对该服务进行熔断处理,

  • 服务降级处理

    • 断路器已打开
    • 无资源执行命令(线程池、队列或信号量已满)
    • 执行命令失败
    • 执行命令超时

      当使用HystrixCommand时通过getFallback()实现服务降级处理,使用HystrixObservableCommand时通过resumeWithFallback()实现服务降级处理。
      在实现服务降级处理时,最好不再有任何网络调用的依赖。一旦降级处理中包括网络处理,将会再次对该响应进行HystrixCommand/HystrixObservableCommand封装处理,从而造成级联处理,增大了系统的不稳定性。

  • 返回结果


2、HystrixCommand与HystrixObservableCommand**
区别:

  • 从命令模式上说,HystrixCommand是一个阻塞型命令,当执行命令时可以直接获取到执行结果。而HystrixObservableCommand是一个非阻塞型命令,该命令的调用者通过订阅其返回对象来获取执行的结果。
  • 从代码编写上说,HystrixCommand命令的业务逻辑写在run()方法中,服务降级逻辑写在getFallback()方法中;HystrixObservableCommand的业务逻辑写在construct()方法中,服务降级逻辑写在resumeWithFallback()方法中。
  • 从执行上说,HystrixCommand的run()是由新创建的线程执行;HystrixObservableCommand的construct则是由调用程序线程执行。
  • 从执行返回的结果上说,HystrixCommand只能返回一个执行结果;HystrixObservableCommand则可以按顺序向调用者发送多个执行结果。

4中命令执行方式的联系和区别:

  • execute():该方法将以同步堵塞方式执行run()。当调用execute()后,Hystrix会创建一个新线程执行run(),同时调用者会在execute()调用处一直处于堵塞状态,直到run()运行完成。
  • queue():该方法将以异步非阻塞方式执行run()。当调用queue()后,调用者线程就直接返回一个Future对象,同时Hystrix创建一个新线程运行run(),调用者通过Future.get()获取到run()的返回结果,而Future.get()则是阻塞执行的。
  • observe():对于HystrixCommand,将创建一个新线程以非阻塞方式执行run();对于HystrixObservableCommand,将在调用程序线程阻塞执行contruct()。然后再调用subscribe()完成事件注册,如果成功则触发onNext()和onComplated()方法,执行异常则触发onError()。
  • toObservable():与observe()不同的是,toObservable()方法是先注册,注册完成后自动触发执行run()或者construct()方法。
  • 最终每一种调用方式都是基于toObservable()方法来实现的。

3、断路器原理分析
在分布式架构中,当某个服务单元发生故障之后,通过断路器的故障监控,向调用方返回一个服务降级处理或错误相应,而不是让客户端长时间地等待。就不会使得线程因调用故障服务被长时间占用而不释放,避免了故障在分布式系统中蔓延。
Hystrix中的断路器就是起到服务容错保护的作用,Hystrix在运行过程中会向每个命令对应的断路器报告成功、失败、超时和拒绝的状态,断路器维护计算统计的数据,根据这些统计的信息来确定断路器是否打开。如果打开,后续的请求都会进行服务降级处理。
Hystrix配置参数:

  • circuitBreaker.enabled:是否启动断路器
  • circuitBreaker.forceOpen:是否强制开发断路器,如果强制打开那么将拒绝执行任何命令。
  • circuitBreaker.forceClosed:是否强制关闭断路器,如果强制关闭将允许执行任何命令。
  • circuitBreaker.errorThresholdPercentage:设置断路器开启的错误百分比,当一段时间内命令执行中的错误超过了阈值,断路器自动被打开。
  • circuitBreaker.requestVolumeThreshold:设定在一个滚动统计窗口中,开启断路器计算命令请求的最小阈值。在一个滚动统计窗口中如果命令请求数小于该值,即使所有请求都失败,断路器也不会打开。默认值为20。滚动统计窗口时长默认是10s,通过metrics.rollingStats.timeInMilliseconds进行设置。
  • circuitBreaker.sleepWindowInMilliseconds:设定断路器进行半开试探休眠时间,

总结:
断路器如何打开:当在一个滚动统计窗口中命令请求数超过requestVolumeThreshold设置的值(默认是20)时,命令执行失败百分比超过了errorThresholdPercenttage设置的值(默认值为50%)
断路器如何关闭:当断路器打开一段时间后(该值通过sleepWindowInMilliseconds设置),Hystrix进入半开状态,当一个命令请求通过这个断路器时,断路器不阻断,而是直接通过,如果该命令还执行失败,断路器则继续为打开状态。如果执行成功,则断路器关闭。

4、Hystrix异常——HystrixBadRequestException
正常在执行HystrixCommand的run()方法时,如果触发异常,就会执行降级处理。如果抛出HystrixBadRequestException异常后,Hystrix并不会触发服务降级处理。
如果在开发中抛出不需要进行降级处理的异常时,可以使用注解参数ignoreException。
image.png

服务隔离

Hystrix实现服务隔离的思路:

  • 使用命令模式(HystrixCommand/HystrixObservableCommand)对服务调用进行封装,使每个命令在单独线程中/信号授权下执行。
  • 为每一个命令的执行提供一个小的线程池/信号量,当线程池/信号量已满时,立即拒绝执行该命令,直接转入服务降级处理。
  • 为每一个命令的执行提供超时处理,当调用超时时,直接转入服务降级处理。
  • 提供断路器组件,通过设置相关配置及实时的命令执行数据统计,完成服务监控数据分析,使得在命令执行中可以快速判断是否可以执行,还是执行服务降级处理。

1、线程池隔离与信号量隔离
线程池隔离:不同服务的执行使用不同的线程池,同时将用户请求的线程与具体业务执行的线程分开,业务执行的线程池可以控制在指定的大小范围内,从而使业务之间不受影响,达到隔离的效果。
信号量隔离:用户请求线程和业务执行线程是同一线程,通过设置信号量的大小限制用户请求对业务的并发访问量,从而达到限流的保护效果。
线程池隔离的有点:

  • 应用系统会被完全保护起来,即使其中一个服务线程池满了,也不会影响到应用的其他服务。
  • 当引入一个新的客户端的时候,如果发生问题,只会影响新的服务,并不会影响其他服务。
  • 当一个失败的服务恢复正常时,系统会立即恢复正常的性能。
  • 如果我们的应用系统一些参数配置错误,那么线程池的运行状况将会很快被检测出来,如延迟、超时、拒绝等。同时可以通过动态属性实时执行来处理纠正错误的参数配置。
  • 如果服务的性能有变化需要调整,可以通过线程池指标动态属性修改,不会影响其他服务请求。
  • Hystrix拥有专门的线程池可提供内置的并发功能,可以在同步调用之上构建异步外观模式。

线程池隔离的缺陷:增加了计算的开销,每个业务请求在执行的时候,会涉及请求排队、线程调度、上下文切换等处理。
2、服务隔离的颗粒度
服务隔离默认根据Command、Group和ThreadPool的命名来控制。
Group从业务逻辑上划分Command为一组
每个独立的外部依赖一个独立的Command,拥有唯一的Command名称。
每个独立的Command和ThreadPool是一对一关系。
image.png
服务隔离颗粒度控制策略:

  • 服务分组+线程池:实现服务隔离的粗粒度控制,一个服务分组/系统配置一个隔离线程池即可。也可以不配置线程池名称或者配置相同的线程池名称。
  • 服务分组+服务+线程池:实现服务隔离的细粒度控制,一个服务分组中的每一个服务配置一个隔离线程池。
  • 混合实现:一个服务分组配置一个隔离线程池,然后对重要服务单独设置隔离线程池。

3、服务隔离配置

  • execution.isolation.strategy:设置服务隔离策略。THREAD为线程池隔离,SEMAPHORE为信号量隔离。默认为THREAD。
  • execution.isolation.thread.timeoutInMilliseconds:用来设置线程池隔离和信号量隔离两种策略的超时时间,单位为毫秒,默认值为1000ms。
  • execution.isolation.semaphore.maxConcurrentRequests:设置使用信号量隔离时最的信号量大小。当请求达到或超过时,就会被降级处理,默认值为10。
  • execution.timeout.enabled:是否开启业务服务超时处理,默认为true。
  • execution.isolation.thread.interruptOnTimeout:当业务超时时是否中断线程,默认值为true。
  • execution.isolation.thread.interruptOnCancel:取消时是否中断服务的执行,默认值为false。

通过Command构建Setter进行控制
image.png
通过注解方式进行配置
image.png

服务降级模式

1、快速失败
快速失败模式是指在服务降级处理逻辑中不提供任何处理,直接抛出一个异常。
image.png
2、静默失败
静默失败即当进行服务降级处理时返回空的结果,针对返回值类型,返回的可能是null、空List或者空Map等。
image.png
image.png
3、返回默认值
image.png
4、返回组装的值
当我们的执行结果返回的是一个包括多个字段的复杂对象时,就可以通过服务请求中的值及一些默认值来组装这个返回结果。比如从cookie、服务请求的参数及header、之前成功返回的结果中获取返回结果需要的值。
image.png
如果是继承HystrixObservableCommand且返回多个值,当服务降级时仅希望返回为出错之前的结果。
image.png
5、返回远程缓存
返回远程缓存是指在服务处理失败的情况下再发起一起远程请求,不过这次请求的是一个缓存,比如读取Redis中的缓存结果。
image.png
当使用远程缓存时,需要重新封装为Command进行调用。执行fallback的线程一定要与主线程区分开,否则可能会造成主线程休眠,线程池被消耗光。
image.png
image.png
6、主/从降级模式
如果从模式仅仅是用来作为主服务失败时的辅助处理,那么最好的方式是将主模式和从模式使用门面模式重新构建一个新的命令。
注意:由于主/从命令都是采用线程池隔离方式执行的,那么所构建的门面命令则可以使用信号量隔离方式,避免了系统多余的开销。
image.png
代码示例:
image.pngimage.pngimage.png

请求缓存

Hystrix提供的请求缓存可以在CommandKey/CommandGroup相同的情况下,直接共享命令执行的结果,降低依赖调用次数,在高并发和CacheKey命中率高场景下可以提升服务性能。
image.png
1、清除缓存
image.png
2、判断是否是从缓存中返回
HystrixCommand中提供了isResponseFromCache()方法,判断是否从请求缓存中返回。
也可以使用注解:

  • @CacheResult:标记返回结果需要进行缓存,该注解要与@HystrixCommand注解一起使用。
  • @CacheKey:用来标记如果构建缓存的值。
  • @CacheRemove:用来标记在方法执行完毕后清除指定的缓存。

    Hystrix监控

    1、Hystrix仪表盘
    首先,增加pom依赖项
    image.png
    其次,启动类中增加注解及bean
    image.png
    最后查看
    image.png
    当点击Monitor Stream之后,当通过Hystrix调用服务时
    image.png
    image.png
    图形信息中的实心圆:颜色表示实例的健康程度(绿色->黄色->橙色->红色),大小则根据请求流量的大小发生变化。
    图形信息中的曲线:统计了2分钟内的请求流量变化。

2、Turbine仪表盘集群监控
image.png
搭建Turbine服务器,pom文件
image.png
编写引导类
image.png
编写配置文件

  • turbine.app-config:配置需要监控的微服务的服务名称
  • trubine.cluster-name-expression:可以在Hystrix的Dashboard中定位不同的服务集群。

image.png
依次启动所有服务,访问 http://localhost:8200/hystrix
image.png