声明:本文仅做个人的一次接口重构过程记录,期间参考了一些写的不错的博客,如果存在抄袭,请留言。
hystrix基本介绍
hystrix 是一个开源的容灾框架,目的是为了解决当依赖服务出现故障或者接口响应时间较慢,拖慢调用方业务系统,甚至引起系统雪崩的问题。
hystrix隔离分析
hystrix 有两种隔离方式,线程隔离,信号量的方式
线程隔离
把执行依赖代码的线程与请求线程(如:jetty线程)分离,请求线程可以自由控制离开的时间(异步过程)。
通过线程池大小可以控制并发量,当线程池饱和时可以提前拒绝服务,防止依赖问题扩散。
线上建议线程池不要设置过大,否则大量堵塞线程有可能会拖慢服务器。
线程隔离的优缺点
线程隔离的优点:
1、 使用线程可以完全隔离第三方代码,请求线程可以快速放回。
2、 当一个失败的依赖再次变成可用时,线程池将清理,并立即恢复可用,而不是一个长时间的恢复。
3、 可以完全模拟异步调用,方便异步编程。
线程隔离的缺点:
1、 线程池的主要缺点是它增加了cpu,因为每个命令的执行涉及到排队(默认使用SynchronousQueue避免排队),调度和上下文切换。
2、 对使用ThreadLocal等依赖线程状态的代码增加复杂性,需要手动传递和清理线程状态。
NOTE: Netflix公司内部认为线程隔离开销足够小,不会造成重大的成本或性能的影响。
Netflix 内部API 每天100亿的HystrixCommand依赖请求使用线程隔,每个应用大约40多个线程池,每个线程池大约5-20个线程。
信号隔离
- 信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请),
如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销.
信号量的大小可以动态调整, 线程池大小不可以.
线程隔离与信号隔离区别如下图:
上图可以看出,左边是线程池隔离,由主线程调用依赖后,依赖服务的执行是在新的HystrixCommand线程池中执行的,右边图中展示,信号量的方式,代码的执行还是在主线程中执行的。
实际hystrix应用场景介绍
现在大部分应用系统都是采用的分布式框架,系统中会有很多的依赖,HTTP、Dubbo、hession等等,在高并发场景下,这些依赖的服务稳定性对系统的影响很大,当依赖阻塞时,大多数服务器的线程池就出现阻塞(BLOCK),拖慢整条链路业务的执行,影响整个线上服务的稳定性,如果没有对依赖的服务做隔离就会出现这样的场景:
某次大直播中(我是做直播的)通过监控发现某个接口RT猛增,通过pinpoint看到是依赖的一个接口响应时间比平时慢了很多,分析问题,该接口RT增加可能是因为依赖的下层接口响应时间变慢,大量请求都阻塞在接口调用处,还有个问题,该接口的dubbo超时时间设置的1秒,1秒,1秒。。。。那这个接口影响1.2秒一点不奇怪,依赖接口都执行了1秒才会返回超时,问题分析到,接下来就是进行优化了。
优化过程
由于代码是别的同学写的,接口优化又迫在眉睫,上面只是举例实际还有别的问题,只是说明一下应用场景,根据实际情况我做了一个代码优化的方案
阶段一:尽可能少动,因为这个接口属于核心接口一旦出现问题可能影响较大(灰度发布的事,这里先不提,以后有时间会专门做一项关于灰度上线,灰度测试等,相关的文档,背锅是不可能背锅的),梳理出接口依赖的第三方服务,根据业务将所有依赖进行了划分,分出三个线程池,第一个核心线程池,该线程池用来处理观看直播所需的一些必须信息,比如直播流地址,主播信息等等,
第二个线程池,存放一些内部依赖的dubbo服务调用,第三个线程池,存放一些调用微博,淘宝等一些http服务
为什么这么优化
可能很多人看到上边会有很多疑问,hystrix不是用来做依赖服务的熔断隔离吗?你这把所有的依赖调用都放到一个线程池子意义何在?这里有必要说一些,上边说过了不了解代码阶段一优化不想改动太多逻辑,单纯的对接口做隔离,保证核心线程容量,非核心线程直接熔断或者干脆不调用对观看直播不会产生影响,这也是对于突发大流量时,业务上的优化,非核心功能可以降级,只保证核心功能(淘宝双十一也是一样)
还有个问题,既然核心线程的接口优先保证容量,那为什么还要单独交给新的线程池去处理,为什么不在主线程中执行呢?主线程在调用核心线程中的逻辑时一样是阻塞在那里,还会增加线程池之间的上下文切换时间,这里也不见得没有好处,我把核心代码隔离出来就可以精准的评估出我接口单台服务器能抗的最大并发数,这样在做容量保障时,就可以根据多少qps评估出要扩多少台机器。其实就是两者之间做一个取舍。
阶段二优化
上边说了第一阶段的优化过程,通过第一阶段优化经历,基本了解代码中有哪些逻辑了,第二阶段准备做数据聚合,什么意思呢,上边说过咱们这个接口调用了太多了的服务,通过阶段一的优化我们已经将该异步的接口做了异步,既然我们做了异步降级,那么也就是说非核心接口以为的数据我们可以尝试提前将这部分数据查询出来拼接好放到某一个容器里边,我们需要的时候之间从该容器中获取,获取不到就直接返回null结果(类似阶段一接口熔断了),由于还没有进行到阶段二这里先这这么多,给大家提供一个优化思路,仅供参考