[享學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提供了對HystrixCommandHystrixObservableCommand生命周期的鉤子方法,開發者可以自定義實現,做一些額外的處理,比如日誌打印、覆蓋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賦值,獲取它的實例優先級順序是這樣的:

  1. 從System系統屬性里找"hystrix.plugin." + classSimpleName + ".implementation",也就是hystrix.plugin.HystrixDynamicProperties.implementation這個key。若存在就Class.forName()
  2. 通過ServiceLoader的SPI方式加載HystrixDynamicProperties的實現類
  3. 通過HystrixArchaiusHelper.createArchaiusDynamicProperties()創建一個實現類:
    1. hystrix-plugins.propertieshystrix-plugins-環境名.properties里的屬性都加入到全局配置中
    2. 從類路徑下Class.forName("com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius")
    3. 這樣Hystrix的屬性就和全局屬性關聯上了嘍,並且使用Archaius動態管理
  4. 最後,若都還沒找到,那就兜底使用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接口的實現類的流程如下(優先級由高到低):

  1. 手動register進來的
  2. 從上一步初始化好的dynamicProperties里找key為:hystrix.plugin." + classSimpleName + ".implementation屬性作為實現類
  3. 使用ServiceLoader的SPI方式查找實現類
  4. 使用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詳解就介紹到這了,這裡最為重要的我認為是對後續自定製監控模塊打好基礎,為擴展做好準備。

同時本文也能告訴我們,一個優秀的框架是需要具備良好的擴展性,以及預留足夠多的鉤子程序的,這樣才能有更多人參與進來,流行度才會鋪開。