[享學Netflix] 十九、Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解
- 2020 年 3 月 18 日
- 筆記
千萬不要覺得學數學不重要,認為買菜都用不到。但是,但是,但是:它能決定你在哪兒買菜(畢竟拉分一般都靠數學)。
代碼下載地址:https://github.com/f641385712/netflix-learning
目錄
前言
Hystrix
提供了插件機制(SPI機制)來提升自身的擴展性,提高彈性。這裡所指的插件包括:
HystrixConcurrencyStrategy
:獲取並發相關類HystrixEventNotifier
:事件通知類HystrixMetricsPublisher
:度量信息類HystrixPropertiesStrategy
:Properties配置類HystrixCommandExecutionHook
:HystrixCommand回調函數類HystrixDynamicProperties
:配置信息
共六類插件。其實也可以說是五類,因為HystrixDynamicProperties
嚴格意義上講不算插件,它是給插件提供外部化配置的一個配置類,只是初始化它的時候也恰好支持到了外部化,所以索性也叫做插件吧。
這些插件被HystrixPlugins
管理着,由它統一負責加載和實例化。本文就介紹這幾大插件的作用,以及講述Hystrix是如何管理、加載它們的。
正文
此部分分為兩大塊進行講解:
- SPI接口介紹
- HystrixPlugins詳解
SPI接口介紹
SPI:Service Provider Interface
,是一種服務發現機制,JDK自帶有ServiceLoader
來實現這種機制,當然更為出名的是Spring的SpringFactoriesLoader
(Dubbo也有自己的SPI實現哦),具體實現方式上都大同小異。
另外,這裡所指的Interface
並不強要求必須是接口,比如本文里使用均為抽象類,也是一樣的可以正常使用。
HystrixConcurrencyStrategy
並發相關的策略類。抽象類,用於使用默認實現為系統的並發相關方面定義不同的行為或實現。
public abstract class HystrixConcurrencyStrategy { // ThreadFactory由HystrixThreadPoolKey來完成分組 // 線程均以守護線程形式,線程名為:`hystrix-threadPoolKey.name()-1/2/3/4...` public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { final ThreadFactory threadFactory = getThreadFactory(threadPoolKey); final int dynamicCoreSize = corePoolSize.get(); final int dynamicMaximumSize = maximumPoolSize.get(); if (dynamicCoreSize > dynamicMaximumSize) { return new ThreadPoolExecutor(dynamicCoreSize, dynamicCoreSize, keepAliveTime.get(), unit, workQueue, threadFactory); } else { return new ThreadPoolExecutor(dynamicCoreSize, dynamicMaximumSize, keepAliveTime.get(), unit, workQueue, threadFactory); } } // 配置來自於HystrixThreadPoolProperties,默認值是: // core核心是10,最大值max也是10,keepAliveTimeMinutes=1分鐘 // BlockingQueue因為不能配置,所以參見下面的這個getBlockingQueue方法 public ThreadPoolExecutor getThreadPool(final HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties threadPoolProperties) { ... } public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { // 如果maxQueueSize木值,那就使用同步隊列 -> 木有緩衝區 if (maxQueueSize <= 0) { return new SynchronousQueue<Runnable>(); } else { // 否則使用Linked,隊列大小是maxQueueSize哦(並不是無界的哦) return new LinkedBlockingQueue<Runnable>(maxQueueSize); } } // 給調用者一個機會,然你可以對callback進行包裝一把 // 該方法子啊HystrixContextRunnable、HystrixContextCallable、HystrixContexSchedulerAction // 里均會被調用 public <T> Callable<T> wrapCallable(Callable<T> callable) { return callable; } }
它主要是用於獲取/創建線程池,根據實際配置*(每個線程池的參數是可以不一樣的)創建ThreadPoolExecutor
。另外提供一個可以對Callback進行包裝、裝飾的方法~
HystrixConcurrencyStrategyDefault
這是它的唯一實現,也是默認實現。以單例形式提供服務:
public class HystrixConcurrencyStrategyDefault extends HystrixConcurrencyStrategy { private static HystrixConcurrencyStrategyDefault INSTANCE = new HystrixConcurrencyStrategyDefault(); public static HystrixConcurrencyStrategy getInstance() { return INSTANCE; } ... }
簡單的說,它是個空實現,用於兜底。
HystrixEventNotifier
hystrix命令執行過程中,接收相應的事件通知。
需要注意的是:這個notifier是同步調用的,因此裡頭方法的實現不能太耗時,不然則會阻塞,如果方法太耗時則需要考慮異步到其他線程。
public abstract class HystrixEventNotifier { // 空實現:當任意事件被觸發時,均執行此方法 public void markEvent(HystrixEventType eventType, HystrixCommandKey key) { // do nothing } // 空實現:當使用線程隔離方式,觸發事件時執行此方法 // 注意:如果被拒絕rejected(比如斷路器全開了)、或者短路short-circuited了,那麼此方法是不會被調用的 public void markCommandExecution(HystrixCommandKey key, ExecutionIsolationStrategy isolationStrategy, int duration, List<HystrixEventType> eventsDuringExecution) { // do nothing } }
在HystrixCommand
以及HystrixObservableCommand
調用的時候,都會調用HystrixEventNotifier
來發佈事件,提供給開發者自定義實現,來做指標收集及監控報警。
同樣的,它的默認實現HystrixEventNotifierDefault
屬於空實現,作為兜底使用。
HystrixEventType
Hystrix
事件類型枚舉,並且還提供了分類:
EXCEPTION_PRODUCING_EVENT_TYPES
:異常事件類型。包括:BAD_REQUEST、FALLBACK_FAILURE、FALLBACK_MISSING、FALLBACK_REJECTION
- 注意:
FALLBACK_MISSING
表示木有提供fallabck函數,所以拋出異常哦。但是如果你正常提供了fallabck函數,那就是FALLBACK_SUCCESS
就不屬於異常的
- 注意:
TERMINAL_EVENT_TYPES
:終端事件。SUCCESS、BAD_REQUEST、FALLBACK_SUCCESS、FALLBACK_FAILURE、FALLBACK_REJECTION、FALLBACK_MISSING、RESPONSE_FROM_CACHE、CANCELLED
,也就是遇上這些事件表示處理結束嘍。
這些事件類型和事件分類在Hystrix
處理結果ExecutionResult
里將會有體現~
HystrixMetricsPublisher
抽象類,默認實現工廠方法,用於創建「Metrics Publisher」實例,以獲取指標和其他相關數據。
public abstract class HystrixMetricsPublisher { public HystrixMetricsPublisherCommand getMetricsPublisherForCommand(HystrixCommandKey commandKey, HystrixCommandGroupKey commandGroupKey, HystrixCommandMetrics metrics, HystrixCircuitBreaker circuitBreaker, HystrixCommandProperties properties) { return new HystrixMetricsPublisherCommandDefault(commandKey, commandGroupKey, metrics, circuitBreaker, properties); } public HystrixMetricsPublisherThreadPool getMetricsPublisherForThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolMetrics metrics, HystrixThreadPoolProperties properties) { return new HystrixMetricsPublisherThreadPoolDefault(threadPoolKey, metrics, properties); } public HystrixMetricsPublisherCollapser getMetricsPublisherForCollapser(HystrixCollapserKey collapserKey, HystrixCollapserMetrics metrics, HystrixCollapserProperties properties) { return new HystrixMetricsPublisherCollapserDefault(collapserKey, metrics, properties); } }
三個方法參數都幾乎一模一樣,分別用於創建如上三組接口的實例。需要說明的是:他們的默認實現XXXDefault
均為空實現。
該接口和Hystrix
的監控密切相關,你可以自己將metrics指標信息落地存儲,然後集成到其它指標監控系統。值得注意的是:Micrometer
對它便有集成,詳見MicrometerMetricsPublisher
就是一種指標採集的實現。
說明:指標、監控是個較大的話題,也是將後指標監控專題的重中之重。
HystrixPropertiesStrategy
創建HystrixCommandProperties、HystrixThreadPoolProperties、HystrixCollapserProperties、HystrixTimerThreadPoolProperties
實例,方便在Hystrix
的各方各面上使用。
public abstract class HystrixPropertiesStrategy { public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) { return new HystrixPropertiesCommandDefault(commandKey, builder); } public HystrixThreadPoolProperties getThreadPoolProperties(HystrixThreadPoolKey threadPoolKey, HystrixThreadPoolProperties.Setter builder) { return new HystrixPropertiesThreadPoolDefault(threadPoolKey, builder); } public HystrixCollapserProperties getCollapserProperties(HystrixCollapserKey collapserKey, HystrixCollapserProperties.Setter builder) { return new HystrixPropertiesCollapserDefault(collapserKey, builder); } public HystrixTimerThreadPoolProperties getTimerThreadPoolProperties() { return new HystrixPropertiesTimerThreadPoolDefault(); } }
這個策略接口比較簡單,並且相關xxxProperties
類在上文有詳細解釋,本處就不再鰲訴。
HystrixCommandExecutionHook
在HystrixCommand
的不同聲明周期的回調接口,默認實現是無操作。
public abstract class HystrixCommandExecutionHook { // 命令開始執行前調用 public <T> void onStart(HystrixInvokable<T> commandInstance) { //do nothing by default } // 發送數據時調用 public <T> T onEmit(HystrixInvokable<T> commandInstance, T value) { return value; //by default, just pass through } public <T> Exception onError(HystrixInvokable<T> commandInstance, FailureType failureType, Exception e) { return e; //by default, just pass through } public <T> void onSuccess(HystrixInvokable<T> commandInstance) { //do nothing by default } .... // 省略其它方法,實在太多了 }
hystrix在執行命令的各個節點會調用HystrixCommandExecutionHook
,這些都是留給調用者的鉤子方法。
HystrixCommandExecutionHook
提供了對HystrixCommand
及HystrixObservableCommand
生命周期的鉤子方法,開發者可以自定義實現,做一些額外的處理,比如日誌打印、覆蓋response、更改線程狀態等等。
HystrixPlugins詳解
本類是Hystrix
內置插件機制的實現。上面介紹了幾個SPI接口/抽象類,但是你會發現Default默認實現均是空實現,因為這些均是Hystrix留給使用者的鉤子,交給你去定製,而這種定製化的實現就是通過HystrixPlugins
來做的。
初始化HystrixDynamicProperties
實際上HystrixDynamicProperties
也是HystrixPlugins
所管理的六大SPI接口之一,但是它稍有點特殊,它亦是給其它5個SPI接口提供配置的基礎,所以它伴隨着HystrixPlugins
的初始化而初始化,並且它只能被初始化一次(其它的均可多次)。
// 它自己以單例形式展示 public class HystrixPlugins { // 獲取HystrixPlugins 單例 public static HystrixPlugins getInstance() { return LazyHolder.INSTANCE; } // =======================它的成員屬性們======================= private final ClassLoader classLoader; // 下面這幾個屬性均是pachage的訪問級別~~~~~~~~~~~~~~~ // 使用AtomicReference來保證原子性 final AtomicReference<HystrixEventNotifier> notifier = new AtomicReference<HystrixEventNotifier>(); final AtomicReference<HystrixConcurrencyStrategy> concurrencyStrategy = new AtomicReference<HystrixConcurrencyStrategy>(); final AtomicReference<HystrixMetricsPublisher> metricsPublisher = new AtomicReference<HystrixMetricsPublisher>(); final AtomicReference<HystrixPropertiesStrategy> propertiesFactory = new AtomicReference<HystrixPropertiesStrategy>(); final AtomicReference<HystrixCommandExecutionHook> commandExecutionHook = new AtomicReference<HystrixCommandExecutionHook>(); private final HystrixDynamicProperties dynamicProperties; // 初始化的時候(只會進行一次),給成員屬性賦值 private HystrixPlugins(ClassLoader classLoader, LoggerSupplier logSupplier) { //This will load Archaius if its in the classpath. this.classLoader = classLoader; dynamicProperties = resolveDynamicProperties(classLoader, logSupplier); } }
在HystrixPlugins
初始化的時候,會給dynamicProperties
賦值,獲取它的實例優先級順序是這樣的:
- 從System系統屬性里找
"hystrix.plugin." + classSimpleName + ".implementation"
,也就是hystrix.plugin.HystrixDynamicProperties.implementation
這個key。若存在就Class.forName() - 通過
ServiceLoader
的SPI方式加載HystrixDynamicProperties
的實現類 - 通過
HystrixArchaiusHelper.createArchaiusDynamicProperties()
創建一個實現類:- 讓
hystrix-plugins.properties
和hystrix-plugins-環境名.properties
里的屬性都加入到全局配置中 - 從類路徑下
Class.forName("com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius")
- 這樣Hystrix的屬性就和全局屬性關聯上了嘍,並且使用
Archaius
動態管理
- 讓
- 最後,若都還沒找到,那就兜底使用
HystrixDynamicPropertiesSystemProperties
:使用System屬性(一般情況下,到第三步肯定就能完成實例化了)
實際生產中,大概率會在第3步完成初始化,也就是HystrixDynamicPropertiesArchaius
實例,這樣的話hystrix-plugin.properties
里的屬性也都是會生效的哦。
初始化其它SPI接口
其它SPI接口並不會主動初始化,而是按需被調用的時候完成查找、初始化動作。
下面以獲取HystrixEventNotifier
實例為例,其它的接口獲取邏輯一毛一樣:
HystrixPlugins: public void registerEventNotifier(HystrixEventNotifier impl) { if (!notifier.compareAndSet(null, impl)) { throw new IllegalStateException("Another strategy was already registered."); } } public HystrixEventNotifier getEventNotifier() { // 由此可見,手動註冊進來的優先級是最高的 if (notifier.get() == null) { Object impl = getPluginImplementation(HystrixEventNotifier.class); // 如果還找到,就使用Defualt默認實例:空實現 if (impl == null) { notifier.compareAndSet(null, HystrixEventNotifierDefault.getInstance()); } else { notifier.compareAndSet(null, (HystrixEventNotifier) impl); } } return notifier.get(); } // 從 private <T> T getPluginImplementation(Class<T> pluginClass) { T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties); if (p != null) return p; return findService(pluginClass, classLoader); }
查找一個SPI接口的實現類的流程如下(優先級由高到低):
- 手動
register
進來的 - 從上一步初始化好的
dynamicProperties
里找key為:hystrix.plugin." + classSimpleName + ".implementation
屬性作為實現類 - 使用
ServiceLoader
的SPI方式查找實現類 - 使用Default實現(空實現)
由於插件的實現類一般不可能動態改變,所以它一般有個最佳實踐:採用hystrix.plugin." + classSimpleName + ".implementation
的方式把它各插件的實現類全類名統一配置在hystrix-plugin.properties
文件里,這也方便了管理。相信這也便是Hystrix
設計一個名hystrix-plugin
的文件的唯一目的吧(因為剛好它裏面的屬性是不具有動態性的,完全符合條件)~
使用示例
為了看到效果,自定義一個SPI實現類:
public class MyHystrixMetricsPublisher extends HystrixMetricsPublisher { public MyHystrixMetricsPublisher() { System.out.println("MyHystrixMetricsPublisher被實例化了..."); } }
在hystrix-plugins.properties
里配置如下:
hystrix.plugin.HystrixMetricsPublisher.implementation=com.yourbatman.hystrix.MyHystrixMetricsPublisher
書寫測試程序:
@Test public void fun1() { HystrixPlugins instance = HystrixPlugins.getInstance(); HystrixDynamicProperties dynamicProperties = instance.getDynamicProperties(); System.out.println(dynamicProperties.getString("name", null).get()); System.out.println("==========================================="); // 類型 System.out.println(dynamicProperties.getClass()); System.out.println(instance.getMetricsPublisher().getClass()); System.out.println(instance.getEventNotifier().getClass()); System.out.println(instance.getConcurrencyStrategy().getClass()); }
運行程序,控制台輸出:
YourBatman =========================================== class com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius MyHystrixMetricsPublisher被實例化了... class com.yourbatman.hystrix.MyHystrixMetricsPublisher class com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifierDefault class com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault
說明:MyHystrixMetricsPublisher
的配置項放在config.properties
也是有效的,但是放在hystrix-plugins.properties
里乃最佳實踐(因為它還有一個優點:hystrix-plugins-環境名稱.properties
這個配置相同key的優先級更高,所以非常方便你進行多環境測試使用)。
總結
關於Netflix Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解就介紹到這了,這裡最為重要的我認為是對後續自定製監控模塊打好基礎,為擴展做好準備。
同時本文也能告訴我們,一個優秀的框架是需要具備良好的擴展性,以及預留足夠多的鉤子程序的,這樣才能有更多人參與進來,流行度才會鋪開。