<p>摘要: 原创出处 <a href="http://www.iocoder.cn/Hystrix/command-execute-second-isolation-strategy/">http://www.iocoder.cn/Hystrix/command-execute-second-isolation-strategy/</a> 「芋道源码」欢迎转载,保留摘要,谢谢!</p>
本文主要基于 Hystrix 1.5.X 版本
- 1. 概述
- 2. HystrixThreadPoolProperties
- 3. HystrixThreadPoolKey
- 4. HystrixConcurrencyStrategy
- 5. HystrixThreadPool
- 6. HystrixScheduler
- 666. 彩蛋
1. 概述
本文主要分享 Hystrix 命令执行(二)之执行隔离策略。
建议 :对 RxJava 已经有一定的了解的基础上阅读本文。
Hystrix 提供两种执行隔离策略( ExecutionIsolationStrategy ) :
SEMAPHORE
:信号量,命令在调用线程执行。在《Hystrix 源码解析 —— 命令执行(一)之正常执行逻辑》「3. TryableSemaphore」 已经详细解析。THREAD
:线程池,命令在线程池执行。在《Hystrix 源码解析 —— 命令执行(一)之正常执行逻辑》「5. #executeCommandWithSpecifiedIsolation(…)」 的#executeCommandWithSpecifiedIsolation(...)
方法中,调用Observable#subscribeOn(Scheduler)
方法,指定在 RxJava Scheduler 执行。- 如果你暂时不了解 Scheduler ,可以阅读 《RxJava 源码解析 —— Scheduler》 。
- 如果你暂时不了解
Observable#subscribeOn(Scheduler)
,可以阅读 《RxJava 源码解析 —— Observable#subscribeOn(Scheduler)》 。
两种方式的优缺点比较,推荐阅读 《【翻译】Hystrix文档-实现原理》「依赖隔离」。
推荐 Spring Cloud 书籍:
- 请支持正版。下载盗版,等于主动编写低级 BUG 。
- 程序猿DD —— 《Spring Cloud微服务实战》
- 周立 —— 《Spring Cloud与Docker微服务架构实战》
- 两书齐买,京东包邮。
2. HystrixThreadPoolProperties
com.netflix.hystrix.HystrixThreadPoolProperties
,Hystrix 线程池属性配置抽象类,点击 链接 查看,已添加中文注释说明。
com.netflix.hystrix.strategy.properties.HystrixPropertiesThreadPoolDefault
,Hystrix 线程池配置实现类,点击 链接 查看。实际上没什么内容,官方如是说 :
Default implementation of {@link HystrixThreadPoolProperties} using Archaius (https://github.com/Netflix/archaius)
3. HystrixThreadPoolKey
com.netflix.hystrix.HystrixThreadPoolKey
,Hystrix 线程池标识接口。
FROM HystrixThreadPoolKey 接口注释
A key to represent a {@link HystrixThreadPool} for monitoring, metrics publishing, caching and other such uses.
This interface is intended to work natively with Enums so that implementing code can be an enum that implements this interface.
- 直白的说 ,希望通过相同的
name
( 标识 ) 获得同 HystrixThreadPoolKey 对象。通过在内部维持一个name
与 HystrixThreadPoolKey 对象的映射,以达到枚举的效果。
HystrixThreadPoolKey 代码如下 :
|
- HystrixThreadPoolKey 实现
com.netflix.hystrix.HystrixKey
接口,点击 链接 查看。该接口定义的#name()
方法,即是上文我们所说的标识( Key )。 intern
属性,name
与 HystrixThreadPoolKey 对象的映射,以达到枚举的效果。com.netflix.hystrix.util.InternMap
,点击 链接 查看带中文注释的代码。
#asKey(name)
方法,从intern
获得 HystrixThreadPoolKey 对象。#getThreadPoolCount()
方法,获得 HystrixThreadPoolKey 数量。
在 AbstractCommand 构造方法里,初始化命令的 threadPoolKey
属性,代码如下 :
|
调用
#initThreadPoolKey(...)
方法,创建最终的threadPoolKey
属性。代码如下 :private static HystrixThreadPoolKey initThreadPoolKey(HystrixThreadPoolKey threadPoolKey, HystrixCommandGroupKey groupKey, String threadPoolKeyOverride) {if (threadPoolKeyOverride == null) {// we don't have a property overriding the value so use either HystrixThreadPoolKey or HystrixCommandGroupif (threadPoolKey == null) {/* use HystrixCommandGroup if HystrixThreadPoolKey is null */return HystrixThreadPoolKey.Factory.asKey(groupKey.name());} else {return threadPoolKey;}} else { // threadPoolKeyOverride 可覆盖属性// we have a property defining the thread-pool so use it insteadreturn HystrixThreadPoolKey.Factory.asKey(threadPoolKeyOverride);}}- 优先级 :
threadPoolKeyOverride
>threadPoolKey
>groupKey
- 优先级 :
4. HystrixConcurrencyStrategy
com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy
,Hystrix 并发策略抽象类。
HystrixConcurrencyStrategy#getThreadPool(...)
方法,代码如下 :
|
- 第 2 行 :调用
#getThreadFactory(...)
方法,获得 ThreadFactory 。点击 链接 查看方法代码。PlatformSpecific#getAppEngineThreadFactory()
方法,无需细看,适用于 Google App Engine 场景。
- 第 4 至 7 行 :「2. HystrixThreadPoolProperties」 有详细解析。
- 第 9 行 :调用
#getBlockingQueue()
方法,获得线程池的阻塞队列。点击 链接 查看方法代码。
- 当
maxQueueSize <= 0
时( 默认值 :-1
) 时,使用 SynchronousQueue 。超过线程池的maximumPoolSize
时,提交任务被拒绝。
- 当
SynchronousQueue > 0
时,使用 LinkedBlockingQueue 。超过线程池的maximumPoolSize
时,任务被拒绝。超过线程池的maximumPoolSize
+ 线程池队列的maxQueueSize
时,提交任务被阻塞等待。
- 第 11 至 23 行 :创建 ThreadPoolExecutor 。看起来代码比较多,根据
allowMaximumSizeToDivergeFromCoreSize
的情况,计算线程池的maximumPoolSize
属性。计算的方式和HystrixThreadPoolProperties#actualMaximumSize()
方法是一致的。
com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault
,Hystrix 并发策略实现类。代码如下( 基本没做啥 ) :
|
在 AbstractCommand 构造方法里,初始化命令的 threadPoolKey
属性,代码如下 :
|
- HystrixPlugins ,Hystrix 插件体系,https://github.com/Netflix/Hystrix/wiki/Plugins 有详细解析。
- 调用
HystrixPlugins#getConcurrencyStrategy()
获得 HystrixConcurrencyStrategy 对象。默认情况下,使用 HystrixConcurrencyStrategyDefault 。当然你也可以参考 Hystrix 插件体系,实现自定义的 HystrixConcurrencyStrategy 实现,以达到覆写#getThreadPool()
,#getBlockingQueue()
等方法。点击 链接 查看该方法代码。
5. HystrixThreadPool
com.netflix.hystrix.HystrixThreadPool
,Hystrix 线程池接口。当 Hystrix 命令使用 THREAD
执行隔离策略时,HystrixCommand#run()
方法在线程池执行。点击 链接 查看。HystrixThreadPool 定义接口如下 :
#getExecutor()
:获得 ExecutorService 。#getScheduler()
/#getScheduler(Func0<Boolean>)
:获得 RxJava Scheduler 。#isQueueSpaceAvailable()
:线程池队列是否有空余。#markThreadExecution()
/#markThreadCompletion()
/#markThreadRejection()
:TODO 【2002】【metrics】
5.1 HystrixThreadPoolDefault
com.netflix.hystrix.HystrixThreadPool.HystrixThreadPoolDefault
,Hystrix 线程池实现类。
构造方法,代码如下 :
|
- 第 9 行 :初始化 HystrixThreadPoolProperties 。
- 第 11 行 :初始化 HystrixConcurrencyStrategy 。
- 第 13 行 :初始化
queueSize
。 - 第 16 至 18 行 :TODO 【2002】【metrics】
- 第 17 行 :调用
HystrixConcurrencyStrategy#getThreadPool(...)
方法,初始化 ThreadPoolExecutor 。
- 第 17 行 :调用
- 第 21 行 :获得 ThreadPoolExecutor 。
- 第 22 行 :获得 ThreadPoolExecutor 的队列。
- 第 26 行 :TODO 【2002】【metrics】
#getExecutor()
方法,代码如下 :
|
- 调用
#touchConfig()
方法,动态调整threadPool
的coreSize
/maximumSize
/keepAliveTime
参数。点击 链接 查看该方法。
#getScheduler()
/ #getScheduler(Func0<Boolean>)
方法,代码如下 :
|
- HystrixContextScheduler 和
shouldInterruptThread
都在 「6. HystrixContextScheduler」 详细解析。
#isQueueSpaceAvailable()
方法,代码如下 :
|
- 由于线程池的队列大小不能动态调整,该方法的实现通过
HystrixThreadPoolProperties.queueSizeRejectionThreshold
属性控制。 - 注意
queueSize
属性,决定了线程池的队列类型。queueSize <= 0
时,#isQueueSpaceAvailable()
都返回true
的原因是,线程池使用 SynchronousQueue 作为队列,不支持新任务排队,任务超过线程池的maximumPoolSize
时,新任务被拒绝。queueSize > 0
时,#isQueueSpaceAvailable()
根据情况true
/false
的原因是,线程池使用 LinkedBlockingQueue 作为队列,支持一定数量的阻塞排队,但是这个数量无法调整。通过#isQueueSpaceAvailable()
方法的判断,动态调整。另外,初始配置的queueSize
要相对大,否则即使queueSizeRejectionThreshold
配置的大于queueSize
,实际提交任务到线程池,也会被拒绝。
5.2 Factory
com.netflix.hystrix.HystrixThreadPool.Factory
,HystrixThreadPool 工厂类,不仅限于 HystrixThreadPool 的创建,也提供了 HystrixThreadPool 的管理( HystrixThreadPool 的容器 )。
threadPools
属性,维护创建的 HystrixThreadPool 对应的映射,代码如下 :
|
- Key 为
HystrixThreadPoolKey#name()
,每个 HystrixThreadPoolKey 对应一个 HystrixThreadPool 对象。
#getInstance(...)
方法,获得 HystrixThreadPool 对象,代码如下 :
|
- 根据
threadPoolKey
先从threadPool
获取已创建的 HystrixThreadPool ;获取不到,创建对应的 HystrixThreadPool 返回,并添加到threadPool
。
#shutdown()
/ #shutdown(timeout, unit)
方法,比较易懂,点击 链接 查看。
5.3 初始化
在 AbstractCommand 构造方法里,初始化命令的 threadPool
属性,代码如下 :
|
- 调用
#initThreadPool(...)
方法,获得 HystrixThreadPool ,点击 链接 查看。
6. HystrixScheduler
Hystrix 实现了自定义的 RxJava Scheduler ,整体类图如下 :
- HystrixContextScheduler ( 实现 RxJava Scheduler 抽象类 ),内嵌类型为 ThreadPoolScheduler ( 实现 RxJava Scheduler 抽象类 )的
actualScheduler
属性。 - HystrixContextWorker ( 实现 RxJava Worker 抽象类 ),内嵌类型为 ThreadPoolWorker ( 实现 RxJava Worker 抽象类 )的
worker
属性。
6.1 HystrixContextScheduler
构造方法,代码如下 :
|
actualScheduler
属性,类型为 ThreadPoolScheduler 。
#createWorker()
方法,代码如下 :
|
- 使用
actualScheduler
创建 ThreadPoolWorker ,传参给 HystrixContextSchedulerWorker 。
6.2 HystrixContextSchedulerWorker
构造方法,代码如下 :
|
worker
属性,类型为 ThreadPoolWorker 。
#schedule(Action0)
方法,代码如下 :
|
- 调用
ThreadPool#isQueueSpaceAvailable()
方法,判断线程池队列是否有空余。这个就是 HystrixContextScheduler 的实际用途。
#unsubscribe()
/ #isUnsubscribed()
方法,使用 worker
判断,点击 链接查看。
6.3 ThreadPoolScheduler
ThreadPoolScheduler 比较简单,点击 链接 查看。
6.4 ThreadPoolWorker
构造方法,代码如下 :
|
subscription
属性,订阅信息。
#schedule(Action0)
方法,代码如下 :
|
- 第 4 至 7 行 :未订阅,返回。
- 第 11 行 : 创建 ScheduledAction 。在 TODO 【2013】【ScheduledAction】 详细解析。
- 第 14 至 15 行 :添加到订阅(
subscription
)。 - 第 18 至 20 行 :使用
threadPool
,提交任务,并创建 FutureCompleterWithConfigurableInterrupt 添加到订阅(sa
)。 - 第 22 行 :返回订阅(
sa
)。整体订阅关系如下 :
#unsubscribe()
/ #isUnsubscribed()
方法,使用 subscription
判断,点击 链接查看。
6.5 FutureCompleterWithConfigurableInterrupt
com.netflix.hystrix.strategy.concurrency.HystrixContextScheduler.FutureCompleterWithConfigurableInterrupt
,实现类似 rx.internal.schedulers.ScheduledAction.FutureCompleter
,在它的基础上,支持配置 FutureTask#cancel(Boolean)
是否可打断运行( mayInterruptIfRunning
)。
构造方法,代码如下 :
|
当命令执行超时,或是主动取消命令执行时,调用 #unsubscribe()
方法,取消执行。
当命令执行超时,或是主动取消命令执行时,调用 #unsubscribe()
方法,取消执行。
当命令执行超时,或是主动取消命令执行时,调用 #unsubscribe()
方法,取消执行。
#unsubscribe()
方法,代码如下 :
|
- 根据
shouldInterruptThread
方法,判断是否强制取消。 shouldInterruptThread
对应的方法,实现代码如下 :
|
- 当
executionIsolationThreadInterruptOnTimeout = true
时,命令可执行超时。当命令可执行超时时,强制取消。 - 当使用
HystrixCommand.queue()
返回的 Future ,可以使用Future#cancel(Boolean)
取消命令执行。从shouldInterruptThread
对应的方法可以看到,如果此时不满足命令执行超时的条件,命令执行取消的方式是非强制的。此时当executionIsolationThreadInterruptOnFutureCancel = true
时,并且调用Future#cancel(Boolean)
传递mayInterruptIfRunning = true
,强制取消命令执行。- 模拟测试用例 :
CommandHelloWorld#testAsynchronous3()
HystrixCommand#queue()
:点击 链接 查看Future#cancel(Boolean)
方法。
- 模拟测试用例 :