歡迎訪問陳同學博客原文
Spring Cloud 源碼學習之 Hystrix 入門
Spring Cloud 之 Hystrix 跨線程傳遞數據
本文學習了 Hystrix 工作原理及源碼,關注點在整體處理流程,不涉及具體的實現細節。後續將逐漸寫Metrics收集、斷路器、隔離、請求緩存等,有興趣可以關注奧。
下面 流程圖 來源於 Hystrix Wiki,展現了 Hystrix 工作原理,官方 Wiki 中對每一步都做了詳細的描述,可以直接參考。
文中源碼基於 Spring Cloud Finchley.SR1、Spring Boot 2.0.6.RELEASE.
工作原理簡述
當需要完成某項任務時,通過 Hystrix 將任務包裹起來,交由 Hystrix 來完成任務,從而享受 Hystrix 帶來保護。這和古代鏢局生意有點類似,將任務委託給鏢局,以期安全完成任務。
上圖展示了 Hystrix 完成任務的處理流程,下面對1到9步驟進行簡述:
1.構建命令
Hystrix 提供了兩個Command, HystrixCommand
和 HystrixObservableCommand
,可以使用這兩個對象來包裹待執行的任務。
例如使用 @HystrixCommand 註解標記方法,Hystrix 將利用AOP自動將目標方法包裝成HystrixCommand來執行。
@HystrixCommand
public String hello() {
...
}
也可以繼承HystrixCommand或HystrixObservableCommand來創建Command,例如:
public class MyCommand extends HystrixCommand {
public MyCommand(HystrixCommandGroupKey group) {
super(group);
}
@Override
protected Object run() throws Exception {
// 需要做的事情及需要返回的結果
return null;
}
}
任務委託給 Hystrix 後,Hystrix 可以應用自己的一系列保護機制,在執行用戶任務的各節點(執行前、執行後、異常、超時等)做一系列的事情。
2.執行命令
有四種方式執行command。
- R execute():同步執行,從依賴服務得到單一結果對象
- Future queue():異步執行,返回一個 Future 以便獲取執行結果,也是單一結果對象
- Observable observe():hot observable,創建Observable後會訂閱Observable,可以返回多個結果
- Observable toObservable():cold observable,返回一個Observable,只有訂閱時纔會執行,可以返回多個結果
execute() 的實現爲 queue().get(); queue() 的實現爲 toObservable().toBlocking().toFuture()。
最後Obserable都由toObservable()來創建,本文的主要內容就是toObservable()。
// 利用queue()拿到Future, 執行 get()同步等待拿到執行結果
public R execute() {
...
return queue().get();
}
// 利用toObservable()得到Observable最後轉成Future
public Future<R> queue() {
final Future<R> delegate = toObservable().toBlocking().toFuture();
...
}
// 利用toObservable()得到Observable並直接訂閱它,立即執行命令
public Observable<R> observe() {
ReplaySubject<R> subject = ReplaySubject.create();
final Subscription sourceSubscription = toObservable().subscribe(subject);
...
}
3.檢查緩存
第3到9步驟構成了 Hystrix 的保護能力,通過這一些列步驟來執行任務,從而起到保護作用。
如果啓用了 Hystrix Cache,任務執行前將先判斷是否有相同命令執行的緩存。如果有則直接返回緩存的結果;如果沒有緩存的結果,但啓動了緩存,將緩存本次執行結果以供後續使用。
4.檢查斷路器是否打開
斷路器(circuit-breaker)和保險絲類似,保險絲在發生危險時將會燒斷以保護電路,而斷路器可以在達到我們設定的閥值時觸發短路(比如請求失敗率達到50%),拒絕執行任何請求。
如果斷路器被打開,Hystrix 將不會執行命令,直接進入Fallback處理邏輯。
5.檢查線程池/信號量情況
Hystrix 隔離方式有線程池隔離和信號量隔離。當使用Hystrix線程池時,Hystrix 默認爲每個依賴服務分配10個線程,當10個線程都繁忙時,將拒絕執行命令。信號量同理。
6.執行具體的任務
通過HystrixObservableCommand.construct()
或者 HystrixCommand.run()
來運行用戶真正的任務。
7.計算鏈路健康情況
每次開始執行command、結束執行command以及發生異常等情況時,都會記錄執行情況,例如:成功、失敗、拒絕以及超時等情況,會定期處理這些數據,再根據設定的條件來判斷是否開啓斷路器。
8.命令失敗時執行 Fallback 邏輯
在命令失敗時執行用戶指定的 Fallback 邏輯。上圖中的斷路、線程池拒絕、信號量拒絕、執行執行、執行超時都會進入 Fallback 處理。
9.返回執行結果
原始結果將以Observable形式返回,在返回給用戶之前,會根據調用方式的不同做一些處理。
下面是 Hystrix Return flow。
源碼學習
小故事
由於最終入口都是 toObservable(),就從 AbstractCommand的 Observable<R> toObservable()
方法開始。
Hystrix 使用觀察者模式,Observable 即被觀察者,被觀察者些狀態變更時,觀察者可以做出各項響應。舉個例子:大廳中一位演講者正在分享,廳中有觀衆和工作人員,可能發生如下事情:
被觀察者 事件 觀察者
-----------------------------------
演講者 分享到精彩處 -> 觀衆鼓掌
演講者 講的口乾舌燥 -> 工作人員遞上一瓶水
演講者 放出自己的二維碼 -> 觀衆掃描
因爲 Hystrix 基於RxJava,RxJava 初次看會比較複雜。爲了便於下文理解,可以將Observable理解爲數據源、數據發射器,上面例子中,演講者各種行爲都可以抽象爲數據源在發射數據,而各種接收者可以做出各種響應。
toObservable()
toObservable() 主要源碼如下:
public Observable<R> toObservable() {
final AbstractCommand<R> _cmd = this;
// 命令執行結束後的清理者
final Action0 terminateCommandCleanup = new Action0() {...};
// 取消訂閱時處理者
final Action0 unsubscribeCommandCleanup = new Action0() {...};
// 重點:Hystrix 核心邏輯: 斷路器、隔離
final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {...};
// 發射數據(OnNext表示發射數據)時的Hook
final Func1<R, R> wrapWithAllOnNextHooks = new Func1<R, R>() {...};
// 命令執行完成的Hook
final Action0 fireOnCompletedHook = new Action0() {...};
// 通過Observable.defer()創建一個Observable
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
final boolean requestCacheEnabled = isRequestCachingEnabled();
final String cacheKey = getCacheKey();
// 首先嚐試從請求緩存中獲取結果
if (requestCacheEnabled) {
HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.get(cacheKey);
if (fromCache != null) {
isResponseFromCache = true;
return handleRequestCacheHitAndEmitValues(fromCache, _cmd);
}
}
// 使用上面的Func0:applyHystrixSemantics 來創建Observable
Observable<R> hystrixObservable =
Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
Observable<R> afterCache;
// 如果啓用請求緩存,將Observable包裝成HystrixCachedObservable並進行相關處理
if (requestCacheEnabled && cacheKey != null) {
HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);
...
} else {
afterCache = hystrixObservable;
}
// 返回Observable
return afterCache
.doOnTerminate(terminateCommandCleanup)
.doOnUnsubscribe(unsubscribeCommandCleanup)
.doOnCompleted(fireOnCompletedHook);
}
});
}
上面的代碼可以換種思維方式來理解。平時開發時都是下面這種模式,按順序不斷的做事情,是一個很好的執行者。
public void methodA{
try {
// 1. 做第一件事情
// 2. 調用methodB()做第二件事情
// 3. 做第三件事情
...
} catch (Exception e) {
// 處理錯誤
} finally {
// 最後一定要做的事情
}
}
用一張圖來看 toObservable() 方法。這種方式是“軍師型”,排兵佈陣,先創造了各個處理者,然後創造被觀察者,再設置Observable發生各種情況時由誰來處理,完全掌控全局。
解釋下Action0、Func1這種對象。Action、Func和Runnable、Callable類似,是一個可以被執行的實體。Action沒有返回值,Action0…ActionN表示有0…N個參數,Action0就表示沒有參數;Func有返值,0…N一樣表示參數。
public interface Action0 extends Action {
void call();
}
public interface Func1<T, R> extends Function {
R call(T t);
}
下面用核心的 applyHystrixSemantics 來闡述一下。
// applyHystrixSemantics 是一個Func0(理解爲執行實體或處理者),表示沒有參數,返回值是Observable。
final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
// Func0 做的事情如下
@Override
public Observable<R> call() {
// 如果未訂閱,返回一個"啞炮" Observable, 即一個不會發射任何數據的Observable
if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
return Observable.never();
}
// 調用applyHystrixSemantics()來創建Observable
return applyHystrixSemantics(_cmd);
}
};
因此,當執行Func0: applyHystrixSemantics時,可以得到一個Observable。toObservable() 大量代碼在準備處理者(觀察者),實際使用時是方法最後的 Observable.defer(new Func0<Observable>(){…}
Observable.defer
defer譯爲延遲,表示演講者會等有觀衆來時纔開始分享。Observable.defer() 就是說:必須有觀察者訂閱了我是,我纔開始發射數據。而defer()的參數是個Func0,是一個會返回Observable的執行實體。下面看看defer():
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
// 再一次使用Observable.defer()技能,這次用的是applyHystrixSemantics這個Func0
Observable<R> hystrixObservable =
Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
... // 此處忽略了請求緩存處理,上面已有提及
Observable<R> afterCache;
...
// 爲Observable綁定幾個特定事件的處理者,這都是上門創建的Action0
return afterCache
.doOnTerminate(terminateCommandCleanup)
.doOnUnsubscribe(unsubscribeCommandCleanup)
.doOnCompleted(fireOnCompletedHook);
}
});
applyHystrixSemantics()
接着看applyHystrixSemantics這個Func0,Func0的call()中調用的是applyHystrixSemantics()函數。
// Semantics 譯爲語義, 應用Hystrix語義很拗口,其實就是應用Hystrix的斷路器、隔離特性
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
// 源碼中有很多executionHook、eventNotifier的操作,這是Hystrix拓展性的一種體現。這裏面啥事也沒做,留了個口子,開發人員可以拓展
executionHook.onStart(_cmd);
// 判斷斷路器是否開啓
if (circuitBreaker.attemptExecution()) {
// 獲取執行信號
final TryableSemaphore executionSemaphore = getExecutionSemaphore();
final AtomicBoolean semaphoreHasBeenReleased = new AtomicBoolean(false);
final Action0 singleSemaphoreRelease = new Action0() {...};
final Action1<Throwable> markExceptionThrown = new Action1<Throwable>() {...};
// 判斷是否信號量拒絕
if (executionSemaphore.tryAcquire()) {
try {
// 重點:處理隔離策略和Fallback策略
return executeCommandAndObserve(_cmd)
.doOnError(markExceptionThrown)
.doOnTerminate(singleSemaphoreRelease)
.doOnUnsubscribe(singleSemaphoreRelease);
} catch (RuntimeException e) {
return Observable.error(e);
}
} else {
return handleSemaphoreRejectionViaFallback();
}
}
// 開啓了斷路器,執行Fallback
else {
return handleShortCircuitViaFallback();
}
}
executeCommandAndObserve()
下面看**executeCommandAndObserve()**方法,處理隔離策略和各種Fallback.
private Observable<R> executeCommandAndObserve(final AbstractCommand<R> _cmd) {
final HystrixRequestContext currentRequestContext = HystrixRequestContext.getContextForCurrentThread();
final Action1<R> markEmits = new Action1<R>() {...};
final Action0 markOnCompleted = new Action0() {...};
// 利用Func1獲取處理Fallback的 Observable
final Func1<Throwable, Observable<R>> handleFallback = new Func1<Throwable, Observable<R>>() {
@Override
public Observable<R> call(Throwable t) {
circuitBreaker.markNonSuccess();
Exception e = getExceptionFromThrowable(t);
executionResult = executionResult.setExecutionException(e);
// 拒絕處理
if (e instanceof RejectedExecutionException) {
return handleThreadPoolRejectionViaFallback(e);
// 超時處理
} else if (t instanceof HystrixTimeoutException) {
return handleTimeoutViaFallback();
} else if (t instanceof HystrixBadRequestException) {
return handleBadRequestByEmittingError(e);
} else {
...
return handleFailureViaFallback(e);
}
}
};
final Action1<Notification<? super R>> setRequestContext ...
Observable<R> execution;
// 利用特定的隔離策略來處理
if (properties.executionTimeoutEnabled().get()) {
execution = executeCommandWithSpecifiedIsolation(_cmd)
.lift(new HystrixObservableTimeoutOperator<R>(_cmd));
} else {
execution = executeCommandWithSpecifiedIsolation(_cmd);
}
return execution.doOnNext(markEmits)
.doOnCompleted(markOnCompleted)
// 綁定Fallback的處理者
.onErrorResumeNext(handleFallback)
.doOnEach(setRequestContext);
}
executeCommandWithSpecifiedIsolation()
接着看隔離特性的處理:executeCommandWithSpecifiedIsolation()
private Observable<R> executeCommandWithSpecifiedIsolation(final AbstractCommand<R> _cmd) {
// 線程池隔離
if (properties.executionIsolationStrategy().get() == ExecutionIsolationStrategy.THREAD) {
// 再次使用 Observable.defer(), 通過執行Func0來得到Observable
return Observable.defer(new Func0<Observable<R>>() {
@Override
public Observable<R> call() {
// 收集metric信息
metrics.markCommandStart(commandKey, threadPoolKey, ExecutionIsolationStrategy.THREAD);
...
try {
... // 獲取真正的用戶Task
return getUserExecutionObservable(_cmd);
} catch (Throwable ex) {
return Observable.error(ex);
}
...
}
// 綁定各種處理者
}).doOnTerminate(new Action0() {...})
.doOnUnsubscribe(new Action0() {...})
// 綁定超時處理者
.subscribeOn(threadPool.getScheduler(new Func0<Boolean>() {
@Override
public Boolean call() {
return properties.executionIsolationThreadInterruptOnTimeout().get() && _cmd.isCommandTimedOut.get() == TimedOutStatus.TIMED_OUT;
}
}));
}
// 信號量隔離,和線程池大同小異,全部省略了
else {
return Observable.defer(new Func0<Observable<R>>() {...}
}
}
getUserExecutionObservable()就不接着寫了,可以自己看下,就是拿到用戶真正要執行的任務。這個任務就是這樣被Hystrix包裹着,置於層層防護之下。
倒過來看
上面方法層層調用,倒過來看,就是先創建一個Observable,然後綁定各種事件對應的處理者,如下圖:
各類doOnXXXX,表示發生XXX事件時做什麼事情。
參考
- DD 《Spring Cloud微服務實戰》
- 朱榮鑫,張天,黃迪璇《Spring Cloud微服務架構進階》
- Hystrix Wiki: How it works
歡迎關注陳同學的公衆號,一起學習,一起成長