[享學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如何管理配置,以及全局配置和實例配置的優先順序關係等等,但是「內容」,也就是每個配置到底什麼意思,能起什麼作用,這在實戰中還是蠻有意義的,因此在後續文章中還會詳細這塊內容。雖然很麻煩,但還是得做呀~