[享學Netflix] 十七、Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化

  • 2020 年 3 月 18 日
  • 筆記

學習設計模式並不是學習新的一門語言,而是建立一種交流的共同語言和辭彙,在方案設計時方便溝通,同時也幫助人們從更抽象的層次去分析問題的本質,而不被一些實現的細枝末節所困擾。

程式碼下載地址:https://github.com/f641385712/netflix-learning

目錄

前言

了解了Hystrix的基本情況後,接下來將逐步深入到它的使用以及原理上。

作為一個流行的開源庫,擴展性、設計的彈性是必不可少的,而所謂彈性一般都通過外部化配置來實現。Hystrix也不例外,它支援非常非常多的配置選項,對於多如牛毛的配置項,管理起來、統一實現動態化亦是一個必備的設計要素。

本文將介紹Hystrix的屬性抽象,以及和Archaius的整合來實現配置的外部化、以及動態化~


正文

我們知道Archaius有個屬性抽象:com.netflix.config.Property介面用於表示一個屬性。而Hystrix並沒有沿用於此,而是自己抽象了一個HystrixProperty<T>介面,用於表示Hystrix自己的一個屬性。

說明:自己抽象出一個介面,而並不直接使用Archaius的Property抽象,是為了降低耦合度,減少強依賴。同時也提高了自身的穩定性


HystrixProperty

它是表示屬性值的通用介面,以便Hystrix可以使用屬性,而不需要綁定到任何特定的支援實現。

public interface HystrixProperty<T> {  	public T get();  }

對於如何得到一個HystrixProperty實例,它提供了一個內部Factory類來做:

