摘要: 原創出處 www.iocoder.cn/Hystrix/com… 「芋道原始碼」歡迎轉載,保留摘要,謝謝!
本文主要基於 Hystrix 1.5.X 版本
- 1. 概述
- 2. 好處
- 3. Observable#defer(...)
- 4. AbstractCommand#toObservavle(...)
- 5. HystrixCachedObservable
- 6. HystrixCommandResponseFromCache
- 666. 彩蛋
???關注微信公眾號:【芋道原始碼】有福利:
- RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
- RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
- 您對於原始碼的疑問每條留言都將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢。
- 新的原始碼解析文章實時收到通知。每週更新一篇左右。
- 認真的原始碼交流微信群。
1. 概述
本文主要分享 Hystrix 執行命令的結果快取。
建議 :對 RxJava 已經有一定的瞭解的基礎上閱讀本文。
Hystrix 執行命令整體流程如下圖:
- 紅圈 :在 《Hystrix 原始碼解析 —— 執行命令方式》 有詳細解析。
- 紫圈 :在
#toObservable()
方法裡,如果請求結果快取這個特性被啟用,並且快取命中,則快取的迴應會立即通過一個 Observable 物件的形式返回;如果快取未命中,則返回【訂閱了執行命令的 Observable】的 ReplySubject 物件快取執行結果。- ReplySubject 能夠重放執行結果,從而實現快取的功效。本文不對 ReplySubject 做太多擴充,感興趣的同學可以閱讀 《ReactiveX/RxJava文件中文版 —— Subject》 。
在官方提供的示例中,我們使用 CommandUsingRequestCache 進行除錯 。
推薦 Spring Cloud 書籍:
- 請支援正版。下載盜版,等於主動編寫低階 BUG 。
- 程式猿DD —— 《Spring Cloud微服務實戰》
- 周立 —— 《Spring Cloud與Docker微服務架構實戰》
- 兩書齊買,京東包郵。
2. 好處
點選 《【翻譯】Hystrix文件-實現原理》「請求快取」 ,檢視對請求快取的好處分享,寫的真的很贊。
3. Observable#defer(...)
本小節為擴充內容,原始碼解析 RxJava ( 非 Hystrix ) 的 Observable#defer(...)
的方法實現。考慮到 Hystrix 大量使用,為了更好的理解,解析下原始碼。
《RxJava 原始碼解析 —— Observable#defer(...)》
4. AbstractCommand#toObservavle(...)
AbstractCommand#toObservavle(...)
方法,程式碼如下 :
1: public Observable<R> toObservable() {
2: final AbstractCommand<R> _cmd = this;
3:
4: //doOnCompleted handler already did all of the SUCCESS work
5: //doOnError handler already did all of the FAILURE/TIMEOUT/REJECTION/BAD_REQUEST work
6: final Action0 terminateCommandCleanup = new Action0() {} // ... 省略
7:
8: //mark the command as CANCELLED and store the latency (in addition to standard cleanup)
9: final Action0 unsubscribeCommandCleanup = new Action0() {} // ... 省略
10:
11: final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
12: @Override
13: public Observable<R> call() {
14: if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
15: return Observable.never();
16: }
17: return applyHystrixSemantics(_cmd);
18: }
19: };
20:
21: final Func1<R, R> wrapWithAllOnNextHooks = new Func1<R, R>() {} // ... 省略
22:
23: final Action0 fireOnCompletedHook = new Action0() {} // ... 省略
24:
25: return Observable.defer(new Func0<Observable<R>>() {
26: @Override
27: public Observable<R> call() {
28: /* this is a stateful object so can only be used once */
29: if (!commandState.compareAndSet(CommandState.NOT_STARTED, CommandState.OBSERVABLE_CHAIN_CREATED)) {
30: IllegalStateException ex = new IllegalStateException("This instance can only be executed once. Please instantiate a new instance.");
31: //TODO make a new error type for this
32: throw new HystrixRuntimeException(FailureType.BAD_REQUEST_EXCEPTION, _cmd.getClass(), getLogMessagePrefix() + " command executed multiple times - this is not permitted.", ex, null);
33: }
34:
35: // 命令開始時間戳
36: commandStartTimestamp = System.currentTimeMillis();
37:
38: // TODO【2001】【列印日誌】
39: if (properties.requestLogEnabled().get()) {
40: // log this command execution regardless of what happened
41: if (currentRequestLog != null) {
42: currentRequestLog.addExecutedCommand(_cmd);
43: }
44: }
45:
46: // 快取開關、快取KEY
47: final boolean requestCacheEnabled = isRequestCachingEnabled();
48: final String cacheKey = getCacheKey();
49:
50: // 優先從快取中獲取
51: /* try from cache first */
52: if (requestCacheEnabled) {
53: HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.get(cacheKey);
54: if (fromCache != null) {
55: isResponseFromCache = true; // 標記 從快取中結果
56: return handleRequestCacheHitAndEmitValues(fromCache, _cmd);
57: }
58: }
59:
60: // 獲得 執行命令Observable
61: Observable<R> hystrixObservable =
62: Observable.defer(applyHystrixSemantics)
63: .map(wrapWithAllOnNextHooks);
64:
65: // 獲得 快取Observable
66: Observable<R> afterCache;
67: // put in cache
68: if (requestCacheEnabled && cacheKey != null) {
69: // wrap it for caching
70: HystrixCachedObservable<R> toCache = HystrixCachedObservable.from(hystrixObservable, _cmd);
71: // 併發若不存在
72: HystrixCommandResponseFromCache<R> fromCache = (HystrixCommandResponseFromCache<R>) requestCache.putIfAbsent(cacheKey, toCache);
73: if (fromCache != null) { // 新增失敗
74: // another thread beat us so we'll use the cached value instead
75: toCache.unsubscribe();
76: isResponseFromCache = true; // 標記 從快取中結果
77: return handleRequestCacheHitAndEmitValues(fromCache, _cmd);
78: } else { // 新增成功
79: // we just created an ObservableCommand so we cast and return it
80: afterCache = toCache.toObservable();
81: }
82: } else {
83: afterCache = hystrixObservable;
84: }
85:
86: //
87: return afterCache
88: .doOnTerminate(terminateCommandCleanup) // perform cleanup once (either on normal terminal state (this line), or unsubscribe (next line))
89: .doOnUnsubscribe(unsubscribeCommandCleanup) // perform cleanup once
90: .doOnCompleted(fireOnCompletedHook);
91: }
92: });
93: }複製程式碼
- 第 2 行 :
_cmd
指向當前命令物件,用於下面實現 FuncX ,ActionX 內部類使用。 - 第 11 至 19 行 :當快取特性未開啟,或者快取未命中時,使用
applyHystrixSemantics
傳入Observable#defer(...)
方法,宣告執行命令的 Observable。 第 25 行 :宣告快取 Observable 。Hystrix 執行命令的 Observable 宣告關係如下:
第 29 至 33 行 :一條命令只能執行一次。
- 第 36 行 :記錄命令開始時間戳。
- 第 38 至 44 行 :TODO【2001】【列印日誌】
- 第 47 至 48 行 :快取存開關、KEY 。
- 第 52 至 58 行 :如果請求結果快取這個特性被啟用,並且快取命中,則快取的迴應會立即通過一個 Observable 物件的形式返回。
- 第 53 行 :
requestCache
快取,在 TODO 【2008】【請求快取】 詳細解析。 - 第 53 行 :「6. HystrixCommandResponseFromCache」 詳細解析。
- 第 56 行 :
#handleRequestCacheHitAndEmitValues(...)
方法,在第 78 行詳細解析。
- 第 53 行 :
- 第 61 至 63 行 :獲取執行命令的 Observable 。在 《Hystrix 原始碼解析 —— 命令執行(一)之正常執行邏輯》 詳細解析。
- 第 68 至 81 行 :當快取特性開啟,並且快取未命中時,建立【訂閱了執行命令的 Observable】的 HystrixCommandResponseFromCache 。
- 第 69 至 72 行 :建立 HystrixCommandResponseFromCache ,並新增到
requestCache
。喲,HystrixRequestCache#putIfAbsent(...)
方法,多個執行緒新增時,只有一個執行緒新增成功。 - 第 73 至 77 行 :新增失敗的執行緒( 們 ):
- 第 75 行 :呼叫
HystrixCommandResponseFromCache#unsubscribe()
方法,取消 HystrixCommandResponseFromCache 的訂閱。這一步很關鍵,因為我們不希望快取不存在時,多個執行緒去執行命令,最好有且只有一個執行緒執行命令。在 「5. HystrixCachedObservable」 詳細解析。 - 第 77 行 :「6. HystrixCommandResponseFromCache」 詳細解析。
- 第 80 行 :呼叫
HystrixCommandResponseFromCachetoObservable()
方法,獲得快取 Observable 。
- 第 80 行 :呼叫
- 第 75 行 :呼叫
- 第 69 至 72 行 :建立 HystrixCommandResponseFromCache ,並新增到
- 第 82 至 84 行 :當快取特性未開啟,使用執行命令 Observable 。
- 第 87 至 91 行 :在返回的 Observable 上,訂閱一些清理的處理邏輯。對這幾個方法有疑惑的同學,可以閱讀 《給 Android 開發者的 RxJava 詳解》「 3) Subscribe (訂閱) 」 。
5. HystrixCachedObservable
com.netflix.hystrix.HystrixCachedObservable
,快取 Observable 。
HystrixCachedObservable 構造方法,程式碼如下 :
1: public class HystrixCachedObservable<R> {
2: /**
3: * 訂閱
4: */
5: protected final Subscription originalSubscription;
6: /**
7: * 快取 cachedObservable
8: */
9: protected final Observable<R> cachedObservable;
10: /**
11: * TODO 【2006】【outstandingSubscriptions】
12: */
13: private volatile int outstandingSubscriptions = 0;
14: //private AtomicInteger outstandingSubscriptions2 = new AtomicInteger(0);
15:
16: protected HystrixCachedObservable(final Observable<R> originalObservable) {
17: ReplaySubject<R> replaySubject = ReplaySubject.create();
18: this.originalSubscription = originalObservable
19: .subscribe(replaySubject);
20:
21: this.cachedObservable = replaySubject
22: .doOnUnsubscribe(new Action0() {
23: @Override
24: public void call() {
25: outstandingSubscriptions--;
26: if (outstandingSubscriptions == 0) {
27: originalSubscription.unsubscribe();
28: }
29: }
30: })
31: .doOnSubscribe(new Action0() {
32: @Override
33: public void call() {
34: outstandingSubscriptions++;
35: }
36: });
37: }複製程式碼
第 17 至 19 行 :實際上,HystrixCachedObservable 不是一個 Observable 的子類,而是對傳入的 Observable 封裝 :使用 ReplaySubject 向傳入的 Observable 發起訂閱,通過 ReplaySubject 能夠重放執行結果,從而實現快取的功效。這裡有幾個卡到筆者的並且很有趣的點,我們一一道來 :從上文中,我們可以看到,傳入的
originalObservable
為hystrixObservable
執行命令 Observable 。在 Hystrix 裡,提供了兩種執行命令的隔離方式 :執行緒池(THREAD
) 和訊號量(SEMAPHORE
)。- 當使用
THREAD
隔離時,#subscribe(replaySubject)
呼叫完成時,實際命令並未開始執行,或者說,這是一個非同步的執行命令的過程。那麼,會不會影響返回執行結果呢?答案當然是不會,BlockingObservable 在得到執行完成才會結束阻塞,此時已經有執行結果。 當使用
SEMAPHORE
隔離時,#subscribe(replaySubject)
呼叫完成時,實際命令已經執行完成,所以即使AbstractCommand#toObservavle(...)
的第 75 行 :呼叫HystrixCommandResponseFromCache#unsubscribe()
方法,也會浪費,重複執行命令。而對於THREAD
隔離的情況,通過取消訂閱的方式,只會執行一次命令。當然,如果“惡搞”THREAD
隔離的情況,增加sleep
的呼叫如下,就能達到重複執行命令的效果。
- 當使用
第 21 至 36 行 :TODO 【2006】【outstandingSubscriptions】原子性沒問題麼?歷史版本使用的是 AtomicInteger 。
HystrixCachedObservable 的其他方法,點選 連結 檢視。
6. HystrixCommandResponseFromCache
com.netflix.hystrix.HystrixCommandResponseFromCache
,是 HystrixCachedObservable 的子類。在父類的基礎上,增加了對 AbstractCommand.executionResult
的關注。
HystrixCachedObservable#from(Observable, AbstractCommand)
方法,建立 HystrixCommandResponseFromCache 物件,點選 連結 檢視。
HystrixCommandResponseFromCache#toObservableWithStateCopiedInto(...)
方法,點選 連結 檢視。
- 通過
completionLogicRun
屬性,保證#doOnError()
,#doOnCompleted()
,#doOnUnsubscribe()
方法有且只有一個方法執行具體邏輯。#doOnError()
,#doOnCompleted()
執行時,呼叫#commandCompleted()
方法,從快取命令(HystrixCommandResponseFromCache.originalCommand
) 複製executionResult
屬性給當前命令(commandToCopyStateInto
) 。#doOnUnsubscribe()
執行時,呼叫#commandUnsubscribed()
方法,使用當前命令(commandToCopyStateInto
)自己的executionResult
,不進行復制。
- TODO 【2007】【executionResult】用途
666. 彩蛋
如鯁在喉的感覺,從週六開始磨了四天多,一直沒寫到一個比較舒服的狀態。
先發現發,如果有不清晰的地方,煩請指出,謝謝。
胖友,分享一波朋友圈可好!