一、概述
在一个分布式系统中,每个服务都可能会调用其它的服务器,服务之间是相互调用相互依赖。假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务。这就是构成所谓“扇出”。
如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,进而引起系统崩溃,即"雪崩效应"。
对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
为应对微服务器系统出现“雪崩”事故,使用微服务Hystrix能避免级联故障,以提高分布式系统的弹性。
二、什么是Hystix
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,可以保证一个服务出现故障时,不会导致整个系统出现雪崩效应,以提高分布式系统弹性;
作为“断路器”,在一个服务出现故障时,可以通过短路器监控,返回一个可以处理的响应结果,保证服务调用线程不会长时间被占用,避免故障蔓延。
三、Hystrix作用
- 对调用其他服务造成的异常和超时提供保护和控制。
- 在复杂的分布式系统中防止级联失败。
- 快速失败和迅速恢复。
- 当必要时fallback和优雅的降级。
- 提供实施监控,警告和可选择的控制。
四、Hystrix设计原则
- 防止单个依赖耗尽容器内所有用户线程
- 降低系统负载,对无法及时处理的请求快速失败(fail fast)而不是排队
- 提供失败回退,以在必要时让失效对用户透明化
- 使用隔离机制降低依赖服务对整个系统的影响
- 针对系统服务的度量、监控和报警,提供优化以满足近实时性的要求
- 在 Hystrix 绝大部分需要动态调整配置并快速部署到所有应用方面,提供优化以满足快速恢复的要求
- 能保护应用不受依赖服务的整个执行过程中失败的影响,而不仅仅是网络请求
五、Hystrix实现原理
5.1 隔离
Hystrix的隔离模式有两种分别是:信号量模式和线程池模式。
信号量不支持超时,当被调服务发生问题时,有少部分用户会长时间无法得到响应。另外使用线程池模式无法获取传递Header。
- 信号量和线程池有如下区别:
线程切换 | 支持异步 | 支持超时 | 支持熔断 | 开销大小 | 支持限量 | |
信号量 | 否 | 否 | 否 | 是 | 小 | 是 |
线程池 | 是 | 是 | 是 | 是 | 大 | 是 |
- 信号量隔离
信号量的使用如图(信号量示意图),当n个并发请求去调用一个目标服务接口时,都要获取一个信号量才能真正去调用目标服务接口,但信号量有限,默认是10个,可以使用maxConcurrentRequests参数配置,如果并发请求数多于信号量个数,就有线程需要进入队列排队,但是排队队列也有上限,默认是5,如果排队队列也满,则必定有请求线程会fallback流程,从而达到限流和防止雪崩的目的。
信号量模式从始至终都只有请求线程自身,是同步调用模式,不支持超时调用,不支持直接熔断,由于没有线程的切换,开销非常小。 - 线程池隔离
线程池的使用示意图如下图所示,当n个请求线程并发对某个接口请求调用时,会先从hystrix管理的线程池里面获得一个线程,然后将参数传递给这个线程去执行真正调用。线程池的大小有限,默认是10个线程,可以使用maxConcurrentRequests参数配置,如果并发请求数多于线程池线程个数,就有线程需要进入队列排队,但排队队列也有上限,默认是 5,如果排队队列也满,则必定有请求线程会走fallback流程。
线程池模式可以支持异步调用,支持超时调用,支持直接熔断,存在线程切换,开销大 - 线程池隔离优点:
- 使用线程池隔离可以完全隔离第三方应用,请求线程可以快速放回。
- 请求线程可以继续接受新的请求,如果出现问题线程池隔离是独立的不会影响其他应用
- 当失败的应用再次变得可用时,线程池将清理并可立即恢复,而不需要一个长时间的恢复。
- 独立的线程池提高了并发性。
- 尽管线程池隔离是由一个单独的线程提供,客户端代码(异常方法里面的请求)应该也 有超时机制,不能让响应的线程无限期等待,应该适时去中断它,阻止 Hystrix 线程池的饱和。
线程池隔离缺点:
线程池隔离的主要缺点是它们增加计算开销(CPU)。每个命令的执行涉及到排队、调度和上 下文切换都是在一个单独的线程上运行的。 - 应用场景:
线程池隔离:第三方应用或者接口和并发量大。
信号量隔离:内部应用或者中间件(redis)和 并发需求不大
5.2 熔断
熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。
服务熔断指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。很多时候刚开始可能只是系统出现了局部的、小规模的故障,然而由于种种原因,故障影响的范围越来越大,最终导致了全局性的后果。
- 基于自反馈调节熔断状态的算法原理
熔断器来源电子工程中的保险丝,一般在所有的家电系统连接外部供电的线路中间都会加一个保险丝,当外部电压过高,达到保险丝的熔点时候,保险丝就会被熔断,从而可以切断家电系统与外部电路的联通,进而保障家电系统不会因为电压过高而损坏。
Hystrix提供的熔断器就有类似功能,当在一定时间段内服务调用方调用服务提供方的服务的次数达到设定的阈值,并且出错的次数也达到设置的出错阈值,就会进行服务降级,让服务调用方之间执行本地设置的降级策略,而不再发起远程调用。但是Hystrix提供的熔断器具有自我反馈,自我恢复的功能,Hystrix会根据调用接口的情况,让熔断器在closed,open,half-open三种状态之间自动切换。
open状态:说明打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。
closed状态:说明关闭了熔断,这时候服务调用方直接发起远程调用。
half-open状态:则是一个中间状态,当熔断器处于这种状态时候,直接发起远程调用。
- 三种状态的转换
- closed->open:正常情况下熔断器为closed状态,当访问同一个接口次数超过设定阈值并且错误比例超过设置错误阈值时候,就会打开熔断机制,这时候熔断器状态从closed->open。
- open->half-open:当服务接口对应的熔断器状态为open状态时候,所有服务调用方调用该服务方法时候都是执行本地降级方法,那么什么时候才会恢复到远程调用那?Hystrix提供了一种测试策略,也就是设置了一个时间窗口,从熔断器状态变为open状态开始的一个时间窗口内,调用该服务接口时候都委托服务降级方法进行执行。如果时间超过了时间窗口,则把熔断状态从open->half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用还是失败,则重新设置熔断器状态为open状态,从新记录时间窗口开始时间。
- half-open->closed: 当熔断器状态为half-open,这时候服务调用方调用服务接口时候,就可以发起远程调用而不再使用本地降级接口,如果发起远程调用成功,则重新设置熔断器状态为closed状态。
系统设计时候要使用一定的熔断策略,来保证当服务提供方服务出现异常访问时,能及时截断访问里量,减少不必要的宕机达到服务的高可用,Hystrix作为熔断器组件使用范围还是很广泛。
5.3 降级
服务降级是指当服务器压力剧增的情况下,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心接口和服务请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样虽然提供的是一个有损的服务,但释放了服务器资源以保证核心业务正常运作或高效运作,保证了整个系统的稳定性和可用性。其实尽可能的把系统资源让给优先级高的服务。
资源有限,而请求是无限的。如果在并发高峰期,不做服务降级处理,一方面肯定会影响整体服务的性能,严重的话可能会导致宕机某些重要的服务不可用。所以,一般在高峰期,为了保证核心功能服务的可用性,都要对某些服务降级处理。比如当双 11 活动时,把交易无关的服务统统降级,如查看快递单,查看历史订单等等。
- 服务降级主要用于什么场景呢
当整个微服务架构整体的负载超出了预设的上限阈值或即将到来的流量预计将会超过预设的阈值时,为了保证重要或基本的服务能正常运行,可以将一些不重要或不紧急的服务或任务进行服务的延迟使用 或暂停使用。
降级的方式可以根据业务来,可以延迟服务,比如延迟给用户增加积分,只是放到一个缓存中,等服务平稳之后再执行;或者在粒度范围内关闭服务。 - 实现服务降级需要考虑几个问题
- 那些服务是核心服务,哪些服务是非核心服务
- 那些服务可以支持降级,那些服务不能支持降级,降级策略是什么
- 除服务降级之外是否存在更复杂的业务放通场景,策略是什么?
- 降级分类
- 超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况;
- 失败次数降级:主要是一些不稳定的 api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况;
- 故障降级:如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)
- 限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:直接转到Tip提示。
服务降级就是指服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示 fallback。
5.4 缓存
Hystrix提供的请求缓存是一个本地缓存,在分布式环境下不适用,并且实际开发中,也不会使用hystrix的请求缓存功能,因为这个功能太多余了,Hystrix的请求缓存只能够在同一个request请求作用域下才生效,下一次的request请求,会将上一次request的请求缓存给清空。
- 请求缓存介绍hystrix请求缓存是指:在同一个request请求之下,多次调用其他微服务的时候,会将第一次调用结果缓存起来,然后之后每次都是从缓存里面获取数据结果。hystrix缓存的是service层方法的返回结果,当在controller层中同一个HTTP请求里面,多次调用service层方法的时候,此时会从缓存中获取数据。什么叫做同一次HTTP请求呢???
- 一个用户访问地址,此时请求进入到controller层,然后我们在controller层方法中,调用了两次service层的方法,那么此时第二次调用service层的方法返回值,将从hystrix缓存里面获取。
- 就只在一次HTTP请求里面,如果用户访问了两次,那么此时就不是同一次HTTP请求了,第一次的缓存数据将会被第二次的缓存数据覆盖掉。
- 开启hystrix功能
在启动类上面,需要使用@EnableCircuitBreaker和@EnableHystrix注解,开启hystrix熔断器功能,否则hystrix将不生效。两个注解的作用是相同的,@EnableHystrix注解就已经包含了@EnableCircuitBreaker注解,但是官方使用的是@EnableCircuitBreaker注解,所以我们也就按照官方的用法,直接使用@EnableCircuitBreaker注解开启功能即可。 - 缓存删除
hystrix也提供了删除缓存的注解@CacheRemove,当进行一些新增、删除、更新操作的时候,此时缓存中的数据就可能不是最新的了,所以这个时候就需要将缓存中的数据删除。使用@CacheRemove注解,同时指定commandKey属性,这就是告诉hystrix需要删除哪个命令下的所有缓存。
5.5 服务合并监控
通常微服务架构中的依赖通过远程调用实现,而远程调用中最常见的问题就是通信消耗与连接数占用。在高并发的情况之下,因通信次数的增加,总的通信时间消耗将会变的不那么理想。同时,因为对依赖服务的线程池资源有限,将出现排队等待与响应延迟的情况。为了优化这两个问题,Hystrix提供了HystrixCollapser来实现请求的合并,以减少通信消耗和线程数的占用。
HystrixCollapser实现了在HystrixCommand之前放置一个合并处理器,它将处于一个很短时间窗(默认10毫秒)内对同一依赖服务的多个请求进行整合并以批量方式发起请求的功能(服务提供方也需要提供相应的批量实现接口)。通过HystrixCollapser的封装,开发者不需要去关注线程合并的细节过程,只需要关注批量化服务和处理。下面我们从HystrixCollapser的使用实例,对其合并请求的过程一探究竟。
五、Hystrix总结
Hystrix的设计目的就是为了提高系统的弹性和高可用。弹性主要体现在Hystrix的熔断、降级、快速恢复和实时监控的功能,当设置了Hystrix的熔断和降级,由于某种原因导致服务熔断后,时间段内之间返回fallback,时间段之后Hystrix成半打开状态,检测服务是否可用,如果可用,熔断关闭,接收请求,否则,时间段内继续保持熔断开启。
虽然现在Hystrix官网宣布进入维护模式,停更了,也是很老的微服务技术,现有很多的技术取而代之,但是,Hystrix的思想一直在,现在很多的技术仍然是借鉴的Hystrix理论知识,学习Hystrix,能让我们更好的学习其他的微服务知识。