Hystrix依赖隔离之线程池隔离

为什么要做线程隔离
比如我们现在有3个业务调用分别是查询订单、查询商品、查询用户 , 且这三个业务请求都是依赖第三方服务-订单服务、商品服务、用户服务 。三个服务均是通过RPC调用 。当查询订单服务 , 假如线程阻塞了 , 这个时候后续有大量的查询订单请求过来 , 那么容器中的线程数量则会持续增加直致CPU资源耗尽到100% , 整个服务对外不可用 , 集群环境下就是雪崩 。
Hystrix是如何通过线程池实现线程隔离的
Hystrix通过命令模式 , 将每个类型的业务请求封装成对应的命令请求 , 比如查询订单->订单Command , 查询商品->商品Command , 查询用户->用户Command 。每个类型的Command对应一个线程池 。创建好的线程池是被放入到ConcurrentHashMap中
1.依赖隔离概述
依赖隔离是Hystrix的核心目的 。依赖隔离其实就是资源隔离 , 把对依赖使用的资源隔离起来 , 统一控制和调度 。那为什么需要把资源隔离起来呢?主要有以下几点:
1.合理分配资源 , 把给资源分配的控制权交给用户 , 某一个依赖的故障不会影响到其他的依赖调用 , 访问资源也不受影响 。
2.可以方便的指定调用策略 , 比如超时异常 , 熔断处理 。
3.对依赖限制资源也是对下游依赖起到一个保护作用 , 避免大量的并发请求在依赖服务有问题的时候造成依赖服务瘫痪或者更糟的雪崩效应 。
4.对依赖调用进行封装有利于对调用的监控和分析 , 类似于hystrix-dashboard的使用 。
Hystrix提供了两种依赖隔离方式:线程池隔离 和 信号量隔离 。如下图 , 线程池隔离 , Hystrix可以为每一个依赖建立一个线程池 , 使之和其他依赖的使用资源隔离 , 同时限制他们的并发访问和阻塞扩张 。每个依赖可以根据权重分配资源(这里主要是线程) , 每一部分的依赖出现了问题 , 也不会影响其他依赖的使用资源 。

Hystrix依赖隔离之线程池隔离

文章插图
 
2.线程池隔离如果简单的使用异步线程来实现依赖调用会有如下问题:1、线程的创建和销毁;2、线程上下文空间的切换 , 用户态和内核态的切换带来的性能损耗 。
【Hystrix依赖隔离之线程池隔离】使用线程池的方式可以解决第一种问题 , 但是第二个问题计算开销是不能避免的 。Netflix在使用过程中详细评估了使用异步线程和同步线程带来的性能差异 , 结果表明在99%的情况下 , 异步线程带来的几毫秒延迟的完全可以接受的 。
Hystrix依赖隔离之线程池隔离

文章插图
 
3.线程池隔离的优缺点优点:
  • 一个依赖可以给予一个线程池 , 这个依赖的异常不会影响其他的依赖 。
  • 使用线程可以完全隔离第三方代码,请求线程可以快速返回 。
  • 当一个失败的依赖再次变成可用时 , 线程池将清理 , 并立即恢复可用 , 而不是一个长时间的恢复 。
  • 可以完全模拟异步调用 , 方便异步编程 。
  • 使用线程池 , 可以有效的进行实时监控、统计和封装 。
缺点:
  • 使用线程池的缺点主要是增加了计算的开销 。每一个依赖调用都会涉及到队列 , 调度 , 上下文切换 , 而这些操作都有可能在不同的线程中执行 。
4.Command Name&Command GroupHystrix使用Command模式对依赖调用进行封装 。当我们写一个调用继承HystrixCommand的时候 , 可以指定一个名称Command Name 。如果不指定Hystrix将会使用getClass().getSimpleName()来默认获取 。如果要指定 , 可以使用如下代码 , 使用HystrixCommandKey.Factory帮助类在构造函数中指定 。
public HelloWorldCommand(String name){//定义命令组 和 方法调用超时时间super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("HelloWorldGroup")).andCommandKey(HystrixCommandKey.Factory.asKey("HelloWorldCommand")));this.name = name;}而Command Group可以把一组Command归为一组 。如上例代码 , 可以使用
HystrixCommandGroupKey.Factory.asKey来指定Command Group 。一般情况下 , 逻辑上是同一类型的会放在同一个Command Group中 。比如 , 获取用户相关信息的依赖可以放在一起 。


推荐阅读