前言
最近公司压测,业务系统压测效果一直不是很好,细问之下才知道,在业务系统中会调用很多下游服务。为了控制调用下游服务的时间,防止太长的响应时间拖垮应用,他们针对每一个下游应用的调用处都使用hystrix进行线程池隔离来进行保护应用。而在使用时加入了execution.isolation.thread.timeoutInMilliseconds来控制每次请求的超时熔断降级,其主要目的是用于处理系统rpc调用中的慢调用,在rpc调用超时的时候会进入fallback逻辑。这种模式虽然在一定程度上能保护应用,而且能够达到超时快速失败的效果,但是在高并发场景下,稍微慢一些的慢调用多起来之后,整个线程池很快就会打满,对系统产生影响。虽然与本文的重点内容无关,但我还是想先来分析一下hystrix线程池隔离超时熔断降级的原理。
hystrix线程池隔离超时熔断降级的工作原理
我们首先来看下com.netflix.hystrix.AbstractCommand#executeCommandAndObserve方法中的一段代码:
可以看到在设置了线程处理超时时间之后除了执行正常的隔离逻辑外还会执行HystrixObservableTimeoutOperator中的处理逻辑。
HystrixObservableTimeoutOperator
HystrixObservableTimeoutOperator中的核心逻辑在call方法中,我们来看下代码:
在call方法中会创建一个TimerListener,在timerListener的tick方法中会先取消掉对当前请求响应的订阅,并执行fallback的逻辑,然后将listener添加到HystrixTimer中,我们看下HystrixTimer的相关代码:
可以看到在HystrixTimer中有一个线程池会在当前时间之后过timeout的时间之后执行listener的tick方法,而在该方法中会取消掉对当前请求响应的订阅,并执行fallback逻辑。
实际上整体并发能力上不去的原因不单纯是HystrixObservableTimeoutOperator引起的问题,而在使用hystrix的线程池隔离方式来处理这种rpc请求隔离时的通用问题。线程池隔离模式中用于处理io请求的线程池的数量是有限的,当服务内部的rpc请求稍有延迟时(比如偶发几条慢请求)线程池中的部分线程便会被占用从而导致处理其他请求的能力急速下降,最终导致能处理并发的能力下降很多。
实际情况下,线程池隔离并没有带来非常多的好处。首先就是过多的线程池会非常影响性能。考虑这样一个场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。个人理解,虽然hystrix对于隔离和熔断能起到很好的作用,而且能支持快速失败,但是对于下游服务不稳定的高并发的场景并不是十分友好。某些方面上来说,sentinel或许是一个更好的选择。
sentinel与hystrix的对比情况如下图:
更多关于sentinel与hystrix的对比官网比较详细,这里不再过多分析,需要了解更多的请参考sentinel官网:https://github.com/sentinel-group/sentinel-website/blob/master/blog/zh-cn/sentinel-vs-hystrix.md