­

[享学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自己的一个属性。

说明:自己抽象出一个接口,而并不直接使用ArchaiusProperty抽象,是为了降低耦合度,减少强依赖。同时也提高了自身的稳定性


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的两个实现类:HystrixDynamicPropertiesSystemPropertiesHystrixDynamicPropertiesArchaius内部。

这就是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

它是一个工具类,用于完成HystrixArchaius的集成,它内部会使用一些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接口、自己抽象动态属性接口就是为了不绑定具体实现,这是一种良好的设计思想,是可以肯定和学习的。