专栏系列文章:SpringCloud系列专栏
系列文章:
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇
SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇
SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口
SpringCloud 源码系列(14)— 服务调用Feign 之 构建@FeignClient接口动态代理
SpringCloud 源码系列(15)— 服务调用Feign 之 结合Ribbon进行负载均衡请求
SpringCloud 源码系列(16)— 熔断器Hystrix 之 基础入门篇
SpringCloud 源码系列(17)— 熔断器Hystrix 之 获取执行订阅对象Observable
线程池隔离执行原理
Hystrix线程池初始化
在构造 HystrixCommand 时,会去初始化 Hystrix 线程池 HystrixThreadPool,跟进去可以发现,初始化的逻辑就在默认实现类 HystrixThreadPoolDefault 的构造方法中,HystrixThreadPoolDefault 也是线程池调度的核心组件。
其中,ThreadPoolExecutor 线程池是由 concurrencyStrategy.getThreadPool(threadPoolKey, properties) 这段代码创建的,queueSize 队列大小默认是 -1,队列 queue 是从 ThreadPoolExecutor 中获取的,所以线程池的构造还得继续看 concurrencyStrategy.getThreadPool。
class HystrixThreadPoolDefault implements HystrixThreadPool {
private final BlockingQueue<Runnable> queue;
private final ThreadPoolExecutor threadPool;
private final HystrixThreadPoolMetrics metrics;
private final int queueSize;
public HystrixThreadPoolDefault(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter propertiesDefaults) {
// 线程池配置
this.properties = HystrixPropertiesFactory.getThreadPoolProperties(threadPoolKey, propertiesDefaults);
// 并发策略
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
// 队列大小,默认 -1
this.queueSize = properties.maxQueueSize().get();
// hystrix 线程池度量器
this.metrics = HystrixThreadPoolMetrics.getInstance(threadPoolKey,
concurrencyStrategy.getThreadPool(threadPoolKey, properties), // 创建线程池
properties);
// 默认返回的是一个使用 SynchronousQueue 队列的线程池,核心线程数和最大线程数都是默认值 10
this.threadPool = this.metrics.getThreadPool();
// SynchronousQueue
this.queue = this.threadPool.getQueue();
/* strategy: HystrixMetricsPublisherThreadPool */
HystrixMetricsPublisherFactory.createOrRetrievePublisherForThreadPool(threadPoolKey, this.metrics, this.properties);
}
}
接着看 getThreadPool 方法,这个方法就是根据配置构建线程池 ThreadPoolExecutor 的,从这个方法我们可以得到如下信息。
- 首先,从
getThreadFactory返回的 ThreadFactory 可以看出,线程名称的格式是:"hystrix-{threadPoolKey}-{number}",这跟我们在日志中看到的 hystrix 线程名称是一致的。 - 调用
getBlockingQueue方法获取一个队列,但是默认情况下maxQueueSize为 -1,那么返回的队列便是SynchronousQueue,这是一个无容量的队列,就是说默认情况下任务不会进入队列,如果线程池线程满了将直接拒绝任务。 - 如果配置了允许扩展到最大线程数 且 最大线程数大于核心线程数,那么创建的线程池在核心线程数满了后,就会继续创建线程直到达到最大线程数。否则,创建的线程池核心线程数和最大线程数就是一样的。
总结一下,默认情况下,dynamicCoreSize、dynamicMaximumSize 都是 10,maxQueueSize 等于 -1,allowMaximumSizeToDivergeFromCoreSize 默认为 false。
那么默认情况下创建的 ThreadPoolExecutor 就是备如下特性:
- 核心线程数 等于 最大线程数。
- 工作队列是一个无容量的
SynchronousQueue队列。 - 基于上面两个特性,线程池工作的最大负载就是10个线程同时工作,超出后将直接拒绝任务。
- 线程名称的格式是:
"hystrix-{threadPoolKey}-{number}"
public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) {
// 名称格式:"hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet()
final ThreadFactory threadFactory = getThreadFactory(threadPoolKey);
// 是否允许核心线程数扩大到最大线程数,默认false
final boolean allowMaximumSizeToDivergeFromCoreSize = threadPoolProperties.getAllowMaximumSizeToDivergeFromCoreSize().get();
// 核心线程数
final int dynamicCoreSize = threadPoolProperties.coreSize().get();
// 线程存活时间
final int keepAliveTime = threadPoolProperties.keepAliveTimeMinutes().get();
// 最大队列数 maxQueueSize 默认为 -1
final int maxQueueSize = threadPoolProperties.maxQueueSize().get();
// maxQueueSize <= 0 ==> SynchronousQueue
// maxQueueSize > 0 ==> LinkedBlockingQueue(maxQueueSize)
final BlockingQueue<Runnable> workQueue = getBlockingQueue(maxQueueSize);
// 允许扩展到最大线程数
if (allowMaximumSizeToDivergeFromCoreSize) {
// 最大线程数
final int dynamicMaximumSize = threadPoolProperties.maximumSize().get();
if (dynamicCoreSize > dynamicMaximumSize) {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
} else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
}
// 不允许扩展到最大线程数,核心线程数 == 最大线程数。默认会进入这里
else {
return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);
}
}
private static ThreadFactory getThreadFactory(final HystrixThreadPoolKey threadPoolKey) {
return new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "hystrix-" + threadPoolKey.name() + "-" + threadNumber.incrementAndGet());
thread.setDaemon(true);
return thread;
}
};
}
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
// maxQueueSize 默认为 -1
if (maxQueueSize <= 0) {
return new SynchronousQueue<Runnable>();
} else {
return new LinkedBlockingQueue<Runnable>(maxQueueSize);
}
}
线程池调度任务
创建调度器
上一篇文章中我们已经分析出 Observable 订阅对象被调度应该是在 executeCommandWithSpecifiedIsolation(_cmd) 方法中的,入口就是 subscribeOn(Scheculer scheduler) 这个订阅。这个 scheduler 是通过上一节中初始化的 Hystrix线程池 HystrixThreadPool 来获取的,threadPool.getScheduler(Func0 func) 返回的是一个 HystrixContextScheduler 调度器。
private Observable<R> executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd) {
// 线程池隔离
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
//...
// 获取 run() 的 Observable
return getUserExecutionObservable(_cmd);
}
})
.doOnTerminate(new Action0() {...})
.doOnUnsubscribe(new Action0() {...})
// 将 defer 返回的 Observable 放到一个调度器中异步执行,调度器 ==> HystrixContextScheduler
.subscribeOn(threadPool.getScheduler(new Func0<Boolean>() {
@Override
public Boolean call() {
// 判断是否中断线程执行,超时后中断执行
return properties.executionIsolationThreadInterruptOnTimeout().get() &&
_cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;
}
}));
}
// 信号量隔离
else {//...}
}
接着看如何创建调度器的。
通过 getScheduler 方法可以了解到,首先会通过 touchConfig() 更新线程池 ThreadPoolExecutor 的配置,这说明我们是可以在运行时动态修改线程池的参数的。但是会发现,只能修改线程池的 corePoolSize、maximumPoolSize、keepAliveTime 三个参数,所以是无法动态扩容线程池队列的。
之后创建了 HystrixContextScheduler,进入构造方法可以看到,内部又创建了一个调度器 actualScheduler,实际的类型是 ThreadPoolScheduler,可以猜想最终的调度任务应该是在 ThreadPoolScheduler 中的。
public Scheduler getScheduler(Func0<Boolean> shouldInterruptThread) {
// 动态更改线程池配置
touchConfig();
return new HystrixContextScheduler(HystrixPlugins.getInstance().getConcurrencyStrategy(), this, shouldInterruptThread);
}
private void touchConfig() {
final int dynamicCoreSize = properties.coreSize().get();
final int configuredMaximumSize = properties.maximumSize().get();
int dynamicMaximumSize = properties.actualMaximumSize();
final boolean allowSizesToDiverge = properties.getAllowMaximumSizeToDivergeFromCoreSize().get();
boolean maxTooLow = false;
if (allowSizesToDiverge && configuredMaximumSize < dynamicCoreSize) {
dynamicMaximumSize = dynamicCoreSize;
maxTooLow = true;
}
if (threadPool.getCorePoolSize() != dynamicCoreSize || (allowSizesToDiverge && threadPool.getMaximumPoolSize() != dynamicMaximumSize)) {
threadPool.setCorePoolSize(dynamicCoreSize);
threadPool.setMaximumPoolSize(dynamicMaximumSize);
}
threadPool.setKeepAliveTime(properties.keepAliveTimeMinutes().get(), TimeUnit.MINUTES);
}
public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool, Func0<Boolean> shouldInterruptThread) {
this.concurrencyStrategy = concurrencyStrategy;
this.threadPool = threadPool;
// actualScheduler => rx.Scheduler => ThreadPoolScheduler
this.actualScheduler = new ThreadPoolScheduler(threadPool, shouldInterruptThread);
}
任务调度执行
下面是线程池调度的流程源码,创建调度器和调度执行的过程看起来就像是层层嵌套代理。
- 整个流程首先创建Hystrix上下文调度器
HystrixContextScheduler,但实际的调度器是ThreadPoolScheduler。 - 调度器创建好了之后就是任务调度了,首先会创建调度工作者,先是 HystrixContextScheduler 调用
createWorker()创建 Hystrix上下文调度工作者HystrixContextSchedulerWorker,但又由 ThreadPoolScheduler 创建了一个代理工作者ThreadPoolWorker。 - 最后就是调度任务了,首先执行 HystrixContextSchedulerWorker 的
schedule调度方法,这个调度方法最后又用 ThreadPoolWorker 来执行调度。
综上来看,真正的调度逻辑,其实就是在 HystrixContextSchedulerWorker 和 ThreadPoolWorker 的调度方法 schedule。
HystrixContextScheduler ::
public Worker createWorker() {
// actualScheduler.createWorker() ==> ThreadPoolWorker
return new HystrixContextSchedulerWorker(actualScheduler.createWorker());
}
ThreadPoolScheduler ::
public Worker createWorker() {
return new ThreadPoolWorker(threadPool, shouldInterruptThread);
}
HystrixContextSchedulerWorker ::
public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
// 队列满了就直接抛出异常
if (threadPool != null) {
if (!threadPool.isQueueSpaceAvailable()) {
throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
}
}
// 调度 worker ==> ThreadPoolWorker
return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit);
}
ThreadPoolWorker ::
public Subscription schedule(final Action0 action) {
// 封装调度Action
ScheduledAction sa = new ScheduledAction(action);
subscription.add(sa);
sa.addParent(subscription);
ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor();
// 提交任务到线程池
FutureTask<?> f = (FutureTask<?>) executor.submit(sa);
// 中断任务的订阅 ==> shouldInterruptThread.call() 判断是否超时中断任务
sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor));
return sa;
}
为了便于理解,将源码抽象成如下的调度流程图:
线程池满了拒绝执行
在 HystrixContextSchedulerWorker 的调度方法中,先调用 threadPool.isQueueSpaceAvailable() 判断线程池队列是否还有可用空间,如果没有就会抛出拒绝异常,如果有就会用 ThreadPoolWorker 进行调度。
@Override
public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
// 队列满了就直接抛出异常
if (threadPool != null) {
if (!threadPool.isQueueSpaceAvailable()) {
throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
}
}
// 调度 worker ==> ThreadPoolWorker
return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit);
}
再来看看 isQueueSpaceAvailable 的逻辑。
默认情况下 queueSize 为 -1,如果 queueSize <= 0,就表示有可用空间,但实际上是一个 SynchronousQueue 无容量队列。之所以返回 true,任务就会直接丢到线程池去执行,如果线程池工作线程满了,就由线程池本身来拒绝任务,这一点从它的注释也可以了解到。
如果设置了队列大小(配置 maxQueueSize),且队列已使用的容量小于 queueSizeRejectionThreshold(默认为5),才表示队列有可用空间。这里可能会感觉有点疑惑,为何不是判断队列是否还有剩余容量?比如判断 threadPool.getQueue().remainingCapacity() > 0 来确定是否还有空间?
通过它的注释可以了解到,其实是为了实现动态扩容的目的,因为队列的大小是不能动态修改的,但为了能在运行时达到队列动态扩容的目的,它用了另一个配置 queueSizeRejectionThreshold 来控制进入队列的数量。比如 maxQueueSize 配置 100,但 queueSizeRejectionThreshold 默认为 5,所以此时队列实际上最多只会进入5个任务;运行时动态修改 queueSizeRejectionThreshold 为 20,这个时候队列最多就会进入20个任务了;因此在配置时 maxQueueSize 要大于 queueSizeRejectionThreshold 才有意义。这种设计还是值得借鉴的。
/**
* Whether the threadpool queue has space available according to the <code>queueSizeRejectionThreshold</code> settings.
*
* Note that the <code>queueSize</code> is an final instance variable on HystrixThreadPoolDefault, and not looked up dynamically.
* The data structure is static, so this does not make sense as a dynamic lookup.
* The <code>queueSizeRejectionThreshold</code> can be dynamic (up to <code>queueSize</code>), so that should
* still get checked on each invocation.
* <p>
* If a SynchronousQueue implementation is used (<code>maxQueueSize</code> <= 0), it always returns 0 as the size so this would always return true.
*/
@Override
public boolean isQueueSpaceAvailable() {
// 队列大小默认为 -1
if (queueSize <= 0) {
// we don't have a queue so we won't look for space but instead let the thread-pool reject or not
return true;
} else {
// 队列已经使用的容量 超过了 队列容量拒绝阈值,queueSizeRejectionThreshold 默认为 5
return threadPool.getQueue().size() < properties.queueSizeRejectionThreshold().get();
}
}
命令提交到线程池执行
最后,再来看 ThreadPoolWorker 的调度方法,在这个方法里面,终于看到提交到线程池的代码了,这一步就真正实现了基于线程池的隔离了。
@Override
public Subscription schedule(final Action0 action) {
if (subscription.isUnsubscribed()) {
// don't schedule, we are unsubscribed
return Subscriptions.unsubscribed();
}
// This is internal RxJava API but it is too useful.
ScheduledAction sa = new ScheduledAction(action);
subscription.add(sa);
sa.addParent(subscription);
// 线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool.getExecutor();
// 提交任务到线程池
FutureTask<?> f = (FutureTask<?>) executor.submit(sa);
// 中断任务的订阅 ==> shouldInterruptThread.call() 判断是否超时中断任务
sa.add(new FutureCompleterWithConfigurableInterrupt(f, shouldInterruptThread, executor));
return sa;
}
schedule 方法最后,向提交到线程池的 ScheduledAction 添加了一个 FutureCompleterWithConfigurableInterrupt 订阅对象,它会在取消订阅的时候取消任务的执行,比如任务超时了,这个时候就会取消任务的执行。
private static class FutureCompleterWithConfigurableInterrupt implements Subscription {
private final FutureTask<?> f;
private final Func0<Boolean> shouldInterruptThread;
private final ThreadPoolExecutor executor;
private FutureCompleterWithConfigurableInterrupt(FutureTask<?> f, Func0<Boolean> shouldInterruptThread, ThreadPoolExecutor executor) {
this.f = f;
this.shouldInterruptThread = shouldInterruptThread;
this.executor = executor;
}
@Override
public void unsubscribe() {
executor.remove(f);
// 判断是否取消任务
if (shouldInterruptThread.call()) {
f.cancel(true);
} else {
f.cancel(false);
}
}
}
超时检测中断线程
超时处理器
前面分析到 executeCommandAndObserve(_cmd) 这个方法有如下这段代码,executeCommandWithSpecifiedIsolation(_cmd) 返回的 Observable 主要是采取不同的隔离策略,然后,如果启用了超时(默认启用),会增加一个 HystrixObservableTimeoutOperator 操作器,看起来就是在控制超时相关的。
private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
//...
Observable<R> execution;
if (properties.executionTimeoutEnabled().get()) {
// HystrixObservableTimeoutOperator 控制超时
execution = executeCommandWithSpecifiedIsolation(_cmd)
.lift(new HystrixObservableTimeoutOperator<R>(_cmd));
} else {
execution = executeCommandWithSpecifiedIsolation(_cmd);
}
// 设置订阅回调
return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}
再继续看 HystrixObservableTimeoutOperator,它会返回一个 Subscriber 对原 Observable 进行一个处理。
- 首先定义了一个
HystrixContextRunnable回调,就是抛出 HystrixTimeoutException 这个异常。 - 然后定义了一个
TimerListener时间监听器,这个监听器的间隔时间是getIntervalTimeInMilliseconds()返回的时间,就是命令执行超时时间。 - 所以,TimerListener 的主要作用就是在超时后更新命令
isCommandTimedOut的状态,如果超时后 isCommandTimedOut 还是NOT_EXECUTED,就会更新到TIMED_OUT,并取消任务的执行,然后发出超时的异常。 - 然后将定义的
TimerListener添加到了HystrixTimer中,所以时间控制的核心逻辑应该是在 HystrixTimer 中。 - 最后还定义了一个 Subscriber 订阅,可以看到它会去更新 isCommandTimedOut 的状态,并清理 TimerListener 调度器。
isCommandTimedOut 对应的 TimedOutStatus 有三种状态:NOT_EXECUTED, COMPLETED, TIMED_OUT。
private static class HystrixObservableTimeoutOperator<R> implements Operator<R, R> {
final AbstractCommand<R> originalCommand;
public HystrixObservableTimeoutOperator(final AbstractCommand<R> originalCommand) {
this.originalCommand = originalCommand;
}
@Override
public Subscriber<? super R> call(final Subscriber<? super R> child) {
final CompositeSubscription s = new CompositeSubscription();
child.add(s);
// 超时回调,抛出 HystrixTimeoutException 异常
final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, new Runnable() {
@Override
public void run() {
child.onError(new HystrixTimeoutException());
}
});
// 超时监听器
TimerListener listener = new TimerListener() {
// tick 在每次间隔时间会调用一次
@Override
public void tick() {
// 超时后更新状态
if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) {
// 通知超时失败
originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey);
// 停止原本的请求
s.unsubscribe();
// 抛出超时异常
timeoutRunnable.run();
}
}
// 返回间隔时间,默认就是命令执行超时时间
@Override
public int getIntervalTimeInMilliseconds() {
return originalCommand.properties.executionTimeoutInMilliseconds().get();
}
};
// 添加监听器,开始计算超时
final Reference<TimerListener> tl = HystrixTimer.getInstance().addTimerListener(listener);
// 设置到原命令中
originalCommand.timeoutTimer.set(tl);
Subscriber<R> parent = new Subscriber<R>() {
@Override
public void onCompleted() {
if (isNotTimedOut()) {
tl.clear(); // 清除 TimerListener
child.onCompleted();
}
}
//...
private boolean isNotTimedOut() {
// 任务执行完成,更新 isCommandTimedOut 状态
return originalCommand.isCommandTimedOut.get() == TimedOutStatus.COMPLETED ||
originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.COMPLETED);
}
};
s.add(parent);
return parent;
}
}
超时处理器调度
接着看 HystrixTimer 的 addTimerListener 方法,这个方法就比较好理解了,就是初始化任务调度器,然后调度 TimerListener 的执行,调度器的延迟执行时间和间隔周期都是 TimerListener 返回的命令超时时间。
总结一下 TimerListener 和 HystrixTimer 整合起来,其实就是检测任务是否超时的,任务执行超时后就会抛出超时异常,然后取消任务的执行,后面应该就会进入降级的逻辑了。
public Reference<TimerListener> addTimerListener(final TimerListener listener) {
// 初始化 executor 调度器:ScheduledThreadPoolExecutor
startThreadIfNeeded();
Runnable r = new Runnable() {
@Override
public void run() {
try {
// 触发监听器
listener.tick();
} catch (Exception e) {
logger.error("Failed while ticking TimerListener", e);
}
}
};
// 超时时间默认1000毫秒,每隔1000毫秒调度一次
ScheduledFuture<?> f = executor.get().getThreadPool().scheduleAtFixedRate(r,
listener.getIntervalTimeInMilliseconds(), // 延迟多久执行
listener.getIntervalTimeInMilliseconds(), // 执行周期
TimeUnit.MILLISECONDS);
return new TimerReference(listener, f);
}
降级回调
执行错误类型
在 Hystrix 基础篇中,我们提到了 Hystrix 执行错误的六种类型,如下图所示,除了 BAD_REQUEST,都会降级进入回调方法中。
在 AbstractCommand 中,可以看到有如下6个处理错误的方法,分别针对 Hystrix 的6中错误类型。在分析 HystrixCommand 的执行流程中,我们知道有很多的回调方法,其中错误回调肯定就会调用下面其中的一个方法来处理错误,这个就不在看了。
需要注意的是,除了 handleBadRequestByEmittingError() 这个处理 BAD_REQUEST 错误的方法,其余5个方法最后都会调用 getFallbackOrThrowException 方法来获取回调方法的Observable,这样就合上图中对应起来了。说明错误回调的封装就是 getFallbackOrThrowException。
// 信号量拒绝回调
private Observable<R> handleSemaphoreRejectionViaFallback() {
Exception semaphoreRejectionException = new RuntimeException("could not acquire a semaphore for execution");
executionResult = executionResult.setExecutionException(semaphoreRejectionException);
eventNotifier.markEvent(HystrixEventType.SEMAPHORE_REJECTED, commandKey);
return getFallbackOrThrowException(this, HystrixEventType.SEMAPHORE_REJECTED, FailureType.REJECTED_SEMAPHORE_EXECUTION,
"could not acquire a semaphore for execution", semaphoreRejectionException);
}
// 短路回调
private Observable<R> handleShortCircuitViaFallback() {
eventNotifier.markEvent(HystrixEventType.SHORT_CIRCUITED, commandKey);
Exception shortCircuitException = new RuntimeException("Hystrix circuit short-circuited and is OPEN");
executionResult = executionResult.setExecutionException(shortCircuitException);
return getFallbackOrThrowException(this, HystrixEventType.SHORT_CIRCUITED, FailureType.SHORTCIRCUIT,
"short-circuited", shortCircuitException);
}
// 线程池拒绝回调
private Observable<R> handleThreadPoolRejectionViaFallback(Exception underlying) {
eventNotifier.markEvent(HystrixEventType.THREAD_POOL_REJECTED, commandKey);
threadPool.markThreadRejection();
return getFallbackOrThrowException(this, HystrixEventType.THREAD_POOL_REJECTED, FailureType.REJECTED_THREAD_EXECUTION,
"could not be queued for execution", underlying);
}
// 超时回调
private Observable<R> handleTimeoutViaFallback() {
return getFallbackOrThrowException(this, HystrixEventType.TIMEOUT, FailureType.TIMEOUT,
"timed-out", new TimeoutException());
}
// 失败回调
private Observable<R> handleFailureViaFallback(Exception underlying) {
//...
eventNotifier.markEvent(HystrixEventType.FAILURE, commandKey);
return getFallbackOrThrowException(this, HystrixEventType.FAILURE, FailureType.COMMAND_EXCEPTION, "failed", underlying);
}
// BAD_REQUEST 处理
private Observable<R> handleBadRequestByEmittingError(Exception underlying) {
Exception toEmit = underlying;
//...
return Observable.error(toEmit);
}
降级回调处理
接着来看回调的处理方法 getFallbackOrThrowException,可以得到如下信息:
- 在执行回调的时候会通过信号量来限流,即
getFallbackSemaphore()获取的信号量,默认返回的实际类型是TryableSemaphore,限流数默认是 10。然后会通过这个信号量获取一个许可证后才去调用回调方法。 - 获取到信号量许可证后,调用
getFallbackObservable()获取回调 Observable,如果用户重载了getFallback()方法,就返回 getFallback() 的 Observable;如果没有重载,将抛出"No fallback available."的异常。 - 最后一步就是订阅回调对象,进入降级回调的方法了。
private Observable<R> getFallbackOrThrowException(final AbstractCommand<R> _cmd, final HystrixEventType eventType, final FailureType failureType, final String message, final Exception originalException) {
// 启用回调
if (properties.fallbackEnabled().get()) {
// 设置当前线程
final Action1<Notification<? super R>> setRequestContext = new Action1<Notification<? super R>>() {...};
// 回调通知
final Action1<R> markFallbackEmit = new Action1<R>() {...};
// 回调完成通知
final Action0 markFallbackCompleted = new Action0() {...};
// 回调错误后处理
final Func1<Throwable, Observable<R>> handleFallbackError = new Func1<Throwable, Observable<R>>() {...};
// 回调信号量 => TryableSemaphore
final TryableSemaphore fallbackSemaphore = getFallbackSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
// 释放信号量许可证
final Action0 singleSemaphoreRelease = new Action0() {...};
// 获取回调 Observable
Observable<R> fallbackExecutionChain;
// 获取信号量许可证
if (fallbackSemaphore.tryAcquire()) {
try {
// 用户是否定义了回调方法,即重写 getFallback()
if (isFallbackUserDefined()) {
executionHook.onFallbackStart(this);
fallbackExecutionChain = getFallbackObservable();
} else {
// 抛出异常:"No fallback available."
fallbackExecutionChain = getFallbackObservable();
}
} catch (Throwable ex) {
fallbackExecutionChain = Observable.error(ex);
}
// 执行回调
return fallbackExecutionChain
.doOnEach(setRequestContext)
.lift(new FallbackHookApplication(_cmd))
.lift(new DeprecatedOnFallbackHookApplication(_cmd))
.doOnNext(markFallbackEmit)
.doOnCompleted(markFallbackCompleted)
.onErrorResumeNext(handleFallbackError)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} else {
return handleFallbackRejectionByEmittingError();
}
} else {
return handleFallbackDisabledByEmittingError(originalException, failureType, message);
}
}
一张图总结Hystrix线程池隔离运行流程
下面用一张图总结下Hystrix任务提交到线程池执行的流程。
断路器工作原理
断路器初始化
在 AbstractCommand 构造方法中,初始化了断路器 HystrixCircuitBreaker, 如果启用了断路器(circuitBreakerEnabled),就从 HystrixCircuitBreaker.Factory 中获取一个 断路器,默认实现类是 HystrixCircuitBreakerImpl。如果未启用断路器,默认实现类则是 NoOpCircuitBreaker,就是什么都不控制的断路器。
this.circuitBreaker = initCircuitBreaker(this.properties.circuitBreakerEnabled().get(), circuitBreaker,
this.commandGroup, this.commandKey, this.properties, this.metrics);
private static HystrixCircuitBreaker initCircuitBreaker(boolean enabled, HystrixCircuitBreaker fromConstructor,
HystrixCommandGroupKey groupKey, HystrixCommandKey commandKey,
HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
if (enabled) {
if (fromConstructor == null) {
// 从 Factory 中获取默认的 断路器
return HystrixCircuitBreaker.Factory.getInstance(commandKey, groupKey, properties, metrics);
} else {
return fromConstructor;
}
} else {
// 禁用断路器就返回什么都不操作的实现类
return new NoOpCircuitBreaker();
}
}
HystrixCircuitBreakerImpl 的构造方法主要需要 HystrixCommandProperties 和 HystrixCommandMetrics,可想而知,断路器需要统计 hystrix 请求的错误、超时、拒绝、成功等次数,然后决定是否打开断路器,那么这些统计的数据来源就是 HystrixCommandMetrics.
protected HystrixCircuitBreakerImpl(HystrixCommandKey key, HystrixCommandGroupKey commandGroup,
HystrixCommandProperties properties, HystrixCommandMetrics metrics) {
this.properties = properties;
this.metrics = metrics;
}
断路器工作机制
断路器允许执行请求
之前分析 applyHystrixSemantics(_cmd) 时,我们看到了断路器的使用,applyHystrixSemantics 是应用Hystrix的核心入口,会先通过断路器判断是否允许放行请求,断路器不允许则会走断路器拒绝降级的方法。
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
// 断路器是否允许请求
if (circuitBreaker.allowRequest()) {
//...
} else {
// 短路 => 降级
return handleShortCircuitViaFallback();
}
}
接着看 allowRequest() 方法:
- 首先判断是否强制打开了断路器,如果打开了,就不允许请求。也就是我们可以在某些情况下手动打开断路器来阻止级联调用。
- 接着判断是否强制关闭了断路器,如果关闭了,就允许请求。也就是说断路器就算打开了,我们也可以手动关闭。
- 如果没有手动打开或关闭断路器,最后会判断 断路器未打开,或者断路器打开了但允许放行一个请求去验证被调用方已经恢复了,就允许放行请求。否则将拒绝请求执行。
public boolean allowRequest() {
// 断路器手动强制打开
if (properties.circuitBreakerForceOpen().get()) {
return false;
}
// 断路器手动强制关闭
if (properties.circuitBreakerForceClosed().get()) {
// 调度一次,进行统计,决定是否打开断路器
isOpen();
return true;
}
// 断路器未打开 或者 运行打开
return !isOpen() || allowSingleTest();
}
断路器打开
isOpen() 方法判断断路器是否打开:
- 首先判断
circuitOpen是否为 true,是则表示断路器打开了。也就是说断路器的状态是由circuitOpen这个 AtomicBoolean 来控制的。 - circuitOpen 为 false,则说明断路器未打开,接着从统计
HystrixCommandMetrics中获取统计的计数HealthCounts,后面的逻辑就是根据 HealthCounts 计算是否要打开断路器。 - 如果
总的请求数 < 断路器请求阈值(默认20)就不会打开断路器。 - 如果
错误百分比率 < 断路器错误比率阈值(默认50%)就不会打开断路器。 - 否则就会打开断路器,circuitOpen 的状态设置为 true,同时设置了断路器的打开时间。
Hystrix 断路器的设计很好的利用了原子类的特性,circuitOpen 的更新采用基于 CAS 指令的 compareAndSet,高并发情况下也能安全更新,更新成功再设置断路器打开时间,更新失败则不更新。
private AtomicBoolean circuitOpen = new AtomicBoolean(false);
public boolean isOpen() {
// circuitOpen 是否打开
if (circuitOpen.get()) {
return true;
}
// 获取请求计数
HealthCounts health = metrics.getHealthCounts();
// 小于断路器请求的阈值,默认为20,至少超过20个请求后才会开启断路器
if (health.getTotalRequests() < properties.circuitBreakerRequestVolumeThreshold().get()) {
return false;
}
// 错误百分比
if (health.getErrorPercentage() < properties.circuitBreakerErrorThresholdPercentage().get()) {
return false;
} else {
// 打开断路器
if (circuitOpen.compareAndSet(false, true)) {
// 设置断路器打开的时间
circuitOpenedOrLastTestedTime.set(System.currentTimeMillis());
return true;
} else {
return true;
}
}
}
断路器半开
如果断路器已经打开了,这时断路器会休眠一段时间后,放一个请求去测试被调用方是否已经恢复,这就是半开状态。
看 allowSingleTest() 这个方法,是否进入半开状态的依据是:
- 判断当前时间是否大于 断路器打开时间+断路器休眠窗口时间,休眠时间默认是5000毫秒,如果是的会同时更新断路器打开时间为当前时间。。
- 也就是说每隔5000毫秒,就放一个请求去测试,如果请求继续失败,断路器还是维持打开状态,这就是半开状态,
OPEN -> HALF-OPEN。
public boolean allowSingleTest() {
// 断路器打开时间
long timeCircuitOpenedOrWasLastTested = circuitOpenedOrLastTestedTime.get();
// 断路器打开 且 当前时间 > (断路器打开时间 + 断路器休眠窗口时间(默认5000毫秒))
if (circuitOpen.get() && System.currentTimeMillis() > timeCircuitOpenedOrWasLastTested + properties.circuitBreakerSleepWindowInMilliseconds().get()) {
// 断路器打开时间设置为当前时间
if (circuitOpenedOrLastTestedTime.compareAndSet(timeCircuitOpenedOrWasLastTested, System.currentTimeMillis())) {
return true;
}
}
return false;
断路器恢复
那断路器打开了何时恢复呢?跟踪一下 circuitOpen 就知道了,HystrixCircuitBreaker 有个方法 markSuccess() 会去在断路器打开时关闭断路器。
public void markSuccess() {
if (circuitOpen.get()) {
// 关闭断路器
if (circuitOpen.compareAndSet(true, false)) {
metrics.resetStream();
}
}
}
在 executeCommandAndObserve(_cmd) 这个方法中,可以看到有两个回调函数 markEmits、markOnCompleted,他们会在请求执行成功和完成后被回调执行,回调函数里调用了 circuitBreaker.markSuccess(); 来告诉断路器请求成功了,然后断路器就会进入关闭状态,HALF -> CLOSE。
private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread();
// 标记命令已经执行
final Action1<R> markEmits = new Action1<R>() {
@Override
public void call(R r) {
if (commandIsScalar()) {
//...
// 告诉断路器请求成功
circuitBreaker.markSuccess();
}
}
};
// 标记命令执行结束
final Action0 markOnCompleted = new Action0() {
@Override
public void call() {
if (!commandIsScalar()) {
//...
// 告诉断路器请求成功
circuitBreaker.markSuccess();
}
}
};
// 处理回调
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {...};
// 设置当前线程
final Action1<Notification<? super R>> setRequestContext = new Action1<Notification<? super R>>() {...};
Observable<R> execution;
if (properties.executionTimeoutEnabled().get()) {
// 超时会由 HystrixObservableTimeoutOperator 处理,抛出 HystrixTimeoutException 超时异常
execution = executeCommandWithSpecifiedIsolation(_cmd).lift(new HystrixObservableTimeoutOperator<R>(_cmd));
} else {
execution = executeCommandWithSpecifiedIsolation(_cmd);
}
// 设置订阅回调
return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}
一张图总结断路器工作机制
最后,还是用一张图总结下断路器的工作机制。