netflix全家桶的代码不是很优雅,各种单例,静态方法调用,hystrix是我读过最屎的代码,兼职无力吐槽,恶心的一批。和其他开源项目项目,代码写的真的是一塌糊涂。

application.yml Hystrix 配置如何被加载

配置如何注入 Hystrix

注入流程

Hystrix熔断可以通过在application.yml配置相关参数,但是和常见的在application.yml的配置参数不同,hystrix的配置参数没有对应的@ConfigurationProperties配置类。(在spring boot体系下,application.yml配置都会对应一个@ConfigurationProperties配置类。)

在我的一番寻找下,发现了application.yml配置如何被hystrix加载的逻辑。

Spring Cloud Hystrix依赖了spring-cloud-netflix-archaius,有一个自动配置类ArchaiusAutoConfiguration

其中configurableEnvironmentConfiguration方法使用ConfigurableEnvironmentConfiguration包装了ConfigurableEnvironment,前者是对apache common包的AbstractConfiguration类的实现,后者是spring environment。在spring boot内,application.yml配置都会被放在environment对象内。

@Bean
public static ConfigurableEnvironmentConfiguration configurableEnvironmentConfiguration(ConfigurableEnvironment env, ApplicationContext context) {
	Map<String, AbstractConfiguration> abstractConfigurationMap = context.getBeansOfType(AbstractConfiguration.class);
	List<AbstractConfiguration> externalConfigurations = new ArrayList<>(abstractConfigurationMap.values());
	ConfigurableEnvironmentConfiguration envConfig = new ConfigurableEnvironmentConfiguration(env);
	// configureArchaius 方法很重要
	configureArchaius(envConfig, env, externalConfigurations);
	return envConfig;
}

configureArchaius(envConfig, env, externalConfigurations);,这个方法很重要, hystrix能够读取application.yml的原因就是在这个方法里做到的。

protected static void configureArchaius(ConfigurableEnvironmentConfiguration envConfig, ConfigurableEnvironment env, List<AbstractConfiguration> externalConfigurations) {
	if (initialized.compareAndSet(false, true)) {
	    ConcurrentCompositeConfiguration config = new ConcurrentCompositeConfiguration();
		... // 忽略 hystrix 其他配置来源,只关注 spring environment
		config.addConfiguration(envConfig,
				ConfigurableEnvironmentConfiguration.class.getSimpleName());
		...
		addArchaiusConfiguration(config);
	}
	...
}

configureArchaius方法内,构建了一个ConcurrentCompositeConfiguration对象,添加了hystrix不同来源的配置,我们只关注spring environment来源的配置。最后调用addArchaiusConfiguration方法。

private static void addArchaiusConfiguration(ConcurrentCompositeConfiguration config) {
	if (ConfigurationManager.isConfigurationInstalled()) {
		...
	}
	else {
		ConfigurationManager.install(config);
	}
}

addArchaiusConfiguration方法内,调用了ConfigurationManager.install 方法,配置config

这就是为什么说hystrix代码屎的原因,配置是通过静态方法注入的,正常的情况下谁会想到配置在这里被注入!!

ConfigurationManager.install方法内,通过DynamicPropertyFactory.initWithConfigurationSource(config)方法,初始化DynamicPropertyFactory内部配置。(这里又是静态方法初始化,一层嵌一层,还都是静态方法调用,巨辣鸡)

public static synchronized void install(AbstractConfiguration config) throws IllegalStateException {
    if (!customConfigurationInstalled) {
        setDirect(config);
        if (DynamicPropertyFactory.getBackingConfigurationSource() != config) {                
            // 重要代码
            DynamicPropertyFactory.initWithConfigurationSource(config);
        }
    } else {
        throw new IllegalStateException("A non-default configuration is already installed");
    }
}

initWithConfigurationSource方法内有一个setDirect方法,在setDirect方法内调用了DynamicProperty.registerWithDynamicPropertySupport(support)方法,初始化DynamicProperty

public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) {
    synchronized (ConfigurationManager.class) {
        ...
        setDirect(dynamicPropertySupport);
        return instance;
    }
}

static void setDirect(DynamicPropertySupport support) {
    synchronized (ConfigurationManager.class) {
        config = support;
        DynamicProperty.registerWithDynamicPropertySupport(support);
        initializedWithDefaultConfig = false;
    }
}

