背景
在微服务架构中,存在着很多的服务单元,若一个单元出现故障,就很容易因为依赖关系而导致故障的蔓延,最终导致整个系统的瘫痪,为了解决这个问题,产生了断路器等一系列服务保护机制。SpringCloud Hystrx具备服务降级,服务熔断,请求缓存,请求合并以及服务监控等强大功能
雪崩效应的常见场景
基础服务的故障会导致其他服务发生故障,服务与服务之间相互依赖,从而发生崩溃,我们称这种现象为雪崩效应。它的常见场景如下:
- 硬件故障:服务器宕机,机房断电
- 流量激增
- 缓存穿透:一般发生在服务重启时,所有缓存失效;以及短时间内大量缓存失效时,大量的缓存不命中,导致请求直击后端服务,引起服务不可用
- 程序BUG
- 同步等待:服务间采用同步调用方式,同步等待导致资源耗尽
初探Hystrix
Hystrix的工作流程
- 构造一个HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数
- 执行命令,Hystrix提供了4种执行命令的方法
- 判断是否使用缓存响应请求,若启动了缓存且缓存可用,直接使用缓存响应请求
- 判断熔断器是否打开,如果打开跳到第8步
- 判断线程池/队列/信号量是否已满,已满则跳到第8步
- 执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或超时,跳到第8步,否则跳到第9步
- 统计熔断监控指标
- 走Fallback备用逻辑
- 返回响应请求
Hystrix执行命令的几种方法
execute()和queue()适用于HystrixCommand对象,而observe()和toObserveable()方法适用于HystrixObservableComand对象
- execute():以同步阻塞方式实现run(),只支持接收一个值对象。Hystrix会从线程池中取出一个线程来执行run(),并等待返回值
- queue():以异步非阻塞方式执行run(),只支持接收一个值对象。调用queue()就直接返回一个Future对象,可以通过Future.get()拿到run()的返回结果。
- observe():如果继承的是HystrixCommand,Hystrix会从线程池中取出一个线程以非阻塞方式执行run();如果继承的是HystrixObservableCommand,将以阻塞方式执行construct()。支持接收多个值对象。
- toObservable():基本过程与observe()类似,不同的地方在于调用observe()返回的是hot Observable,也就是说observe()会自行触发run()/construct(),无论是否存在订阅者;而调用toObservable()方法返回的是cold Observable,不会立即触发run()/construct(),必须有订阅者订阅Observable才行
Hystrix容错
Hystrix的容错主要是通过添加容许延迟和容错方法,帮助控制这些分布式服务之间的交互。还通过隔离服务之间的访问点,阻止他们之间的联级故障以及提供回退选项来提高系统的弹性。Hystrix主要有以下几种容错方法:
- 资源隔离
- 熔断
- 降级
资源隔离
资源隔离主要是对线程的隔离,Hystrix提供了两种线程隔离的方式:线程池和信号量
线程隔离-线程池
Hystrix通过命令模式对发送请求的对象和执行请求的对象进行解耦,将不同的业务请求封装成对应的命令请求。当第一次创建Command时,根据配置创建一个线程池,后续相同的请求创建Command时,将会重用已创建的线程池 。通过将发送请求线程与执行请求的线程分离,可有效防止发生级联故障。当线程池或请求队列饱和时,Hystrix将拒绝服务,使得请求线程可以快速失败,从而避免依赖问题扩散。 线程隔离之后服务的依赖关系线程池隔离的优缺点
优点:
- 保护应用程序以免受到来自依赖故障的影响
- 当依赖从故障中恢复正常时,应用程序会立即恢复正常的性能
- 当应用程序的参数配置错误时,线程池会很快检测到这一点并纠正错误的参数配置
- 如果服务器性能有变化,比如增加或减少超时时间,可以通过线程池指标动态属性修改不会影响到其他调用请求
缺点:
每个命令的执行都在单独的线程中完成,增加了排队,调度和上下文切换的开销
线程隔离-信号量
当依赖延迟极低的服务时,线程池隔离技术引入的开销超过了它所带来的好处。这时候可以使用信号量隔离技术来代替。客户端想依赖服务发起请求时,首先要获取一个信号量才能真正发起调用,由于信号量的数量有限,当并发请求量超过信号量个数时,后续的操作都会直接拒绝。信号量隔离主要是通过控制并发请求量,达到限流的目的
线程池和信号量的区别
- 信号量不需要切换线程,不支持异步,不支持超时,支持熔断,支持限流,开销小
- 线程池需要切换线程,支持异步,支持超时,支持熔断,支持限流,开销小
熔断
Hystrix向熔断器报告成功,失败,超时和拒绝的状态,熔断器维护并统计这些数据,并根据这些统计信息来决策熔断开关是否打开。如果打开,熔断后续请求,快速返回。每隔一段时间后(默认5秒)熔断器尝试半开,放入一部分请求流量进来,如果请求成功,熔断器关闭
熔断器配置
- circuitBreaker.enabled:是否启用熔断器,默认为true
- circuitBreaker.forceOpen:强制打开熔断器
- circuitBreaker.forceClosed:强制关闭熔断器
- circuitBreaker.errorThresholdPercentage:错误率,默认是50%,如果一段时间内请求错误率大于50%,这种情况下会触发熔断器打开
- circuitBreaker.requestVolumeThreshold:默认是20,意思是一段时间内至少有20个请求才进行errorThresholdPercentage计算
- circuitBreaker.sleepWindowInMilliseconds:半开状态试探睡眠时间,默认是5000ms。如熔断器开启5000ms之后会尝试放过去一部分流量进行试探,确认依赖服务是否恢复
熔断器工作原理
- 调用allowRequest()判断是否允许将请求提交到线程池
- 调用isOpen()判断熔断器是否打开。如果熔断器打开进入第三步;否则继续判断如果一个周期内总的请求数小于circuitBreaker.requestVolumeThreshold的值,请求允许放行;否则继续判断如果一个周期内错误率小于circuitBreaker.errorThresholdPercentage的值,请求允许放行,否则打开熔断器进入第三步
- 调用allowSingleTest()判断是否允许单个请求通行:如果熔断器打开,且距离熔断器打开时间或上一次试探请求时间超过circuitBreaker.sleepWindowInMilliseconds值,熔断器进入半开状态允许放行一个试探请求,否则不允许放行
回退降级
降级,通常情况下是指业务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务;或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保证主体业务不受影响
Hystrix在以下几种情况下会走降级逻辑:
- 执行construct()或run()抛出异常
- 熔断器打开导致命令短路
- 命令的线程池或信号量容量超额,命令被拒绝
- 命令执行超时
降级回退方式
- Fail Fast快速失败:命令执行发生任何类型的故障直接抛出异常
- Fail Silent无声失败:在降级方法中通过返回null,空map,空list或其他类似的响应来完成
- Fallback:Static:在降级方法中返回静态默认值
- Fallback:Cache via Network:如果调用依赖服务失败,可以从缓存服务在查询旧数据版本
- Primary + Secondary with Fallback:主次模型,它的使用场景还是很多的,如当系统升级新功能时,如果新版本的功能出现问题,通过开关控制降级调用旧版本的功能
网友评论