熔斷器 Hystrix 原始碼解析 —— 執行結果快取

芋道原始碼_以德服人_不服就幹發表於2017-11-26

摘要: 原創出處 www.iocoder.cn/Hystrix/com… 「芋道原始碼」歡迎轉載,保留摘要,謝謝!

本文主要基於 Hystrix 1.5.X 版本


???關注微信公眾號:【芋道原始碼】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有原始碼分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文註釋原始碼 GitHub 地址
  3. 您對於原始碼的疑問每條留言將得到認真回覆。甚至不知道如何讀原始碼也可以請教噢
  4. 新的原始碼解析文章實時收到通知。每週更新一篇左右
  5. 認真的原始碼交流微信群。

1. 概述

本文主要分享 Hystrix 執行命令的結果快取

建議 :對 RxJava 已經有一定的瞭解的基礎上閱讀本文。

Hystrix 執行命令整體流程如下圖:

FROM 《【翻譯】Hystrix文件-實現原理》「流程圖」

  • 紅圈 :在 《Hystrix 原始碼解析 —— 執行命令方式》 有詳細解析。
  • 紫圈 :在 #toObservable() 方法裡,如果請求結果快取這個特性被啟用,並且快取命中,則快取的迴應會立即通過一個 Observable 物件的形式返回;如果快取未命中,則返回【訂閱了執行命令的 Observable】的 ReplySubject 物件快取執行結果。

在官方提供的示例中,我們使用 CommandUsingRequestCache 進行除錯 。

推薦 Spring Cloud 書籍

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 物件的形式返回。
  • 第 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 。
  • 第 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 能夠重放執行結果,從而實現快取的功效。這裡有幾個卡到筆者的並且很有趣的點,我們一一道來 :從上文中,我們可以看到,傳入的 originalObservablehystrixObservable 執行命令 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. 彩蛋

如鯁在喉的感覺,從週六開始磨了四天多,一直沒寫到一個比較舒服的狀態。

先發現發,如果有不清晰的地方,煩請指出,謝謝。

胖友,分享一波朋友圈可好!

相關文章