调试一个初次见到的代码比重写代码要困难两倍。因此,按照定义,如果你写代码非常巧妙,那么没有人足够聪明来调试它。
–> 返回专栏总目录 <–
代码下载地址: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
里这两个方法也是一样的)。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加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(一)IClient请求客户端
- [享学Netflix] 三十九、Ribbon核心API源码解析:ribbon-core(二)IClientConfig配置详解
- [享学Netflix] 四十、Ribbon核心API源码解析:ribbon-core(三)RetryHandler重试处理器
- [享学Netflix] 四十一、Ribbon核心API源码解析:ribbon-core(四)ClientException客户端异常
- [享学Netflix] 四十二、Ribbon的LoadBalancer五大组件之:IPing心跳检测