今天,多少孩子既要美国式的自由,又要中国式的宠爱。却没有美国孩子的独立,却又失去了中国传统的孝道。 代码下载地址:https://github.com/f641385712/netflix-learning
上篇文章介绍了Hystrix
和Archaius
的整合,对深入了解Hystrix
内部的属性配置打好了基础。对于属性配置,程序员们的感受可能是可能既爱又恨,因为那些浩如烟海的配置项确实可能已经超过了你的脑容量。
但越是这样的工作,就越不可能靠强记的,而是应掌握其规律,学会查“字典”才是永恒之道。Hystrix
的属性配置不在少数,但它管理得非常的好,因此本文将从全局配置和实例配置作为切入点,授之以渔帮小伙伴们从根本上掌握Hystrix
的配置相关知识点。
配置虽不可或缺,但也切勿让繁杂的配置扰乱了你的心智,所以从根本上找到管理配置的规律,将为你编码、调优过程大大减负。
在正式接触Hystrix
的配置管理类之前,有必要先对基础支撑组件做些了解。
ChainBuilder
用于快速构建一个HystrixDynamicProperty
实例。上篇文章已经说过HystrixDynamicProperty
的实例均以匿名方式实现,并没有可以给你直接使用的实例。因此若你想在“外部”需要构建它,还得自己写个实现类着实麻烦,因此就有了本构建器来帮你解决难题。
它是HystrixPropertiesChainedProperty
的一个静态public内部类:
HystrixPropertiesChainedProperty:
public static abstract class ChainBuilder<T> {
// 构建所需的所有的动态属性们
private List<HystrixDynamicProperty<T>> properties = new ArrayList<>();
// 添加一个动态属性property
public ChainBuilder<T> add(HystrixDynamicProperty<T> property) {
properties.add(property);
return this;
}
// 这里最重要的方法是:getDynamicProperty(),它会自己动态去获取
// 另外注意:这个getType是个抽象方法~~~~
public ChainBuilder<T> add(String name, T defaultValue) {
properties.add(getDynamicProperty(name, defaultValue, getType()));
return this;
}
protected abstract Class<T> getType();
// 构建
// 简单的说:若properties这个List一个值都木有,就抛错
// 若只有一个值,那便是它
// 若有多个值(比如你add了多次),那就按照你add的顺序,以第一个不为null的为准
public HystrixDynamicProperty<T> build() {
...
}
}
// HystrixPlugins.getInstance().getDynamicProperties()
// 这句最终得到的是一个`HystrixDynamicPropertiesArchaius`,也就是从全局的Configuration去获取值
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;
}
使用它,不仅可以快速构建出一个HystrixDynamicProperty
动态属性实例,而且还能和全局的Configuration完成整合,并且控制add()的优先级,功能还是挺强大的,推荐使用。
ChainBuilder
自己是个抽象类,那么如何构建它呢?这里提供有4个static工具方法供以使用:
HystrixPropertiesChainedProperty:
public static ChainBuilder<String> forString() {
return forType(String.class);
}
public static ChainBuilder<Integer> forInteger() {
return forType(Integer.class);
}
public static ChainBuilder<Boolean> forBoolean() {
return forType(Boolean.class);
}
public static ChainBuilder<Long> forLong() {
return forType(Long.class);
}
@Test
public void fun2() {
HystrixDynamicProperty<String> property = HystrixPropertiesChainedProperty.forString()
.add("hystrix.command.myApp.personName", null)
.add("hystrix.command.default.personName", "name-default")
.build();
System.out.println(property.get());
}
配置书写在config.properties
里,这段代码会根据配置的不同而结果不同:
name-default
James
hystrix.command.myApp.personName=James
hystrix.command.default.personName=Bryant
Bryant
hystrix.command.default.personName=Bryant
这段示例代码,便是Hystrix实现动态配置,并且实现全局 + 实例配置相结合的缩影。
说明:Hystrix为几乎所有的key,即可配置一个全局配置,又可以单独为某个
HystrixCommand
单独配置一个个性化数值,弹性非常强
一个接口,代表Hystrix的一个key。
public interface HystrixKey {
// “name”这个词代替了“key”,这样enum就可以实现这个接口,并且它可以本地工作。
// 所以这个考量其实还蛮巧妙的,考虑挺周全
String name();
}
这个接口极其简单,它有如下实现类:
先看Default默认实现:
HystrixKey:
// 注意:它还是个抽象类呢(当然接口内的肯定是static喽)
abstract class HystrixKeyDefault implements HystrixKey {
private final String name;
public HystrixKeyDefault(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public String toString() {
return name;
}
}
这个抽象默认实现就太简单了,其它实现也均继承于它。
HystrixKey
的子接口。表示HystrixCommand
的组名,用于将报告、告警、仪表盘dashboards等等信息分组放在一起。
// 该接口木有任何新增方法,只是提供了一个内部Factory类
public interface HystrixCommandGroupKey extends HystrixKey {
// 接口内部类
class Factory {
// 用于intern缓存HystrixCommandGroupDefault实例
// 这样我们就不会为了同一个键重复创建它们数百万次,提高效率
// InternMap它就是一个Map,内部持有ConcurrentHashMap用作缓存使用~
// 这样:每一个String类型的key,调用interned()方法后就会被缓存进Map里~~~
// 注意:这个只是私有方法,供内部方法使用
private static final InternMap<String, HystrixCommandGroupDefault> intern = new InternMap<>(key -> new HystrixCommandGroupDefault(key));
// 根据一个字符串的key,得到一个HystrixCommandGroupKey实例
public static HystrixCommandGroupKey asKey(String name) {
return intern.interned(name);
}
// 私有,私有静态内部类实现HystrixCommandGroupKey 接口~~~
// 注意:该类私有,外部并不能访问和构造
private static class HystrixCommandGroupDefault extends HystrixKey.HystrixKeyDefault implements HystrixCommandGroupKey {
public HystrixCommandGroupDefault(String name) {
super(name);
}
}
// 获取key的总数
static int getGroupCount() {
return intern.size();
}
}
}
此接口内聚性非常高:外部可访问的只能是接口,实现类都放在了内部,并且仅仅只提供asKey()
一个方法供以使用,因此使用起来也是极其方便的设计。
另外两个子接口HystrixThreadPoolKey
以及HystrixCommandKey
的逻辑和上面一毛一样,略过。
本文将以HystrixCommandProperties
为例,逐步学习Hystrix
对配置的管理,掌握了它后,其它自然就触类旁通。
HystrixCommandProperties
用于配置一个HystrixCommand
实例的配置类,含有很多配置项,默认使用archaius
管理。
public abstract class HystrixCommandProperties {
... // 非常多的成员变量HystrixProperty,每个都代表着一个属性,后面会逐个解释
// 构造器,key是必须的,其它都非必传(这个key蛮关键的)
// 前缀均写死为:hystrix,一般也请不要改变它
protected HystrixCommandProperties(HystrixCommandKey key) {
this(key, new Setter(), "hystrix");
}
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) {
this(key, builder, "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()方法赋值
...
// 线程池无全局配置,只能配实例级别的
this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build();
}
...
...
... // 省略所有的成员属性的get方法
}
该抽象类集中管理了配置,并且很比较巧妙的实现了默认值、外部化、动态化配置的一体化实现。
Hystrix
它支持全局配置和实例配置,核心处理逻辑如下代码,其中最为关键之地在于它的getProperty()
这个处理方法,它会通过此方法给每个成员属性赋值。
HystrixCommandProperties:
// 得到一个Boolean类型的ChainBuilder构建出一个属性值HystrixProperty
// 实际build的是个HystrixDynamicProperty动态属性哦~~~~
private static HystrixProperty<Boolean> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) {
return forBoolean() //
.add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue)
.add(propertyPrefix + ".command.default." + instanceProperty, defaultValue)
.build();
}
... // 省略String、Integer等类型
有了文首对ChainBuilder
内容的铺垫,理解这段代码就毫无障碍了,规则如下:
HystrixCommand
实例,而实例配置仅作用于指定名称的HystrixCommand
实例(实例名称就是HystrixCommandKey key
)hystrix.command.MyInstanceName.circuitBreaker.enabled = false
MyInstanceName
就代表实例名称,该配置只会作用于指定的实例hystrix.command.circuitBreaker.enabled = true
有两个稍微特殊点的案例这里做特别说明。
executionIsolationThreadPoolKeyOverride
:
this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build();
因为它不能有全局配置,所以只会有实例配置,譬如:hystrix.command.MyInstanceName.threadPoolKeyOverride = myThreadPool
executionIsolationStrategy
:执行时的隔离策略。因为隔离策略只能有如下两个取值:
HystrixCommandProperties:
// 内部静态枚举类:执行时隔离的两种策略
// THREAD:在隔离的线程里执行run方法,并且使用线程池来限制并发的大小
// SEMAPHORE:就在当前线程里执行run方法,但是会使用全局的Semaphore信号量来控制并发
public static enum ExecutionIsolationStrategy {
THREAD, SEMAPHORE
}
所以这个配置它对value取值是有要求的:依赖于ExecutionIsolationStrategy.valueOf(value)
,只有它不抱错才算有效,否则配置无效会被忽略。譬如:hystrix.command.MyInstanceName.execution.isolation.strategy = SEMAPHORE
是一个合法的value值~
说明:这个value值不能是小写,比如全大写。也就是说可能取值有且仅有两个:
THREAD
和SEMAPHORE
,这点上Hystrix的容错性做得似乎不是特别的好~
Hystrix
里大量的使用内部类Setter来表示作用于其上的配置,用户可构建它的实例而通过编码的方式自定义属性值。
HystrixCommandProperties.Setter
自然也不例外,也是这个作用。
HystrixCommandProperties:
public static class Setter {
private Boolean circuitBreakerEnabled = null;
...
private ExecutionIsolationStrategy executionIsolationStrategy = null;
...
private Boolean requestLogEnabled = null;
... // 省略所有get、set方法
}
它的作用就是对外API,方便你使用API方式定制参数,形如这样使用:
@Test
public void fun4(){
// 使用API方式定制配置
HystrixCommandProperties.Setter setter = HystrixCommandProperties.Setter();
setter.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD)
.withExecutionTimeoutEnabled(true)
.withExecutionTimeoutInMilliseconds(3000);
// HystrixPropertiesStrategy
HystrixCommandProperties hystrixProperties = new HystrixPropertiesCommandDefault(HystrixCommandKey.Factory.asKey("MyInstanceName"), setter);
// ... 省略应用hystrixProperties的步骤喽~
}
一般情况下,直接使用Setter的方式极少,但是在API集成方面就必须使用到它了。例如Hystrix
和Feign
集成时,Setter就起到了配置桥梁的作用,不容忽视。
像截图中的其它配置类,如HystrixThreadPoolProperties
、HystrixTimerThreadPoolProperties
、HystrixCollapserProperties
等,原理和处理方式和上面的HystrixCommandProperties
一毛一样,唯一区别就是key的中间部分不一样:
.threadpool.
/ .threadpool.default.
.timer.threadpool.default.
.collapser.
/ .collapser.default.其它部分此处就不做过多展开了,略。
HystrixCommandProperties
是个抽象类,并不能直接使用。像这种配置抽象类均由一个默认实现类:
public class HystrixPropertiesCommandDefault extends HystrixCommandProperties {
// 只提供给你一个构造器,并没有想让你去自定义前缀的意思。固定值是:hystrix
public HystrixPropertiesCommandDefault(HystrixCommandKey key, Setter builder) {
super(key, builder);
}
}
吐槽一句:写这个类名的那个人估计当时睡着了,
HystrixPropertiesCommand
是个什么鬼,命名是HystrixCommandProperties
嘛~
因此,可以这么来用:
@Test
public void fun5() {
// 请注意:这里传null会抛出异常,Hystrix很多代码的健壮性其实是非常不够的,这是它的缺点,需要批评
// HystrixCommandProperties commandProperties = new HystrixPropertiesCommandDefault(HystrixCommandKey.Factory.asKey("myApp"), null);
HystrixCommandProperties commandProperties = new HystrixPropertiesCommandDefault(HystrixCommandKey.Factory.asKey("myApp"), HystrixCommandProperties.Setter());
// 很明显,这里打印的肯定就是属性的默认值喽
System.out.println(commandProperties.circuitBreakerEnabled().get());
System.out.println(commandProperties.executionIsolationStrategy().get());
System.out.println(commandProperties.executionTimeoutEnabled().get());
System.out.println(commandProperties.executionTimeoutInMilliseconds().get());
}
控制台打印:
true
THREAD
true
1000
关于Netflix Hystrix配置之:全局配置和实例配置就介绍到这了。我十分相信你看完此篇文章之后,再也不会惧怕那种浩如烟海的配置了,至少不会再担心Hystrix
不知道如何配了吧。
本文只讲述了Hystrix
如何管理配置,以及全局配置和实例配置的优先级关系等等,但是“内容”,也就是每个配置到底什么意思,能起什么作用,这在实战中还是蛮有意义的,因此在后续文章中还会详细这块内容。虽然很麻烦,但还是得做呀~