任何一个傻瓜都会写让机器理解的代码,只有好的程序员才能写出人类可以理解的代码。
代码下载地址: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的配置事,
才会设为true,让其支持动态属性了enableDynamicProperties
- 言外之意:纯默认值管理下,是不支持动态属性的
-
方法能够保证你配置的value值原滋原味getStringValue()
- 比如你配置的是1,2,3,那么保证存储到properties里面的也是1,2,3,而非[1,2,3]
- 注意这里处理和
是有差异的,它依赖的是putDefaultStringProperty()
这个API,而它只会返回AbstractConfiguration#getString()
,也就是如果你是逗号分隔的话,只会返回第一个值(这个差异特别重要,所有默认值里千万不要配置逗号分隔的形式,否则就是bug了)collection.iterator().next()
- . 请务必确保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就介绍到这了,本篇内容比较重要,但是并不难,希望读者能够学有所获。