最快的腳步不是跨越,而是繼續;最慢的步伐不是緩慢,而是徘徊。
–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning
前言
Hystrix的源碼因爲是基於RxJava來書寫的,一方面是很多小夥伴對RxJava並不熟悉,另一方面是基於觀察者模式實現的代碼繞來繞去就是不好理解,所以總的來說Hystrix的源碼是比較難啃的。
前面我們已經把Hystrix的正常執行 + 異常fallback執行都“逐個擊破”了,有了良好的知識鋪墊,本文主要僅需做歸併即可捋出其執行原理。另外,雖然我們最常使用的是HystrixCommand
,而真正的執行邏輯99%
都是在AbstractCommand
裏,它纔是集大成者。
正常執行部分請參考這裏:https://fangshixiang.blog.csdn.net/article/details/104556721
異常執行部分請參考這裏:https://fangshixiang.blog.csdn.net/article/details/104718511
正文
如圖,這是Hystrix的執行過程示意圖:
AbstractCommand源碼解析
它是HystrixCommand
和HystrixObservableCommand
的抽象父類,實現了絕大部分的執行邏輯以及熔斷器控制、事件發送等…
說明:
xxxCollapser
系列如:HystrixCollapser
和HystrixObservableCollapser
它們是沒有提取Abstract抽象實現的,而是直接實現的接口。
每個請求都會生成一個command
實例,而每個command
實例都對應着一個HystrixCommandKey、HystrixCircuitBreaker、HystrixThreadPool、HystrixConcurrencyStrategy、HystrixRequestCache...
等等組件來實現各式各樣的功能。
說明:每次請求
command
是新生成的一個實例,但是對應的那些組件們可不是新的實例哦,因爲都是同一個HystrixCommandKey
的請求共用一個組件實例的~
AbstractCommand
是一個“很大的”類,此類源代碼行數2000+,所以拆分成如下幾個部分講解。
成員屬性
AbstractCommand
類擁有近30個成員屬性,但還好有了前面N篇文章的鋪陳,大部分的API、組件功能都已瞭然於胸了,所以閱讀起來還是很流暢的。
abstract class AbstractCommand<R> implements HystrixInvokableInfo<R>, HystrixObservable<R> {
// command的id
protected final HystrixCommandKey commandKey;
// 線程池分組名(理論上不同的Command可以共用一個線程池,節約資源嘛)
protected final HystrixThreadPoolKey threadPoolKey;
// 邏輯分組。用於統計
protected final HystrixCommandGroupKey commandGroup;
// 各種properties配置 均可以通過SPI方式提供
protected final HystrixCommandProperties properties;
// SPI接口(這幾個接口有詳細介紹,不陌生)
protected final HystrixEventNotifier eventNotifier;
protected final HystrixConcurrencyStrategy concurrencyStrategy;
protected final HystrixCommandExecutionHook executionHook;
// 熔斷器。若你使用配置顯示enable=false了
// 其實現就是NoOpCircuitBreaker,否則就是默認實現
protected final HystrixCircuitBreaker circuitBreaker;
// 線程池。默認實現是HystrixThreadPoolDefault
// 線程池參數使用HystrixThreadPoolProperties配置
// 通過HystrixConcurrencyStrategy#getThreadPool()得到ThreadPoolExecutor執行器
// 說明:每次getThreadPool()一下都會用最新的配置配置執行器。所以可以達到動態化的目的
// 比如說CorePoolSize、MaximumPoolSize等等都可以在properties裏動態改變
protected final HystrixThreadPool threadPool;
// Command指標收集器
protected final HystrixCommandMetrics metrics;
// 執行時候時使用的信號量(每個key一個信號量控制哦)
// 說明:它並沒有使用JDK的java.util.concurrent.Semaphore,而是自己的實現
// 信號量的實現非常簡單,所以就略嘍。
// 當沒開啓信號量隔離的時候,該實現類使用的是TryableSemaphoreNoOp
protected final TryableSemaphore executionSemaphoreOverride;
// 發生fallabck時的信號量,也是每個key一個。至於fallback都需要信號量隔離,前面有詳細說明
// 它哥倆均只有在隔離策略是SEMAPHORE纔有效。它哥倆信號量默認值都是10
protected final TryableSemaphore executionSemaphoreOverride;
// 各種狀態值(這裏也說明command是有狀態的:一個實例只能執行一次)
protected AtomicReference<CommandState> commandState = new AtomicReference<>(CommandState.NOT_STARTED);
protected AtomicReference<ThreadState> threadState = new AtomicReference<>(ThreadState.NOT_USING_THREAD);
protected final AtomicReference<TimedOutStatus> isCommandTimedOut = new AtomicReference<>(TimedOutStatus.NOT_EXECUTED);
// 執行結果
protected volatile ExecutionResult executionResult = ExecutionResult.EMPTY;
// 取消時的執行結果
protected volatile ExecutionResult executionResultAtTimeOfCancellation;
// 表示爲:響應是否來自於緩存
// 若緩存開啓了,並且請求時緩存命中了,那此值就會被置爲true
protected volatile boolean isResponseFromCache = false;
// command開始執行的時刻(並不代表一定會執行到目標方法哦)
protected volatile long commandStartTimestamp = -1L;
// 緩存、日誌相關
protected final HystrixRequestCache requestCache;
protected final HystrixRequestLog currentRequestLog;
// 緩存默認名稱(官方數據,加了這個緩存後效率提升了1-2微秒)
// 默認的key名使用getSimpleName(),簡單類名
// 但若你沒有簡單類名(比如內部類),那就使用全類名getName()
private static ConcurrentHashMap<Class<?>, String> defaultNameCache = new ConcurrentHashMap<>();
// 緩存該key是否有fallback方法,這樣如果木有就避免每次都反射去找了
protected static ConcurrentHashMap<HystrixCommandKey, Boolean> commandContainsFallback = new ConcurrentHashMap<>();
// 唯一構造器,完成了所有屬性的初始化
protected AbstractCommand(...){
// group不能爲null,但是key可以爲null -> 自動用簡單類名
this.commandGroup = initGroupKey(group);
this.commandKey = initCommandKey(key, getClass());
...
// 自己沒配置,那就使用傳入的值。若傳入爲null,就使用groupKey
// 大多數情況下讓其保持和groupKey一樣即可
this.threadPoolKey = initThreadPoolKey(threadPoolKey, this.commandGroup, this.properties.executionIsolationThreadPoolKeyOverride().get());
...
this.threadPool = initThreadPool(threadPool, this.threadPoolKey, threadPoolPropertiesDefaults);
...
}
... // 省略所有的init方法
// 允許摺疊器將此命令實例標記爲用於摺疊請求以及摺疊多少請求
void markAsCollapsedCommand(HystrixCollapserKey collapserKey, int sizeOfBatch) {
eventNotifier.markEvent(HystrixEventType.COLLAPSED, this.commandKey);
executionResult = executionResult.markCollapsed(collapserKey, sizeOfBatch);
}
// ========執行方法============
public Observable<R> observe() { ... }
public Observable<R> toObservable() { ... }
// 抽象方法:目標方法以及針對的fallback方法
protected abstract Observable<R> getExecutionObservable();
protected abstract Observable<R> getFallbackObservable();
}
光看AbstractCommand
擁有的成員屬性,就知道它有多複雜了。需要注意的是:它的屬性的訪問權限大都是protected的,所以子類均可直接訪問。它的這些屬性的初始化均在唯一的構造器裏完成,每個屬性的初始化邏輯大體相似:緩存 -> SPI -> 初始化 -> 默認值,因爲比較簡單,本文略。
完成成員屬性的準備工作後,下面就開始它的執行過程部分了。
toObservable() 所有執行方式的入口
我們知道HystrixCommand
的執行方法有多種,但其實不管哪種執行方法,最終都依賴於toObservable()
這個方法,toObservable()
它是執行原理的集大成者,所有執行方式的入口。
execute() 依賴於 queue() 依賴於 toObservable()
observe() 依賴於 toObservable()
該方法作用:用於訂閱Observable
的回調命令的異步執行,也就是說自己返回一個可被訂閱的對象:數據發射器。一旦有訂閱者就會延遲的發送數據/命令,新訂閱者是不會監聽到歷史數據的。
AbstractCommand:
public Observable<R> toObservable() {
... // 暫時省略非常多的Action和Func們
// 需要注意的是:這裏使用的是defer(),所以裏面內容是不會立馬執行的,知道有訂閱者訂閱了就執行
// 也就是說observable = command.toObservable()是不會執行
// observable.subscribe(xxx)訂閱後,纔會開始執行
// 下面使用defer的效果都一樣~~~~~~~~~
return Observable.defer(() -> {
... // 檢驗線程狀態CommandState,每個command只能被執行一次,否則額拋出HystrixRuntimeException異常
commandStartTimestamp = System.currentTimeMillis(); // 命令開始執行
... // 記錄日誌:不管發生了什麼,都要記錄這個命令的執行
// 是否允許請求緩存:properties.requestCacheEnabled().get() && getCacheKey() != null;
// 雖然properties裏默認是true開啓的,但是需要你重寫getCacheKey()緩存纔會生效的呀
boolean requestCacheEnabled = isRequestCachingEnabled();
String cacheKey = getCacheKey(); // 默認是null。重寫它緩存纔會生效
// 如果開啓了緩存,就先從緩存裏找結果
if (requestCacheEnabled) {
... // 一旦命中緩存,就處理好數據後return(關於請求緩存會後面再聊)
// 若命中了緩存,設置isResponseFromCache = true;
}
// =========如果沒有獲取到緩存,則需要執行命令獲得結果。========
// applyHystrixSemantics函數:執行熔斷器 + 目標方法等核心邏輯(最爲複雜和關鍵的一個函數,下有詳解)
// 所以這裏返回的hystrixObservable已經是目標命令結果了
// wrapWithAllOnNextHooks:觸發HystrixCommandExecutionHook相關回調
// 另外,此處也是defer實現哦,所以目標方法並不會立馬執行哦~~~(執行時機交給調用者纔對嘛)
Observable<R> hystrixObservable = Observable.defer(applyHystrixSemantics)
.map(wrapWithAllOnNextHooks);
// 它是最終的return
Observable<R> afterCache;
// 若開啓了緩存
if(requestCacheEnabled){
... // 把結果hystrixObservable緩存起來並處理後返回
} else {
afterCache = hystrixObservable;
}
// terminateCommandCleanup:執行清理(分爲目標方法執行了or沒執行)
// unsubscribeCommandCleanup:取消訂閱
// fireOnCompletedHook:觸發executionHook.onSuccess(_cmd)該方法
return afterCache.doOnTerminate(terminateCommandCleanup)
.doOnUnsubscribe(unsubscribeCommandCleanup)
.doOnCompleted(fireOnCompletedHook);
});
}
執行步驟文字描述
- 判斷線程狀態是否是
NOT_STARTED
,否則拋出HystrixRuntimeException
異常:一個命令只能執行一次 - 命令開始,使用
HystrixRequestLog
記錄該命令的執行(顯示配置requestLogEnabled = false
可關閉日誌的記錄) - 若開啓了請求緩存,那就先從緩存裏找結果(不會執行目標方法)
- 緩存開啓的條件是:
requestCacheEnabled = true
且且且getCacheKey() != null
。所以你若想要請求緩存有效,請重寫此方法並不要返回null
- 緩存開啓的條件是:
- 沒開啓緩存(緩存沒命中),則需要執行目標命令獲得結果,
Observable.defer()
保證了目標方法此時並不會被執行,而是訂閱時才異步執行(交給調用者決定嘛)applyHystrixSemantics()
方法爲執行目標方法最最最核心邏輯,後有詳解
- 若開啓了緩存,把結果放進緩存裏
- 返回結果。並且註冊上相關清理動作:
terminateCommandCleanup
:把線程狀態標記爲TERMINAL
。分爲兩種情況:- 目標代碼沒有被執行(比如從緩存裏拿的結果):清空定時監聽、記錄執行耗時、
HystrixCommandMetrics#markCommandDone()
,觸發執行完成後的函數回調(若endCurrentThreadExecutingCommand
不爲null的話) - 目標代碼執行了。邏輯完全同上,只是
markCommandDone(true)
此處傳true而已
- 目標代碼沒有被執行(比如從緩存裏拿的結果):清空定時監聽、記錄執行耗時、
unsubscribeCommandCleanup
:把線程狀態標記爲UNSUBSCRIBED
。觸發executionHook.onUnsubscribe
等動作,並且,並且重複和上步驟一模一樣的動作fireOnCompletedHook
: 僅觸發動作executionHook.onSuccess(_cmd)
這裏主要是套了一層緩存,以及清理相關動作。但其實最爲核心的還是在applyHystrixSemantics()
這個函數裏,它纔是真正的關鍵。
applyHystrixSemantics()
AbstractCommand:
private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
executionHook.onStart(_cmd); // 開始執行
// 若斷路器放行(斷路器閉合狀態,當然嘍也可能是半開狀態)
if (circuitBreaker.allowRequest()) {
// 若你實現的是線程池隔離,那麼此處實現就是TryableSemaphoreNoOp
// 若你使用信號量隔離,它就會生效啦
TryableSemaphore executionSemaphore = getExecutionSemaphore();
...
// 嘗試申請信號資源
if (executionSemaphore.tryAcquire()) {
executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
return executeCommandAndObserve(_cmd) // =====執行目標方法=====
.doOnError(markExceptionThrown) // eventNotifier.markEvent() ...
.doOnTerminate(singleSemaphoreRelease) // 確保釋放信號量
.doOnUnsubscribe(singleSemaphoreRelease); // 確保釋放信號量
} else { // 若木有信號資源了,進入到信號量的fallabck
return handleSemaphoreRejectionViaFallback();
}
} else { // 斷路器打開了禁止你訪問,那就直接執行fallabck邏輯
return handleShortCircuitViaFallback();
}
}
這裏有一個小技巧:TryableSemaphore
信號量看起來是不管咋樣都存在的,但是若你是線程池隔離的話,它的實現是NoOp空的,完美的嵌入到了正常流程裏。
執行步驟:
- 詢問斷路器是否允許請求:
circuitBreaker.allowRequest()
,若不允許執行直接執行ShortCircuit
短路fallabck邏輯,否則繼續 - 嘗試緩存信號量資源(若是線程池隔離,此處永遠爲true),若沒有信號量資源了,觸發信號量拒絕的fallabck邏輯,否則繼續
- 執行目標方法邏輯
executeCommandAndObserve(_cmd)
到這裏,執行流程分爲兩大分支:正常執行和異常執行,剛好和前面內容完成接軌:
- 正常執行部分請參考這裏:https://fangshixiang.blog.csdn.net/article/details/104556721
- 異常執行部分請參考這裏:https://fangshixiang.blog.csdn.net/article/details/104718511
總結
關於Hystrix的執行原理,AbstractCommand詳解就介紹到這了,到此關於AbstractCommand
的整個內容算是全部講述完成,所以你對Hystrix的原理應該基本掌握。Hystrix在設計上還是蠻值得借鑑的,面向使用者的API可以提供多個,但是最終都歸一到一處,達到更高的內聚效果,維護起來也更加的方便。
聲明
原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲
。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
- [享學Netflix] 一、Apache Commons Configuration:你身邊的配置管理專家
- [享學Netflix] 二、Apache Commons Configuration事件監聽機制及使用ReloadingStrategy實現熱更新
- [享學Netflix] 三、Apache Commons Configuration2.x全新的事件-監聽機制
- [享學Netflix] 四、Apache Commons Configuration2.x文件定位系統FileLocator和FileHandler
- [享學Netflix] 五、Apache Commons Configuration2.x別樣的Builder模式:ConfigurationBuilder
- [享學Netflix] 六、Apache Commons Configuration2.x快速構建工具Parameters和Configurations
- [享學Netflix] 七、Apache Commons Configuration2.x如何實現文件熱加載/熱更新?
- [享學Netflix] 八、Apache Commons Configuration2.x相較於1.x使用上帶來哪些差異?
- [享學Netflix] 九、Archaius配置管理庫:初體驗及基礎API詳解
- [享學Netflix] 十、Archaius對Commons Configuration核心API Configuration的擴展實現
- [享學Netflix] 十一、Archaius配置管理器ConfigurationManager和動態屬性支持DynamicPropertySupport
- [享學Netflix] 十二、Archaius動態屬性DynamicProperty原理詳解(重要)
- [享學Netflix] 十三、Archaius屬性抽象Property和PropertyWrapper詳解
- [享學Netflix] 十四、Archaius如何對多環境、多區域、多雲部署提供配置支持?
- [享學Netflix] 十五、Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享學Netflix] 十六、Hystrix斷路器:初體驗及RxJava簡介
- [享學Netflix] 十七、Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化
- [享學Netflix] 十八、Hystrix配置之:全局配置和實例配置
- [享學Netflix] 十九、Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解
- [享學Netflix] 二十、Hystrix跨線程傳遞數據解決方案:HystrixRequestContext
- [享學Netflix] 二十一、Hystrix指標數據收集(預熱):滑動窗口算法(附代碼示例)
- [享學Netflix] 二十二、Hystrix事件源與事件流:HystrixEvent和HystrixEventStream
- [享學Netflix] 二十三、Hystrix桶計數器:BucketedCounterStream
- [享學Netflix] 二十四、Hystrix在滑動窗口內統計:BucketedRollingCounterStream、HealthCountsStream
- [享學Netflix] 二十五、Hystrix累計統計流、分發流、最大併發流、配置流、功能流(附代碼示例)
- [享學Netflix] 二十六、Hystrix指標數據收集器:HystrixMetrics(HystrixDashboard的數據來源)
- [享學Netflix] 二十七、Hystrix何爲斷路器的半開狀態?HystrixCircuitBreaker詳解
- [享學Netflix] 二十八、Hystrix事件計數器EventCounts和執行結果ExecutionResult
- [享學Netflix] 二十九、Hystrix執行過程核心接口:HystrixExecutable、HystrixObservable和HystrixInvokableInfo
- [享學Netflix] 三十、Hystrix的fallback回退/降級邏輯源碼解讀:getFallbackOrThrowException
- [享學Netflix] 三十一、Hystrix觸發fallback降級邏輯的5種情況及代碼示例
- [享學Netflix] 三十二、Hystrix拋出HystrixBadRequestException異常爲何不會觸發熔斷?
- [享學Netflix] 三十三、Hystrix執行目標方法時,如何調用線程池資源?
- [享學Netflix] 三十四、Hystrix目標方法執行邏輯源碼解讀:executeCommandAndObserve