DynamicProperty.registerWithDynamicPropertySupport方法内,初始化了dynamicPropertySupportImpl对象,该对象保存了hystrix所有不同来源的配置。

static synchronized void initialize(DynamicPropertySupport config) {
    dynamicPropertySupportImpl = config;
    config.addConfigurationListener(new DynamicPropertyListener());
    updateAllProperties();
}


static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {
    initialize(config);
}

总结

上面的流程分析了spring boot application.yml配置如何注入到hystrix内。总结其实很简单,在自动配置类ArchaiusAutoConfiguration内,获取spring environment,通过一系列hystrix相关类的static方法,注入到DynamicProperty对象内,由dynamicPropertySupportImpl持有。

SpringBoot Application start --> Bean Initialization -->

ArchaiusAutoConfiguration configurableEnvironmentConfiguration -->

ConfigurationManager.install(config) -->

DynamicPropertyFactory.initWithConfigurationSource(config) -->

DynamicPropertyFactory.initWithConfigurationSource -->

DynamicPropertyFactory.setDirect -->

DynamicProperty.registerWithDynamicPropertySupport(support)

配置如何被读取

此小节只关注配置hystrix如何读取配置,hystrix核心流程不涉及,所以直接从配置相关的流程开始分析。

hystrix内,针对方法的熔断配置,最后都会对应一个HystrixCommand对象,所以从HystrixCommand入手。

HystrixCommand继承了AbstractCommand,后者在构造器内初始化hystrix properties

protected AbstractCommand(HystrixCommandGroupKey group, HystrixCommandKey key, HystrixThreadPoolKey threadPoolKey, HystrixCircuitBreaker circuitBreaker, HystrixThreadPool threadPool,
        HystrixCommandProperties.Setter commandPropertiesDefaults, HystrixThreadPoolProperties.Setter threadPoolPropertiesDefaults,
        HystrixCommandMetrics metrics, TryableSemaphore fallbackSemaphore, TryableSemaphore executionSemaphore,
        HystrixPropertiesStrategy propertiesStrategy, HystrixCommandExecutionHook executionHook) {

    ...
    this.properties = initCommandProperties(this.commandKey, propertiesStrategy, commandPropertiesDefaults);
    ...
}

在构造器内通过initCommandProperties初始化hystrix properties。在initCommandProperties方法中,会调用HystrixPropertiesFactory.getCommandProperties获取command properties

private static HystrixCommandProperties initCommandProperties(HystrixCommandKey commandKey, HystrixPropertiesStrategy propertiesStrategy, HystrixCommandProperties.Setter commandPropertiesDefaults) {
    if (propertiesStrategy == null) {
        return HystrixPropertiesFactory.getCommandProperties(commandKey, commandPropertiesDefaults);
    } else {
        // used for unit testing
        return propertiesStrategy.getCommandProperties(commandKey, commandPropertiesDefaults);
    }
}

getCommandProperties方法内,会调用HystrixPropertiesStrategy对象getCommandProperties方法。返回一个HystrixCommandProperties对象。

public static HystrixCommandProperties getCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) {
    ...
    if (cacheKey != null) {
        ...
        if (properties != null) {
            return properties;
        } else {
            ...
            properties = hystrixPropertiesStrategy.getCommandProperties(key, builder);
            ...
        }
    }
    ...
}

HystrixPropertiesStrategy.getCommandProperties方法,返回HystrixCommandProperties对象,该对象继承HystrixCommandProperties类,后者包含了所有的hystrix配置内容。

public HystrixCommandProperties getCommandProperties(HystrixCommandKey commandKey, HystrixCommandProperties.Setter builder) {
    return new HystrixPropertiesCommandDefault(commandKey, builder);
}

HystrixCommandProperties构造器内会初始化各种hystrix 配置值。

protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
    this.key = key;
    this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled);
    ...
}

这些配置都通过getProperty方法获取配置值。从getProperty方法可以看到,hystrix command 如果配置了commandKey,会读取commandKey 对应的配置,默认还会加上一个default的配置。

这段代码解释了为什么我们配置default这个commandKey能够让所有的hystrix生效。

private static HystrixProperty<Integer> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) {
    return forInteger()
            .add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue)
            .add(propertyPrefix + ".command.default." + instanceProperty, defaultValue)
            .build();
}

