[享学Netflix] 十八、Hystrix配置之:全局配置和实例配置

  • 2020 年 3 月 18 日
  • 筆記

今天,多少孩子既要美国式的自由,又要中国式的宠爱。却没有美国孩子的独立,却又失去了中国传统的孝道。 代码下载地址:https://github.com/f641385712/netflix-learning

目录

前言

上篇文章介绍了HystrixArchaius的整合,对深入了解Hystrix内部的属性配置打好了基础。对于属性配置,程序员们的感受可能是可能既爱又恨,因为那些浩如烟海的配置项确实可能已经超过了你的脑容量。

但越是这样的工作,就越不可能靠强记的,而是应掌握其规律,学会查“字典”才是永恒之道。Hystrix的属性配置不在少数,但它管理得非常的好,因此本文将从全局配置和实例配置作为切入点,授之以渔帮小伙伴们从根本上掌握Hystrix的配置相关知识点。


正文

配置虽不可或缺,但也切勿让繁杂的配置扰乱了你的心智,所以从根本上找到管理配置的规律,将为你编码、调优过程大大减负。


HystrixPropertiesChainedProperty.ChainBuilder

在正式接触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里,这段代码会根据配置的不同而结果不同:

  1. 不写任何配置,输出:name-default
    1. 注意:因为第一个add()最终值是null(不管是获取,还是默认值均为null嘛),所以就以第二个值为准啦
  2. 书写配置如下,输出:James
hystrix.command.myApp.personName=James  hystrix.command.default.personName=Bryant
  1. 书写配置如下,输出:Bryant
hystrix.command.default.personName=Bryant

这段示例代码,便是Hystrix实现动态配置,并且实现全局 + 实例配置相结合的缩影。

说明:Hystrix为几乎所有的key,即可配置一个全局配置,又可以单独为某个HystrixCommand单独配置一个个性化数值,弹性非常强


HystrixKey

一个接口,代表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;          }      }

这个抽象默认实现就太简单了,其它实现也均继承于它。


HystrixCommandGroupKey

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

本文将以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内容的铺垫,理解这段代码就毫无障碍了,规则如下:

  • Hystrix属性配置分为全局配置实例配置
  • 全局配置作用与所有的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值不能是小写,比如全大写。也就是说可能取值有且仅有两个:THREADSEMAPHORE,这点上Hystrix的容错性做得似乎不是特别的好~


Setter

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集成方面就必须使用到它了。例如HystrixFeign集成时,Setter就起到了配置桥梁的作用,不容忽视。


其它xxxProperties

像截图中的其它配置类,如HystrixThreadPoolPropertiesHystrixTimerThreadPoolPropertiesHystrixCollapserProperties等,原理和处理方式和上面的HystrixCommandProperties一毛一样,唯一区别就是key的中间部分不一样:

  • HystrixThreadPoolProperties:.threadpool. / .threadpool.default.
  • HystrixTimerThreadPoolProperties:.timer.threadpool.default.
  • HystrixCollapserProperties:.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如何管理配置,以及全局配置和实例配置的优先级关系等等,但是“内容”,也就是每个配置到底什么意思,能起什么作用,这在实战中还是蛮有意义的,因此在后续文章中还会详细这块内容。虽然很麻烦,但还是得做呀~