针对于限流熔断组件Hystrix的回退降级实现方案和机制
依赖隔离
依赖隔离之线程&线程池
Hystrix使用独立的,每个依赖服务对应一个线程池的方式,来隔离这些依赖服务,这样,某个依赖服务的高延迟只会拖慢这个依赖服务对应的线程池。
高延迟请求的例子
当然,也可以不使用线程池来使你的系统免受依赖服务失效的影响,这需要你小心的设置网络连接/读取超时时间和重试配置,并保证这些配置能正确正常的运作,以使这些依赖服务在失效时,能快速返回错误。
Netflix在设计Hystrix时,使用线程/线程池来实现隔离,原因如下:
-
多数系统同时运行了(有时甚至多达数百个)不同的后端服务,这些服务由不同开发组开发。
-
每个服务都提供了自己的客户端库
-
客户端库经常会发生变动
-
客户端库可能会改变逻辑,加入新的网络请求
-
客户端库可能会包含重试逻辑,数据解析,缓存(本地缓存或分布式缓存),或者其他类似逻辑。
-
客户端库对于使用者来说,相当于『黑盒』,其实现细节,网络访问方式,默认配置等等均对使用者透明。
-
即使客户端库本身未发生变化,服务自身发生变化,也可能会影响其性能,从而导致客户端配置不再可靠。
-
中间依赖服务可能包含一些其依赖服务提供的客户端库,而这些库可能不受控且配置不合理
-
绝大多数网络访问都采用同步的方式进行
-
客户端代码可能也会有失效或者高延迟,而不仅仅是在网络访问时
面对失效时 Hystrix 包装的请求拓扑图
线程池的优势
将依赖服务请求通过使用不同的线程池隔离,其优势如下:
-
(拒绝请求)系统完全与依赖服务请求隔离开来,即使依赖服务对应线程池耗尽,也不会影响系统其它请求
-
(资源隔离)降低了系统接入新的依赖服务的风险,若新的依赖服务存在问题,也不会影响系统其它请求
-
当依赖服务失效后又恢复正常,其对应的线程池会被清理干净,相对于整个 Tomcat容器的线程池被占满需要耗费更长时间以恢复可用来说,此时系统可以快速恢复。
-
若依赖服务的配置有问题,线程池能迅速反映出来(通过失败次数的增加,高延迟,超时,拒绝访问等等),同时,你可以在不影响系统现有功能的情况下,处理这些问题(通常通过热配置等方式)。
-
若依赖服务的实现发生变更,性能有了很大的变化(这种情况时常发生),需要进行配置调整(例如增加/减小超时阈值,调整重试策略等)时,也可以从线程池的监控信息上迅速反映出来(失败次数增加,高延迟,超时,拒绝访问等等),同时,你可以在不影响其他依赖服务,系统请求和用户的情况下,处理这些问题
线程池处理能起到隔离的作用以外,还能通过这种内置的并发特性,在客户端库同步网络IO上,建立一个异步的Facade(类似 Netflix API建立在 Hystrix 命令上的 Reactive、全异步化的那一套 Java API)
简而言之,通过线程池提供的依赖服务隔离,可以使得我们能在不停止服务的情况下,更加优雅地应对客户端库和子系统性能上的变化。
线程池的弊端
Netflix在设计Hystrix 时,认为相对于其带来的好处,其带来的负载的一点点升高对系统的影响是微乎其微的。
线程池的开销
Hystrix的开发人员测试了在子线程中执行construct()或run()方法带来的额外时延,以及在父线程中整个请求的耗时,通过这个测试,你能直观了解 Hystrix 使用线程池带来的一点点系统负载的升高影响(线程,监控,日志,熔断器等)。
Netflix API 使用线程池来隔离依赖服务,每天可以处理超过 100 亿的 Hystrix 命令,每个 API 实例有超过 40 个线程池,每个线程池有 5 到 20 个工作线程(绝大部分设置为 10 个线程)。
下图展示了一个HystrixCommand以 60QPS 的速度,在一个 API 实例(每台服务器每秒运行的线程数峰值为 350)上被执行的耗时监控:
线程池开销
中位数显示二者(未使用线程池和使用线程池)没有差别。
-
90%的情况下,使用线程池有3ms的延迟
-
99% 的情况下,使用线程池有9ms的延迟,尽管如此,相对于请求的总时间(2ms28ms),延迟(0ms9ms)基本可以忽略不计
90%的情况下,这些延迟和在使用了熔断器之后更高的延迟,在绝大多数 Netflix 的需求来看,是微不足道的,更何况其能带来系统稳定性和鲁棒性上的巨大提升。
但绝大多数情况下,Netflix 更偏向于使用线程池来隔离依赖服务,因为其带来的额外开销可以接受,并且能支持包括超时在内的所有功能。
信号量
HystrixCommand和HystrixObservableCommand在两个地方支持使用信号量:
- 失败回退逻辑:当 Hystrix 需要执行失败回退逻辑时,其在调用线程(Tomcat 线程)中使用信号量
注意:如果依赖服务使用信号量来进行隔离,当依赖服务出现高延迟,其调用线程也会被阻塞,直到依赖服务的网络请求超时。