forInteger方法会返回一个ChainBuilder对象,随后调用此对象的add方法。又要吐槽一下hystrix代码,这里一个add方法,正常谁会想到add里做了特殊的处理,配置就是在add方法里读取的。

public ChainBuilder<T> add(String name, T defaultValue) {
    properties.add(getDynamicProperty(name, defaultValue, getType()));
    return this;
}

add方法内,会调用getDynamicProperty方法,想不到吧! add方法 里面还有特殊的处理逻辑。

private static <T> HystrixDynamicProperty<T> getDynamicProperty(String propName, T defaultValue, Class<T> type) {
    HystrixDynamicProperties properties = HystrixPlugins.getInstance().getDynamicProperties();
    HystrixDynamicProperty<T> p = HystrixDynamicProperties.Util.getProperty(properties, propName, defaultValue, type);
    return p;
}

HystrixPlugins.getInstance().getDynamicProperties()方法内部又是一堆逻辑,里面会通过SPI方式初始化HystrixDynamicPropertiesArchaius对象并返回。该对象继承了HystrixDynamicProperties

HystrixDynamicProperties.Util.getProperty方法内,通过代理的方式,实际根据返回类型调用对应的HystrixDynamicPropertiesArchaius.getXXX方法。

public static <T> HystrixDynamicProperty<T> getProperty(
        HystrixDynamicProperties properties, String name, T fallback, Class<T> type) {
    return (HystrixDynamicProperty<T>) doProperty(properties, name, fallback, type);
}

private static HystrixDynamicProperty<?> doProperty(
        HystrixDynamicProperties delegate, 
        String name, Object fallback, Class<?> type) {
    if(type == String.class) {
        return delegate.getString(name, (String) fallback);
    }
    else if (type == Integer.class) {
        return delegate.getInteger(name, (Integer) fallback);
    }
    else if (type == Long.class) {
        return delegate.getLong(name, (Long) fallback);
    }
    else if (type == Boolean.class) {
        return delegate.getBoolean(name, (Boolean) fallback);
    }
    throw new IllegalStateException();
}

getLong为例,以下代码为HystrixDynamicPropertiesArchaius.getLong代码。方法内返回一个LongDynamicProperty对象。

@Override
public HystrixDynamicProperty<Long> getLong(String name, Long fallback) {
    return new LongDynamicProperty(name, fallback);
}

根据类型不同,类似LongDynamicProperty的类有多种,它们都继承自PropertyWrapper类。PropertyWrapper类构造器内,调用DynamicProperty.getInstance,获取propName对应的值。

protected PropertyWrapper(String propName, V defaultValue) {
    this.prop = DynamicProperty.getInstance(propName);
    this.defaultValue = defaultValue;
    ...
}

getInstance方法内,会创建DynamicProperty对象。

public static DynamicProperty getInstance(String propName) {
    ...
    DynamicProperty prop = ALL_PROPS.get(propName);
    if (prop == null) {
        prop = new DynamicProperty(propName);
        DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);
        if (oldProp != null) {
            prop = oldProp;
        }
    }
    return prop;
}

DynamicProperty构造器内,会调用updateValue方法,方法内部会调用dynamicPropertySupportImpl.getString(propName)获取配置值。

上一节里分析到Hystrix初始化配置时,配置会由DynamicPropertydynamicPropertySupportImpl对象持有。所以从application.yml里注入的配置在这里被读取了。整个配置读取流程走通了。

private DynamicProperty(String propName) {
    this.propName = propName;
    updateValue();
}
private boolean updateValue() {
    String newValue;
    try {
        if (dynamicPropertySupportImpl != null) {
            newValue = dynamicPropertySupportImpl.getString(propName);
        } else {
            return false;
        }
    }
    ...
}

总结

application.yml配置先通过spring bean构建时期,注入到DynamicProperty对象内,hystrix初始化时会构建HystrixCommandProperties对象,内部会初始化hystrix相关配置。HystrixCommandProperties通过一系列花式操作获取ArchaiusDynamicProperty对象,该对象继承PropertyWrapper,后者的构造器内会通过DynamicProperty静态方法getInstance获取hystrixapplication.yml内的配置值。

hystrix代码有点乱,不知道为什么一个配置流程封装的这么复杂,完全没必要。