任何一個傻瓜都會寫讓機器理解的代碼,只有好的程序員才能寫出人類可以理解的代碼。
–> 返回專欄總目錄 <–
代碼下載地址:https://github.com/f641385712/netflix-learning
目錄
前言
配置對於一個程序到底有多重要自然不用多說,每個庫均有它自己的配置管理方式,比如Spring有Enviroment
抽象等。
本文即將介紹的是Ribbon中一個使用頻繁,且非常重要的接口:IClientConfig
,它負責Ribbon的配置管理,包括所有默認值的維護,以及提供提供其讀寫能力。
正文
Ribbon所有的配置均交由IClientConfig
統一管理,並提供統一的入口、出口,其它核心組件如IClient、ILoadBalancer均會讀取此配置來控制其行爲。
弱弱說一句:Ribbon的配置管理依賴於Netflix Archaius庫,學習它可跳轉到這裏:Netflix Archaius全文講解
IClientConfig
定義各種API用於初始化IClient
或者ILoadBalancer
的客戶端配置,以及方法執行。
public interface IClientConfig {
// 如account、user...
public String getClientName();
// 默認值是ribbon
public String getNameSpace();
// 加載給定客戶端/負載均衡器(名爲clinetName)的屬性。該方法重要,重要,重要
// 內部會自動加載默認是,也就是loadDefaultValues()方法
public void loadProperties(String clientName);
// 加載配置的默認值們,放進全局的Configuration裏面。和clientName無關,公用的
public void loadDefaultValues();
@Deprecated
public void setProperty(IClientConfigKey key, Object value);
@Deprecated
public Object getProperty(IClientConfigKey key);
@Deprecated
public Object getProperty(IClientConfigKey key, Object defaultVal);
// 請用這三個帶有泛型的方法,代替上面的三個方法 不用強轉更安全
// 入參是IClientConfigKey,這在上文已經詳解過
public <T> IClientConfig set(IClientConfigKey<T> key, T value);
public <T> T get(IClientConfigKey<T> key);
public <T> T get(IClientConfigKey<T> key, T defaultValue);
... // 省略getPropertyAsInteger getPropertyAsString getPropertyAsBoolean方法
// 配置內是否包含某個key
public boolean containsProperty(IClientConfigKey key);
//返回此Client客戶端配置使用的適用虛擬地址(“vip”)。
//會依賴於VipAddressResolver進行解析
public String resolveDeploymentContextbasedVipAddresses();
}
該接口有唯一實現類DefaultClientConfigImpl
,它管理着常用屬性key對應的默認值,以及實現所有的接口方法。
DefaultClientConfigImpl
從Archaius
的ConfigurationManager
加載屬性的默認客戶端配置。也可以通過編程的方式實現IClientConfig
的加載。
您可以在classpath文件中定義屬性,也可以將其定義爲系統屬性。如果是前者,那麼應該調用ConfigurationManager.loadPropertiesFromResources()
API來加載文件到全局配置Configuration
裏。而絕大數的配置你並不會顯示的配置出來,這些默認值便通過DefaultClientConfigImpl
來管理:
public class DefaultClientConfigImpl implements IClientConfig {
// ===================默認值們==================
public static final Boolean DEFAULT_PRIORITIZE_VIP_ADDRESS_BASED_SERVERS = Boolean.TRUE;
public static final String DEFAULT_NFLOADBALANCER_PING_CLASSNAME = "com.netflix.loadbalancer.DummyPing";
// 默認在單臺Server上不會重試
public static final int DEFAULT_MAX_AUTO_RETRIES = 0;
// 默認這臺不行,只會重試下一臺(若你有多臺機器,其它的就不會被嘗試到了)
public static final int DEFAULT_MAX_AUTO_RETRIES_NEXT_SERVER = 1;
public static final int DEFAULT_READ_TIMEOUT = 5000;
public static final int DEFAULT_CONNECT_TIMEOUT = 2000;
...
// 默認的nameSpace值
public static final String DEFAULT_PROPERTY_NAME_SPACE = "ribbon";
// all connections idle for 30 secs
public static final int DEFAULT_CONNECTIONIDLE_TIME_IN_MSECS = 30000;
}
以上爲定義的public static
默認值,各默認值和CommonClientConfigKey
管理的通用key們一一對應。這種對應關係也是交由DefaultClientConfigImpl
來做的,進而把k-v放進全局配置裏:
DefaultClientConfigImpl:
// properties裝載全部的屬性k-v,注意key是如ConnectTimeout,而非ribbon.ConnectTimeout這種
protected volatile Map<String, Object> properties = new ConcurrentHashMap<>();
// dynamicProperties:它相較於properties只會裝載支持動態化的的屬性k-v
// 所以可以看到它的v是DynamicStringProperty具有動態性嘛
private final Map<String, DynamicStringProperty> dynamicProperties = new ConcurrentHashMap<String, DynamicStringProperty>();
// 該變量無任何作用,感覺像是忘記作者忘記刪掉的一個成員變量
protected Map<IClientConfigKey<?>, Object> typedProperties = new ConcurrentHashMap<>();
private String clientName = null;
// 用於解析vipAddress(下有解析)
private VipAddressResolver resolver = null;
// 是否允許動態屬性,注意:這裏值雖然是true
// 但是參考下面的構造器:構造之處會把此值改爲false
private boolean enableDynamicProperties = true;
// 默認的名稱空間是ribbon,請不要改變它
// 說明:所有的配置都是和NameSpace名稱空間綁定的哦
private String propertyNameSpace = DEFAULT_PROPERTY_NAME_SPACE;
... // 省略所有的屬性的get方法
public void setClientName(String clientName){
this.clientName = clientName;
}
@Override
public String getClientName() {
return clientName;
}
// =======僅有的兩個構造器,需要特別注意,特別注意=======
// 構造的時候,會把enableDynamicProperties改爲false,表示不允許屬性值動態化
public DefaultClientConfigImpl() {
this.dynamicProperties.clear();
this.enableDynamicProperties = false;
}
public DefaultClientConfigImpl(String nameSpace) {
this();
this.propertyNameSpace = nameSpace;
}
當構造一個DefaultClientConfigImpl
實例時,enableDynamicProperties
會被置爲false,也就是不會支持動態屬性。實例建好了,但是還沒有完成默認值的“裝載”,下面繼續看看。
屬性裝載
屬性的[配置格式]
任何配置的加載均需要有格式,這在代碼裏也有所體現:
DefaultClientConfigImpl:
// 默認值,全局通用的值的格式
String getDefaultPropName(String propName) {
return getNameSpace() + "." + propName;
}
// 指定clientName的格式
public String getInstancePropName(String restClientName, String key) {
return restClientName + "." + getNameSpace() + "." + key;
}
// 獲取指定propName對應的key,這裏體現了優先級
// 若有clientName就拿指定clientName的,否則去拿全局的
private String getConfigKey(String propName) {
return (clientName == null) ? getDefaultPropName(propName) : getInstancePropName(clientName, propName);
}
它規定了加載的格式爲:
// 默認情況下nameSpace=ribbon,並且大概率沒人會改它
<clientName>.<nameSpace>.<propertyName>=<value>
這種配置是和clientName綁定的:指定Client客戶端專屬,大多數時候我們需要全局公用的配置,也就是所謂兜底的默認值,如果某個屬性缺少clientName,則將其解釋爲適用於所有客戶端的屬性:
<nameSpace>.<propertyName>=<value>
// 如下,它就是所有client共用的屬性
ribbon.ReadTimeout=5000
loadDefaultValues()裝載默認值
構建DefaultClientConfigImpl
時並不會立馬加載默認值到全局Configuration
配置,而是需要顯示被調用的。
DefaultClientConfigImpl:
// 它是個接口方法
@Override
public void loadDefaultValues() {
putDefaultIntegerProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, getDefaultMaxHttpConnectionsPerHost());
putDefaultIntegerProperty(CommonClientConfigKey.MaxTotalHttpConnections, getDefaultMaxTotalHttpConnections());
...
putDefaultStringProperty(CommonClientConfigKey.VipAddressResolverClassName, getDefaultVipaddressResolverClassname());
...
putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
}
// 1、先去Configuration裏找,有沒有配置好的全局屬性值(比如配置在config.properties裏的,或者系統屬性裏的)
// 2、若沒有就使用defaultValue,就是本類的常量值作爲默認值嘍
// 3、放進去setPropertyInternal(propName, value);
protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
setPropertyInternal(propName, value);
}
protected void setPropertyInternal(final String propName, Object value) {
// 記錄所有的k-v鍵值對到properties裏面
// 需要注意的是它的key是propertyName,如ConnectTimeout,而並非是ribbon.ConnectTimeout這種
String stringValue = (value == null) ? "" : String.valueOf(value);
properties.put(propName, stringValue);
// 此處非常關鍵:非常關鍵:非常關鍵
// 若enableDynamicProperties=false,也就是沒有開啓動態屬性的支持,就return了
// 也就是不會註冊回調函數從而支持動態屬性
if (!enableDynamicProperties) {
return;
}
// ======若開啓了動態屬性的支持======
// clientName.ribbon.xxx或者ribbon.xxx
String configKey = getConfigKey(propName);
// 看看Configuration裏是否已經有配置該值
DynamicStringProperty prop = DynamicPropertyFactory.getInstance().getStringProperty(configKey, null);
// 動態屬性回調:用於同步更新properties裏的值
prop.addCallback(() -> {
// 注意:這個get方法具有動態性哦~~~~~~~
String value = prop.get();
if (value != null) {
properties.put(propName, value);
} else {
properties.remove(propName);
}
});
dynamicProperties.put(propName, prop);
}
通過調用該方法,實現了把所有的默認k-v均裝進了Map<String, Object> properties
裏面,並且還對屬性們開啓了動態監測(若enableDynamicProperties=true
的話),使得其具有動態性。
loadProperties(clientName)裝載指定Client值
在我們開發過程中,大多數時候我們是想得到指定Client
它所有擁有的配置。該方法加載給定Client的屬性。它首先加載所有屬性的默認值,以及任何已經用Archaius ConfigurationManager定義的屬性。
DefaultClientConfigImpl:
// 它是個接口方法
@Override
public void loadProperties(String restClientName){
// 重要:重要:重要:此處是唯一把它改爲true的地方
enableDynamicProperties = true;
// 順帶也幫你把成員屬性clientName賦上值
setClientName(restClientName);
// 加載默認值(注意:此時均具有動態性了)
loadDefaultValues();
// 此處巧用subset方法,得到一個新的Configuration
// 簡答的說:就是把所有的以"account."開頭的都拿過來(此處架設restClientName=account)
Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
// 遍歷,把Configuration裏面的值都放進全局成員屬性properties裏
for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
...
// 如果是ribbon.xxx的話,就切割一下。保證後面部分纔是真實的key
if (prop.startsWith(getNameSpace())){
prop = prop.substring(getNameSpace().length() + 1);
}
// 改方法上文有講,此處重要的是getStringValue()方法
setPropertyInternal(prop, getStringValue(props, key));
}
}
// 這是爲了解決默認情況下AbstractConfiguration的問題自動將逗號分隔的字符串轉換爲數組
// 我們知道在AbstractConfiguration下若你配置的是1,2,3的話,那最終存進去的是數組形式[1,2,3]
// 而本方法就是給它還原,具體邏輯比較簡單,就不再詳述了
protected static String getStringValue(Configuration config, String key) {
...
}
- 只有調用了該方法,去加載指定client的配置事,
enableDynamicProperties
纔會設爲true,讓其支持動態屬性了- 言外之意:純默認值管理下,是不支持動態屬性的
getStringValue()
方法能夠保證你配置的value值原滋原味- 比如你配置的是1,2,3,那麼保證存儲到properties裏面的也是1,2,3,而非[1,2,3]
- 注意這裏處理和
putDefaultStringProperty()
是有差異的,它依賴的是AbstractConfiguration#getString()
這個API,而它只會返回collection.iterator().next()
,也就是如果你是逗號分隔的話,只會返回第一個值(這個差異特別重要,所有默認值裏千萬不要配置逗號分隔的形式,否則就是bug了)
- . 請務必確保restClientName不能爲null(它程序內沒有判斷,其實算個小bug),否則拋錯
絕大多數情況下,我們並不需要主動調用loadDefaultValues()
,而只需使用loadProperties(restClientName)
方法即可完成配置的加載。
屬性獲取
屬性完成裝載後,獲取就比較簡單了。
DefaultClientConfigImpl:
// 接口方法:獲取所有的屬性k-v
// 說明:這裏返回的是實例本身,並不是副本。所有你是可以向裏面添加、刪除值的
@Override
public Map<String, Object> getProperties() {
return properties;
}
// 雖然它已經被標記爲過期:使用get()方法代替,但因爲使用的人還不少,所以看看
@Override
public Object getProperty(IClientConfigKey key){
String propName = key.key();
Object value = getProperty(propName);
return value;
}
protected Object getProperty(String key) {
if (enableDynamicProperties) {
DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
// 先找指定Client的屬性
dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
// 找不到再去找全局的
dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
}
return properties.get(key);
}
getProperty()
方法的執行邏輯:
- 若開啓了動態屬性
enableDynamicProperties=true
,那就先去動態屬性裏找- 先找指定Client自己的配置(clientName.ribbon.xxx)
- 沒有找到就找全局的配置(ribbon.xxx)
- 木有找到,就去全局的
properties
裏找
DefaultClientConfigImpl:
@Override
public <T> T get(IClientConfigKey<T> key) {
// 同樣也依賴於上面說的getProperty()方法哦
Object obj = getProperty(key.key());
if (obj == null) {
return null;
}
Class<T> type = key.type();
... // 根據type類型做數據轉換,略。這就是帶泛型的好處
}
// =============當然還有些快捷方法==========
@Override
public int getPropertyAsInteger(IClientConfigKey key, int defaultValue) { ... }
@Override
public String getPropertyAsString(IClientConfigKey key, String defaultValue) { ... }
@Override
public boolean getPropertyAsBoolean(IClientConfigKey key, boolean defaultValue) { ... }
// =========判斷方法=========
@Override
public boolean containsProperty(IClientConfigKey key){
Object o = getProperty(key);
return o != null ? true: false;
}
實例構建
IClientConfig
作爲最常打交道的一個接口,所以Ribbon也很暖心的給我們提供了多種構建該實例的方式。
示例一:靜態方法方式
我們知道IClientConfig
的唯一實現類只有DefaultClientConfigImpl
,所以它提供了幾個靜態方法讓你便捷構建其實例:
DefaultClientConfigImpl:
// 空的。因爲load加載方法均還沒調用,因此裏面的properties屬性都是空的
// 此時若你getProperty,得到結果均是null
public static DefaultClientConfigImpl getEmptyConfig() {
return new DefaultClientConfigImpl();
}
// 帶有clientName的DefaultClientConfigImpl實例,推薦使用
// 它內部調用了config.loadProperties(clientName),所以是個完整的config了
public static DefaultClientConfigImpl getClientConfigWithDefaultValues(String clientName) {
return getClientConfigWithDefaultValues(clientName, DEFAULT_PROPERTY_NAME_SPACE);
}
// clintName的名字爲default,一般不建議使用
public static DefaultClientConfigImpl getClientConfigWithDefaultValues() {
return getClientConfigWithDefaultValues("default", DEFAULT_PROPERTY_NAME_SPACE);
}
// 自己指定clientName、自己指定nameSpace 一般也用不着
public static DefaultClientConfigImpl getClientConfigWithDefaultValues(String clientName, String nameSpace) {
DefaultClientConfigImpl config = new DefaultClientConfigImpl(nameSpace);
config.loadProperties(clientName);
return config;
}
此方式是推薦使用的方式,特別是帶ClientName的方法,得到的是一個有血有肉的,可以立馬提供服務的實例。
示例二:Builder方式
IClientConfig
接口內有一個內部類com.netflix.client.config.IClientConfig.Builder
即使Builder模式。
IClientConfig:
public static class Builder {
private IClientConfig config;
// 它構建出來的實例也是DefaultClientConfigImpl
public static Builder newBuilder() {
Builder builder = new Builder();
builder.config = new DefaultClientConfigImpl();
return builder;
}
// 推薦使用它而不是上面的空構造
public static Builder newBuilder(String clientName) {
Builder builder = new Builder();
builder.config = new DefaultClientConfigImpl();
builder.config.loadProperties(clientName);
return builder;
}
// 當然你也可以指定具體的Class實現,只是我們從來不會這麼幹.....
public static Builder newBuilder(Class<? extends IClientConfig> implClass, String clientName) {
Builder builder = new Builder();
try {
builder.config = implClass.newInstance();
builder.config.loadProperties(clientName);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
return builder;
}
...
// =======提供一些快捷設值方法=======
public Builder withMaxAutoRetries(int value) {
config.set(CommonClientConfigKey.MaxAutoRetries, value);
return this;
}
...
public Builder withConnectTimeout(int value) {
config.set(CommonClientConfigKey.ConnectTimeout, value);
return this;
}
...
public IClientConfig build() {
return config;
}
}
該builder在稍微複雜點的構建過程中還是比較好用的。
示例三:new方式
不說了,就是使用構造器進行new。需要特別注意的是:剛new出來的IClientConfig
實例,enableDynamicProperties
是爲false的哦。
代碼示例
public void fun5() {
// 靜態方法構建
IClientConfig config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("YourBatman");
System.out.println(config.getClientName());
System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
System.out.println("-----------------------");
// Builder構建
config = IClientConfig.Builder.newBuilder("YourBatman")
.withConnectTimeout(8000)
.withReadTimeout(10000)
.build();
System.out.println(config.getClientName());
System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
System.out.println("-----------------------");
// new方式構建
config = new DefaultClientConfigImpl();
System.out.println("load前:");
System.out.println(config.getClientName());
System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
config.loadProperties("YourBatman");
System.out.println("load後:");
System.out.println(config.getClientName());
System.out.println(config.get(CommonClientConfigKey.ConnectTimeout));
}
運行程序,打印:
YourBatman
2000
-----------------------
YourBatman
8000
-----------------------
load前:
null
null
load後:
YourBatman
2000
總結
關於Ribbon的配置管理接口IClientConfig就介紹到這了,本篇內容比較重要,但是並不難,希望讀者能夠學有所獲。
聲明
原創不易,碼字不易,多謝你的點贊、收藏、關注。把本文分享到你的朋友圈是被允許的,但拒絕抄襲
。你也可【左邊掃碼/或加wx:fsx641385712】邀請你加入我的 Java高工、架構師 系列羣大家庭學習和交流。
- [享學Netflix] 一、Apache Commons Configuration:你身邊的配置管理專家
- [享學Netflix] 二、Apache Commons Configuration事件監聽機制及使用ReloadingStrategy實現熱更新
- [享學Netflix] 三、Apache Commons Configuration2.x全新的事件-監聽機制
- [享學Netflix] 四、Apache Commons Configuration2.x文件定位系統FileLocator和FileHandler
- [享學Netflix] 五、Apache Commons Configuration2.x別樣的Builder模式:ConfigurationBuilder
- [享學Netflix] 六、Apache Commons Configuration2.x快速構建工具Parameters和Configurations
- [享學Netflix] 七、Apache Commons Configuration2.x如何實現文件熱加載/熱更新?
- [享學Netflix] 八、Apache Commons Configuration2.x相較於1.x使用上帶來哪些差異?
- [享學Netflix] 九、Archaius配置管理庫:初體驗及基礎API詳解
- [享學Netflix] 十、Archaius對Commons Configuration核心API Configuration的擴展實現
- [享學Netflix] 十一、Archaius配置管理器ConfigurationManager和動態屬性支持DynamicPropertySupport
- [享學Netflix] 十二、Archaius動態屬性DynamicProperty原理詳解(重要)
- [享學Netflix] 十三、Archaius屬性抽象Property和PropertyWrapper詳解
- [享學Netflix] 十四、Archaius如何對多環境、多區域、多雲部署提供配置支持?
- [享學Netflix] 十五、Archaius和Spring Cloud的集成:spring-cloud-starter-netflix-archaius
- [享學Netflix] 十六、Hystrix斷路器:初體驗及RxJava簡介
- [享學Netflix] 十七、Hystrix屬性抽象以及和Archaius整合實現配置外部化、動態化
- [享學Netflix] 十八、Hystrix配置之:全局配置和實例配置
- [享學Netflix] 十九、Hystrix插件機制:SPI接口介紹和HystrixPlugins詳解
- [享學Netflix] 二十、Hystrix跨線程傳遞數據解決方案:HystrixRequestContext
- [享學Netflix] 二十一、Hystrix指標數據收集(預熱):滑動窗口算法(附代碼示例)
- [享學Netflix] 二十二、Hystrix事件源與事件流:HystrixEvent和HystrixEventStream
- [享學Netflix] 二十三、Hystrix桶計數器:BucketedCounterStream
- [享學Netflix] 二十四、Hystrix在滑動窗口內統計:BucketedRollingCounterStream、HealthCountsStream
- [享學Netflix] 二十五、Hystrix累計統計流、分發流、最大併發流、配置流、功能流(附代碼示例)
- [享學Netflix] 二十六、Hystrix指標數據收集器:HystrixMetrics(HystrixDashboard的數據來源)
- [享學Netflix] 二十七、Hystrix何爲斷路器的半開狀態?HystrixCircuitBreaker詳解
- [享學Netflix] 二十八、Hystrix事件計數器EventCounts和執行結果ExecutionResult
- [享學Netflix] 二十九、Hystrix執行過程核心接口:HystrixExecutable、HystrixObservable和HystrixInvokableInfo
- [享學Netflix] 三十、Hystrix的fallback回退/降級邏輯源碼解讀:getFallbackOrThrowException
- [享學Netflix] 三十一、Hystrix觸發fallback降級邏輯的5種情況及代碼示例
- [享學Netflix] 三十二、Hystrix拋出HystrixBadRequestException異常爲何不會觸發熔斷?
- [享學Netflix] 三十三、Hystrix執行目標方法時,如何調用線程池資源?
- [享學Netflix] 三十四、Hystrix目標方法執行邏輯源碼解讀:executeCommandAndObserve
- [享學Netflix] 三十五、Hystrix執行過程集大成者:AbstractCommand詳解
- [享學Netflix] 三十六、Hystrix請求命令:HystrixCommand和HystrixObservableCommand
- [享學Netflix] 三十七、源生Ribbon介紹 — 客戶端負載均衡器
- [享學Netflix] 三十八、Ribbon核心API源碼解析:ribbon-core(一)