一、概要
hystrix的執(zhí)行隔離策略有兩種。一種是線程池的模式哼拔,另外一種是信號量的模式嚼黔。hystrix默認的策略是線程池的模式细层。
- 線程池
對于每個command的執(zhí)行會啟用一個新的線程,同一個command key會使用同一個線程池唬涧,不同的command key會使用不同的線程池就進行隔離疫赎。
優(yōu)點:由于command的執(zhí)行隔離在不同的線程中,因此某一個command線程執(zhí)行異常不會影響到其它command的執(zhí)行碎节。如果希望使用到異步的方式執(zhí)行捧搞,只能選擇這種模式。
缺點:由于多了線程的介入狮荔,相當于增加了線程間的上下文切換的成本胎撇。另外如果系統(tǒng)的command key很多,可能會影響到系統(tǒng)的整體性能殖氏。 - 信號量
個人理解跟信號量其實沒有太大的關聯性晚树。本質上是跟主線程共用一個線程,只是這里增加了一個最大并發(fā)數的限制(信號量)受葛。
優(yōu)點:不涉及線程上下文切換题涨。在大部分業(yè)務場景下其實是能滿足的(eg.tomcat單請求單線程的業(yè)務偎谁,command key之間沒有異步執(zhí)行的需求)
缺點:某一個command執(zhí)行過程中導致線程崩潰會影響到整個流程的執(zhí)行(共享線程)。
二纲堵、源碼分析
- 信號量的接口定義
/* package */static interface TryableSemaphore {
/**
* Use like this:
* <p>
*
* <pre>
* if (s.tryAcquire()) {
* try {
* // do work that is protected by 's'
* } finally {
* s.release();
* }
* }
* </pre>
*
* @return boolean
*/
public abstract boolean tryAcquire();
/**
* ONLY call release if tryAcquire returned true.
* <p>
*
* <pre>
* if (s.tryAcquire()) {
* try {
* // do work that is protected by 's'
* } finally {
* s.release();
* }
* }
* </pre>
*/
public abstract void release();
public abstract int getNumberOfPermitsUsed();
}
接口的定義很簡單巡雨,上層調用會先tryAcquire獲得信號量,然后通過release方式釋放信號量席函。
- 實現類
實現類有兩種铐望。TryableSemaphoreActual和TryableSemaphoreNoOp。
- TryableSemaphoreActual
信號量的實現類茂附。 - TryableSemaphoreNoOp
這個實現類相當于不限制正蛙,線程池模式的實現類采用的方式就是這種。
/* package */static class TryableSemaphoreActual implements TryableSemaphore {
protected final HystrixProperty<Integer> numberOfPermits;
private final AtomicInteger count = new AtomicInteger(0);
public TryableSemaphoreActual(HystrixProperty<Integer> numberOfPermits) {
this.numberOfPermits = numberOfPermits;
}
@Override
public boolean tryAcquire() {
int currentCount = count.incrementAndGet();
if (currentCount > numberOfPermits.get()) {
count.decrementAndGet();
return false;
} else {
return true;
}
}
@Override
public void release() {
count.decrementAndGet();
}
@Override
public int getNumberOfPermitsUsed() {
return count.get();
}
}
這個實現類就不多加解釋了营曼,主要是用了AtomicInteger做加減的操作乒验。
3.調用層
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
...
/* determine if we're allowed to execute */
if (circuitBreaker.allowRequest()) {
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
final Action0 singleSemaphoreRelease = new Action0() {
@Override
public void call() {
if (semaphoreHasBeenReleased.compareAndSet(false, true)) {
-- 釋放信號量的操作
executionSemaphore.release();
}
}
};
final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {
@Override
public void call(Throwable t) {
eventNotifier.markEvent(HystrixEventType.EXCEPTION_THROWN, commandKey);
}
};
-- 獲取信號量
if (executionSemaphore.tryAcquire()) {
try {
/* used to track userThreadExecutionTime */
executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
return executeCommandAndObserve(_cmd)
.doOnError(markExceptionThrown)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} catch (RuntimeException e) {
return Observable.error(e);
}
} else {
return handleSemaphoreRejectionViaFallback();
}
} else {
return handleShortCircuitViaFallback();
}
}
三、總結
在實際的工作中對信號量和線程池的選擇還是要慎重蒂阱,事實上對于一般場景的單請求單線程的業(yè)務锻全,信號量是能夠滿足需求的。
對于需要異步(command并行執(zhí)行)的業(yè)務需要使用線程池的方式來實現录煤。