[享學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中對此提供了兩種實現:
HystrixDynamicPropertiesSystemPropertiesHystrixDynamicPropertiesArchaius
當然後續你還會看到機遇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為例,進行闡述
- 先載入
app.application文件 - 在獲取到部署環境值,若不為空的話(比如環境名為test),就載入
app-test.application這個文件,並且若出現相同key,後者覆蓋前者- 環境是由
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也是可行的~
- 在Spring Boot環境其實你可以把它改為
ConfigurationManager管理的是一個ConcurrentCompositeConfiguration組合配置,而這個組合配置就含有DynamicURLConfiguration( + 系統屬性)
綜上可知,這就是為何默認config.properties文件的屬性是具有動態性的原因。而hystrix-plugins.properties它是被ConfigurationManager#loadCascadedPropertiesFromResources()載入的,所以僅僅只是屬性生效而已,並不具有動態性。
實際生產中,名hystrix-plugins.properties的屬性文件並不是給你配置其它屬性的,從命名中你就知道:它給你配置插件用,也就是SPI使用的,後面會再次提到它。
再次強調:生產環境,請勿在名為
hystrix-plugins.properties的文件里配置業務屬性,避免不必要的干擾
總結
關於Netflix Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化就介紹到這了,本文旨在讓你認識到Hystrix如何管理器屬性Property,以及和Archaius整合使得具有動態化的。
從源碼處其實能看出很多有趣的小設計:自己抽象Property介面、自己抽象動態屬性介面就是為了不綁定具體實現,這是一種良好的設計思想,是可以肯定和學習的。

