Spring Cloud 系列之 Netflix Hystrix 服务容错(二)
本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Netflix Hystrix 服务容错(一)
本篇文章讲解 Hystrix 服务隔离中的线程池隔离与信号量隔离。
1|0服务隔离
点击链接观看:服务隔离视频(获取更多请关注公众号「哈喽沃德先生」)
1|1线程池隔离
没有线程池隔离的项目所有接口都运行在一个 ThreadPool
中,当某一个接口压力过大或者出现故障时,会导致资源耗尽从而影响到其他接口的调用而引发服务雪崩效应。我们在模拟高并发场景时也演示了该效果。
通过每次都开启一个单独线程运行。它的隔离是通过线程池,即每个隔离粒度都是个线程池,互相不干扰。线程池隔离方式,等于多了一层的保护措施,可以通过 hytrix 直接设置超时,超时后直接返回。
隔离前
隔离后
优点:
- 使用线程池隔离可以安全隔离依赖的服务(例如图中 A、C、D 服务),减少所依赖服务发生故障时的影响面。比如 A 服务发生异常,导致请求大量超时,对应的线程池被打满,这时并不影响 C、D 服务的调用。
- 当失败的服务再次变得可用时,线程池将清理并立即恢复,而不需要一个长时间的恢复。
- 独立的线程池提高了并发性。
缺点:
- 请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的 CPU 开销。
- 因为涉及到跨线程,那么就存在 ThreadLocal 数据的传递问题,比如在主线程初始化的 ThreadLocal 变量,在线程池线程中无法获取。
点击链接观看:线程池隔离视频(获取更多请关注公众号「哈喽沃德先生」)
添加依赖
服务消费者 pom.xml 添加 hystrix 依赖。
业务层
服务消费者业务层代码添加线程隔离规则。
@HystrixCommand
注解各项参数说明如下:
启动类
服务消费者启动类开启熔断器注解。
测试
服务提供者接口添加 Thread.sleep(2000)
,模拟服务处理时长。
JMeter 开启 20 线程循环 50 次访问:http://localhost:9090/order/1/product/list
浏览器访问:http://localhost:9090/order/1/product 控制台打印结果如下:
1|2信号量隔离
每次调用线程,当前请求通过计数信号量进行限制,当信号量大于了最大请求数 maxConcurrentRequests
时,进行限制,调用 fallback
接口快速返回。信号量的调用是同步的,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)。
添加依赖
服务消费者 pom.xml 添加 hystrix 依赖。
业务层
服务消费者业务层代码添加信号量隔离规则。
@HystrixCommand
注解各项参数说明如下:
启动类
服务消费者启动类开启熔断器注解。
测试
服务提供者接口添加 Thread.sleep(2000)
,模拟服务处理时长。
服务消费者信号量最大并发设置为 6
,方便模拟高并发。
JMeter 开启 20 线程循环 50 次访问:http://localhost:9090/order/1/product/list
浏览器也访问:http://localhost:9090/order/1/product/list 结果如下:
1|3线程池隔离 vs 信号量隔离
隔离方式 | 是否支持超时 | 是否支持熔断 | 隔离原理 | 是否是异步调用 | 资源消耗 |
---|---|---|---|---|---|
线程池隔离 | 支持 | 支持 | 每个服务单独用线程池 | 支持同步或异步 | 大 |
信号量隔离 | 不支持 | 支持 | 通过信号量的计数器 | 同步调用,不支持异步 | 小 |
线程池隔离
-
请求线程和调用 Provider 线程不是同一条线程;
-
支持超时,可直接返回;
-
支持熔断,当线程池到达最大线程数后,再请求会触发
fallback
接口进行熔断; -
隔离原理:每个服务单独用线程池;
-
支持同步和异步两种方式;
-
资源消耗大,大量线程的上下文切换、排队、调度等,容易造成机器负载高;
-
无法传递 Http Header。
信号量隔离
- 请求线程和调用 Provider 线程是同一条线程;
- 不支持超时;
- 支持熔断,当信号量达到
maxConcurrentRequests
后。再请求会触发fallback
接口进行熔断; - 隔离原理:通过信号量的计数器;
- 同步调用,不支持异步;
- 资源消耗小,只是个计数器;
- 可以传递 Http Header。
总结
- 请求并发大,耗时长(计算大,或操作关系型数据库),采用线程隔离策略。这样可以保证大量的线程可用,不会由于服务原因一直处于阻塞或等待状态,快速失败返回。还有就是对依赖服务的网络请求的调用和访问,会涉及 timeout 这种问题的都使用线程池隔离。
- 请求并发大,耗时短(计算小,或操作缓存),采用信号量隔离策略,因为这类服务的返回通常会非常的快,不会占用线程太长时间,而且也减少了线程切换的开销,提高了缓存服务的效率。还有就是适合访问不是对外部依赖的访问,而是对内部的一些比较复杂的业务逻辑的访问,像这种访问系统内部的代码,不涉及任何的网络请求,做信号量的普通限流就可以了,因为不需要去捕获 timeout 类似的问题,并发量突然太高,稍微耗时一些导致很多线程卡在这里,所以进行一个基本的资源隔离和访问,避免内部复杂的低效率的代码,导致大量的线程被夯住。
下一篇我们讲解 Hystrix 的服务熔断和服务降级以及基于 Feign 的服务熔断处理,记得关注噢~