HystrixProperty:    	public static class Factory {  		// 最普通的實現:該屬性木有動態性哦  		public static <T> HystrixProperty<T> asProperty(final T value) {  			return () -> value;  		}      		//==========下面方法便是和Archaius的集成,讓屬性具有動態性==========  		// 但是請注意:下面的DynamicIntegerProperty等等API並不是Archaius的  		// 而屬於HystrixPropertiesChainedArchaiusProperty  		// 所以其實可以看到,默認實現是使用的Archaius哦~~~~          public static HystrixProperty<Integer> asProperty(final DynamicIntegerProperty value) {              return () -> value.get();          }          ... // 省略DynamicLongProperty/DynamicStringProperty...等其它類型      		public static <T> HystrixProperty<T> asProperty(final HystrixProperty<T> value, final T defaultValue) { ... }  		// 只要第一個不為null,就取出值  		public static <T> HystrixProperty<T> asProperty(final HystrixProperty<T>... values) { ... }  		public static <T> HystrixProperty<T> nullProperty() {  			return () -> null;  		}  	}

可以看到,創建一個HystrixProperty實例完全可以由Factory來實現,從而解耦對第三方的強制依賴。它還有個子介面HystrixDynamicProperty,特彆強調了屬性的動態性。


HystrixDynamicProperty

通用介面表示一個動態屬性值,以便Hystrix可以使用屬性,而不綁定到任何特定的支援實現。

public interface HystrixDynamicProperty<T> extends HystrixProperty<T>{      public String getName();      // 如果屬性有被更改,會執行此回調函數      public void addCallback(Runnable callback);  }

所有的HystrixDynamicProperty實現類都是以匿名的形式呈現:分別分部在HystrixDynamicProperties的兩個實現類:HystrixDynamicPropertiesSystemProperties和HystrixDynamicPropertiesArchaius內部。

這就是Hystrix的屬性抽象,內容蠻簡單的,可以粗暴的理解為就是薄薄的一層代理而已,具體實現還得接著看下文。


和Archaius整合

根據前面所學,Archaius是一個優秀的配置管理庫,同作為自家產品,想要有外部化、動態配置的能力,沒有理由不用它嘛。

當然嘍,這並不是必須的,Hystrix它並不強耦合Archaius,而是選擇了一個SPI介面:HystrixDynamicProperties用來增加隔離性,以及可擴展性。


HystrixDynamicProperties

它是一個hystrix plugin,也就是SPI介面:允許你從不同的源里處獲得configuration,並不限定實現必須是Archaius。

public interface HystrixDynamicProperties {    	public HystrixDynamicProperty<String> getString(String name, String fallback);  	public HystrixDynamicProperty<Integer> getInteger(String name, Integer fallback);  	public HystrixDynamicProperty<Long> getLong(String name, Long fallback);  	public HystrixDynamicProperty<Boolean> getBoolean(String name, Boolean fallback);  }

同時,為了保持調用方法的統一性,它提供了一個工具類:

HystrixDynamicProperties:    	public static class Util {    		// 這樣出口方法可以保持統一:都叫getProperty  		// 需要注意的是:實際它就是簡單的delegate.getString/getInteger/...  		// 所以它僅支援上述的四種類型,否則拋出IllegalStateException異常          public static <T> HystrixDynamicProperty<T> getProperty(HystrixDynamicProperties delegate, String name, T fallback, Class<T> type) {              return (HystrixDynamicProperty<T>) doProperty(properties, name, fallback, type);          }  	}

在實際獲取屬性中,推薦使用Util#getProperty()方法哦~。

既然它是個SPI,那必然有具體實現方式。Hystrix中對此提供了兩種實現:

  • HystrixDynamicPropertiesSystemProperties
  • HystrixDynamicPropertiesArchaius

當然後續你還會看到機遇ServiceLoader的實現~


HystrixDynamicPropertiesSystemProperties

顧名思義,這些屬性全部來自於System.getProperty(),所以天然具有動態性,但是它有個很大缺點是:無法執行回調callback,並且還無法使用外部化配置。

HystrixDynamicPropertiesSystemProperties是單例方式提供服務,具體實現程式碼非常簡單,無非是System.getProperty(xxx)而已嘛,因此本處省略源碼部分。


HystrixDynamicPropertiesArchaius

底層是用com.netflix.config.PropertyWrapper來實現屬性的動態性。同時Javadoc里也說明了:只要本類在classpath下,那最終就會使用它作為HystrixDynamicProperties SPI的實現,Archaius被載入集成。

public class HystrixDynamicPropertiesArchaius implements HystrixDynamicProperties {        @Override      public HystrixDynamicProperty<String> getString(String name, String fallback) {          return new StringDynamicProperty(name, fallback);      }      ...      // ========基於PropertyWrapper的實現=======      private abstract static class ArchaiusDynamicProperty<T>  extends PropertyWrapper<T> implements HystrixDynamicProperty<T> {            protected ArchaiusDynamicProperty(String propName, T defaultValue) {              super(propName, defaultValue);          }        	// 該方法是HystrixProperty介面的          @Override          public T get() {              return getValue();          }      }    	// ===========具體類型的實現==========  	private static class StringDynamicProperty extends ArchaiusDynamicProperty<String> {            protected StringDynamicProperty(String propName, String defaultValue) {              super(propName, defaultValue);          }    		// 動態獲取屬性值          @Override          public String getValue() {              return prop.getString(defaultValue);          }    	}  	...  }

關於SPI實現的載入,可參見下面這個幫助類:HystrixArchaiusHelper的處理。另外,竟然是SPI,那一般會使用到ServiceLoader等載入機制,後面你就能看到Hystrix也有相關的處理方式。


HystrixArchaiusHelper

它是一個工具類,用於完成Hystrix和Archaius的集成,它內部會使用一些Archaius的核心API:

class HystrixArchaiusHelper {    	// 懶載入  	// 對於那些在類路徑中有Archaius **但選擇不使用它的類**  	// 要保持類裝入最少。所以按需載入  	// ConfigurationManager是Archaius的核心API      private static class LazyHolder {        	// Method方法:此方法用於載入xxx.proerties資源,下面附帶解釋一把          private final static Method loadCascadedPropertiesFromResources;          private final static String CONFIG_MANAGER_CLASS = "com.netflix.config.ConfigurationManager";        	// 靜態程式碼塊,給屬性賦值          static {              Method load = null;              try {                  Class<?> configManager = Class.forName(CONFIG_MANAGER_CLASS);                  load = configManager.getMethod("loadCascadedPropertiesFromResources", String.class);              } catch (Exception e) { }              loadCascadedPropertiesFromResources = load;          }      }      // 判斷:Archaius的1.x版本是否已經準備好了      static boolean isArchaiusV1Available() {          return LazyHolder.loadCascadedPropertiesFromResources != null;      }      // 使用這個Method,載入內容到Configuration里來      static void loadCascadedPropertiesFromResources(String name) {      	if (isArchaiusV1Available()) {      		LazyHolder.loadCascadedPropertiesFromResources.invoke(null, name);      	}      }  }

以上程式碼主要是從classpath里找到ConfigurationManager#loadCascadedPropertiesFromResources(configName)這個method,該method負責對屬性資源進行載入:

ConfigurationManager:    	// Cascaded:往下的,傾瀉; 流注  	public static void loadCascadedPropertiesFromResources(String configName) throws IOException {  		Properties props = loadCascadedProperties(configName);  		...  		ConfigurationUtils.loadProperties(props, instance);  	}

這個方法會根據configName去找到對應的Properties文件,具體邏輯步驟如下:

說明:此處以configName的值等於app為例,進行闡述

  1. 先載入app.application文件
  2. 在獲取到部署環境值,若不為空的話(比如環境名為test),就載入app-test.application這個文件,並且若出現相同key,後者覆蓋前者
    1. 環境是由DeploymentContext#getDeploymentEnvironment() 來決定的,可以為null

這就是這個Method的作用:用於載入屬性文件內容到Configuration里。同時會提供一個方法,用於創建一個HystrixDynamicProperties實例(當然是基於archaius的):

HystrixArchaiusHelper:        static HystrixDynamicProperties createArchaiusDynamicProperties() {          if (isArchaiusV1Available()) {              loadCascadedPropertiesFromResources("hystrix-plugins");              try {              	// HystrixDynamicPropertiesArchaius的全類名                  Class<?> defaultProperties = Class.forName("com.netflix.hystrix.strategy.properties.archaius" + ".HystrixDynamicPropertiesArchaius");                  return (HystrixDynamicProperties) defaultProperties.newInstance();            	} ...          }          // Fallback to System properties.          return null;      }

該方法會創建一個HystrixDynamicPropertiesArchaius實例,並且載入名為hystrix-plugins.properties、hystrix-plugins-環境名.properties的屬性文件到全局配置里。

綜上,確切的說:Hystrix是通過HystrixDynamicPropertiesArchaius完成和Archaius的整合的。


使用示例

@Test  public void fun1() throws InterruptedException {      HystrixPlugins instance = HystrixPlugins.getInstance();        HystrixDynamicProperties dynamicProperties = instance.getDynamicProperties();      System.out.println(dynamicProperties.getClass());        // dynamicProperties.getString()      HystrixDynamicProperty<String> nameProperty = HystrixDynamicProperties.Util.getProperty(dynamicProperties, "name", "defaultValue", String.class);      nameProperty.addCallback(() -> {          String name = nameProperty.getName(); // 屬性名          System.out.println("屬性" + name + "發生了變更");      });      System.out.println(nameProperty.get());        // hold住主執行緒      while (true) {          TimeUnit.SECONDS.sleep(60); // 因為poll輪詢默認是60秒check一次          System.out.println(nameProperty.get());      }  }

準備一個屬性文件,名為:hystrix-plugins.properties:

name=YourBatman

運行程式,控制台輸出:

YourBatman  YourBatman  YourBatman  YourBatman  ...

what?竟然沒有動態化???


為何動態化沒生效?

例子中,要想它生效,其實很簡單:把它寫進名為:config.properties文件里,即可動態化了。

再次運行程式,控制台輸出:

class com.netflix.hystrix.strategy.properties.archaius.HystrixDynamicPropertiesArchaius  YourBatman  屬性name發生了變更  YourBatman-Changed

表面上看,僅僅是文件名不同,效果卻大不一樣。而實際上背後是它的載入原理,這在前面Archaius正解里有詳細描述,這裡簡單複習一下:

  • 在Archaius中,僅僅是PolledConfigurationSource的配置元,才會有動態性
  • URLConfigurationSource是該介面的一個實現類,所以關聯到的屬性文件均具有動態性。而它默認關聯的文件名是:config.properties
    • 在Spring Boot環境其實你可以把它改為application.properties也是可行的~
  • ConfigurationManager管理的是一個ConcurrentCompositeConfiguration組合配置,而這個組合配置就含有DynamicURLConfiguration( + 系統屬性)

綜上可知,這就是為何默認config.properties文件的屬性是具有動態性的原因。而hystrix-plugins.properties它是被ConfigurationManager#loadCascadedPropertiesFromResources()載入的,所以僅僅只是屬性生效而已,並不具有動態性。

實際生產中,名hystrix-plugins.properties的屬性文件並不是給你配置其它屬性的,從命名中你就知道:它給你配置插件用,也就是SPI使用的,後面會再次提到它。

再次強調:生產環境,請勿在名為hystrix-plugins.properties的文件里配置業務屬性,避免不必要的干擾


總結

關於Netflix Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化就介紹到這了,本文旨在讓你認識到Hystrix如何管理器屬性Property,以及和Archaius整合使得具有動態化的。

從源碼處其實能看出很多有趣的小設計:自己抽象Property介面、自己抽象動態屬性介面就是為了不綁定具體實現,這是一種良好的設計思想,是可以肯定和學習的。

Tags: