[享学Netflix] 十七、Netflix Hystrix属性抽象以及和Archaius整合实现配置外部化、动态化

学习设计模式并不是学习新的一门语言,而是建立一种交流的共同语言和词汇,在方案设计时方便沟通,同时也帮助人们从更抽象的层次去分析问题的本质,而不被一些实现的细枝末节所困扰。

–> 返回专栏总目录 <–
代码下载地址: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,特别强调了属性的动态性。

扫描二维码关注公众号,回复: 9428365 查看本文章

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接口、自己抽象动态属性接口就是为了不绑定具体实现,这是一种良好的设计思想,是可以肯定和学习的。
分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了312 篇原创文章 · 获赞 478 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104511940