调试一个初次见到的代码比重写代码要困难两倍。因此,按照定义,如果你写代码非常巧妙,那么没有人足够聪明来调试它。
代码下载地址:https://github.com/f641385712/netflix-learning
前言
继上文讲述了LoadBalancer五大组件之一的IPIng后,本文继续讲解它的
ServerList
组件。
正文
ServerList
该接口定义了获取服务器列表的方法。
// T并不规定必须是Server,也可以是它的子类
// 但实际情况是:一般并不会自己去继承Server来玩,因为Server已经够抽象了
public interface ServerList<T extends Server> {
// 返回初始状态的服务器列表。比如初试配置了10台那它永远是10个Server
public List<T> getInitialListOfServers();
// 返回更新后的服务列表。它会周期ping过后,返回活着的
public List<T> getUpdatedListOfServers();
}
复制
它的继承图谱如下(
Spring Cloud
环境下多了个实现):

AbstractServerList
该抽象实现有且提供一个public方法:提供loadBalancer使用的过滤器
AbstractServerListFilter
。
此处疑问:获取
ServerListFilter
的方法为何写在
ServerList
里呢?八竿子打不着好吗?不知道作者咋想的呢?因此本处先一笔略过
public abstract class AbstractServerList<T extends Server> implements ServerList<T>, IClientConfigAware {
public AbstractServerListFilter<T> getFilterImpl(IClientConfig niwsClientConfig) throws ClientException{
...
}
}
复制
还好它有个实现类:
ConfigurationBasedServerList
,基于配置的ServerList。
ConfigurationBasedServerList
它可以从
Configuration
配置中加载服务器列表的实用工具实现类。属性名的定义格式如下:
<clientName>.<nameSpace>.listOfServers=<comma delimited hostname:port strings>
复制
依赖的 IClientConfig
配置管理去获取配置值,所以没有ClientName获取的就是全局的,但是那又有什么意义呢???
// 这里挺有意思,到这里却确定了泛型类型是Server,搞怪~
public class ConfigurationBasedServerList extends AbstractServerList<Server> {
private IClientConfig clientConfig;
// 在此处两个方法的实现效果是一模一样的。
@Override
public List<Server> getInitialListOfServers() {
return getUpdatedListOfServers();
}
@Override
public List<Server> getUpdatedListOfServers() {
// key是:listOfServers(请注意:首字母l是小写)
String listOfServers = clientConfig.get(CommonClientConfigKey.ListOfServers);
// derive->逗号分隔 -> new Server(s.trim())
// 也就是说s是个id
return derive(listOfServers);
}
protected List<Server> derive(String value) { ... }
}
复制
该实现类需要引起重视,因为在
Spring Cloud
中,脱离eureka使用ribbon的经典配置:
# 禁用ribbon在eureka里使用
ribbon.eureka.enabled=false
# 配置服务提供者的地址
account.ribbon.listOfServers=localhost:8080,localhost:8081
复制
这样配置后,它就是使用
ConfigurationBasedServerList
来加载服务器列表的。
代码示例
- API方式配置:
@Test
public void fun2(){
// 准备配置
IClientConfig config = new DefaultClientConfigImpl();
// config.set(CommonClientConfigKey.valueOf("listOfServers"), "www.baidu.com,http://yourbatman.com:8080");
config.set(CommonClientConfigKey.ListOfServers, " www.baidu.com,http://yourbatman.com:8080 ");
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
serverList.getInitialListOfServers().forEach(server -> {
System.out.println(server.getId());
System.out.println(server.getHost());
System.out.println(server.getPort());
System.out.println(server.getHostPort());
System.out.println(server.getScheme());
System.out.println("-----------------------------");
});
}
复制
运行程序,控制台输出:
www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------
yourbatman.com:8080
yourbatman.com
8080
yourbatman.com:8080
http
-----------------------------
复制
- 配置文件方式:
config.properties
配置内容为:
account.ribbon.listOfServers= www.baidu.com,http://yourbatman.com:8080
复制
编写测试代码:
@Test
public void fun100(){
DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("account");
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
serverList.getInitialListOfServers().forEach(server -> {
System.out.println(server.getId());
System.out.println(server.getHost());
System.out.println(server.getPort());
System.out.println(server.getHostPort());
System.out.println(server.getScheme());
System.out.println("-----------------------------");
});
}
复制
运行程序,结果完全同上。
小bug
有小伙伴跟我反应了一种case,在此处顺便我给补上。它是这么做的(仅仅改了Config而已):
@Test
public void fun101(){
DefaultClientConfigImpl config = DefaultClientConfigImpl.getEmptyConfig();
config.loadDefaultValues(); // 注意:这句必须显示调用,否则配置里无值
ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
serverList.initWithNiwsConfig(config);
serverList.getInitialListOfServers().forEach(server -> {
System.out.println(server.getId());
System.out.println(server.getHost());
System.out.println(server.getPort());
System.out.println(server.getHostPort());
System.out.println(server.getScheme());
System.out.println("-----------------------------");
});
}
复制
config.properties内容(使用的全局配置):
ribbon.listOfServers=www.baidu.com,http://yourbatman.com:8080
复制
运行程序输出:
www.baidu.com:80
www.baidu.com
80
www.baidu.com:80
null
-----------------------------
复制
what?只输出一个???为何只会打印一个呢?
在了解具体原因之前,确保你已经对
IClientConfig
的原理非常了解了(不了解可以点这里)。
ConfigurationBasedServerList
使用的是
clientConfig.get(CommonClientConfigKey.ListOfServers)
去获取属性值,而get()方法依赖的是
DefaultClientConfigImpl#getProperty(String key)
:
DefaultClientConfigImpl:
protected Object getProperty(String key) {
if (enableDynamicProperties) {
// 它里面会先找指定clientName的属性,再找全局
// 秒就秒在这里,它使用的是DynamicProperty.getInstance(..).getString()
// 这个getString()方法为**原样输出**,这点特别重要
...
}
return properties.get(key);
}
复制
例子中只调用了加载默认值方法,所以
enableDynamicProperties
仍旧为false,所以此处会走
properties.get(key)
方法,该方法就是一Map的get方法没啥可说的,重点是它的值是如何放进去的?
DefaultClientConfigImpl:
public void loadDefaultValues() {
...
// 回去配置文件加载默认值放进去
putDefaultStringProperty(CommonClientConfigKey.ListOfServers, "");
}
protected void putDefaultStringProperty(IClientConfigKey propName, String defaultValue) {
String value = ConfigurationManager.getConfigInstance().getString(getDefaultPropName(propName), defaultValue);
setPropertyInternal(propName, value);
}
复制
这个
AbstractConfiguration#getString()
方法就是罪魁祸首:当key对应的value值是集合时,只会返回第一个值
collection.iterator().next()
。所以放进
properties
时只有一个值,
properties.get(key)
取出来肯定就只有一个喽。
解决问题非常的简单,不要用
config.loadDefaultValues()
方法即可,而是这么做,问题就得到解决。
DefaultClientConfigImpl config = DefaultClientConfigImpl.getClientConfigWithDefaultValues("noClient")
复制
这个“bug”本身并不重要,因为说过
listOfServers
禁止使用全局配置。但是该结果反映出来的原理还是可以深究的。实际上
Ribbon
里的这种小bug不止一处,后续文章还会继续提到。
说明:这种小问题我个人觉得是Ribbon的考虑不够全面导致的,当然喽绝大多数情况下都无伤大雅,可以从“操作”上规避它~
StaticServerList
它是
Spring Cloud
对
ServerList
接口的实现,因为比较简单,因此在此处一并带过。顾名思义它表示静态的List,这种实现太简单了。
public class StaticServerList<T extends Server> implements ServerList<T> {
private final List<T> servers;
public StaticServerList(T... servers) {
this.servers = Arrays.asList(servers);
}
@Override
public List<T> getInitialListOfServers() {
return servers;
}
@Override
public List<T> getUpdatedListOfServers() {
return servers;
}
}
复制
该实现类没有任何地方被使用到过,
Spring Cloud
提供出来是若你有写死Server地址在代码里的时候,可以使用它,只是我们需要这么做的概率极小极小而已。
哪里调用?
ServerList#getInitialListOfServers
方法无任何地方调用,
ServerList#getUpdatedListOfServers
方法的唯一调用处是:
DynamicServerListLoadBalancer#updateListOfServers
处:
DynamicServerListLoadBalancer:
// 更新服务列表。
public void updateListOfServers() {
// 拿到列表
servers = serverListImpl.getUpdatedListOfServers();
// 把列表过滤一把
servers = filter.getFilteredListOfServers(servers);
// 设置最新列表
pdateAllServerList(servers);
}
复制
总结
关于Ribbon的LoadBalancer五大组件之:ServerList就介绍到这了,本篇内容依旧比较简单。该API用于获取Server列表,它可以来自于写死的List、配置文件、甚至是远程网络(如eureka配置中心)。另外从实现中你可以看到:
getInitialListOfServers和getUpdatedListOfServers
两方法是完全对等的(哪怕和eureka集成的实现
com.netflix.niws.loadbalancer.DiscoveryEnabledNIWSServerList
里这两个方法也是一样的)。