之前,猿Why认为应用中集成Consul进行服务注册与服务发现。那么,应用中的服务列表应当也是由Consul模块进行更新。为了给同事一个准确的回答,再次看了看源码后,发现服务列表的更新,并不是由服务注册中心和服务发现模块来处理的(比如Consul、Eureka)。
首先,服务注册中心的概念是抽象的,所以服务列表的更新必然不是由服务发现的具体实现方式(Consul、Eureka)来实现的。具体实现方式仅需要提供从服务注册中心获取服务列表的API,提供给负载均衡时候的服务发现使用。
猿Why目前工作中用到的负载均衡是spring-cloud-starter-netflix-ribbon。所以,从ribbon做服务选择的时候入手,了解服务更新的原理。
服务选择入口
以com.netflix.loadbalancer.RandomRule为例:
public class RandomRule extends AbstractLoadBalancerRule {
/**
* Randomly choose from all living servers
*/
@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
return null;
}
Server server = null;
while (server == null) {
if (Thread.interrupted()) {
return null;
}
// 分析服务列表更新关键入口
List<Server> upList = lb.getReachableServers();
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0) {
/*
* No servers. End regardless of pass, because subsequent passes
* only get more restrictive.
*/
return null;
}
int index = chooseRandomInt(serverCount);
server = upList.get(index);
if (server == null) {
/*
* The only time this should happen is if the server list were
* somehow trimmed. This is a transient condition. Retry after
* yielding.
*/
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
// Shouldn't actually happen.. but must be transient or a bug.
server = null;
Thread.yield();
}
return server;
}
protected int chooseRandomInt(int serverCount) {
return ThreadLocalRandom.current().nextInt(serverCount);
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
关键API
com.netflix.loadbalancer.BaseLoadBalancer#getReachableServers
如何获取最新的服务列表?
通过Debug断点,找到服务列表更新的入口。
所以,调试一番,基本就能看到服务列表是如何被更新的。com.netflix.loadbalancer.ServerList#getUpdatedListOfServers方法的注释也写的很清楚。通过这个方法,可以30s更新一次服务列表。
什么时候更新服务列表?
到这里,已经知道一次的服务列表更新是如何实现的。早些时候,猜测是通过定时任务来更新的,所以看到:
org.springframework.cloud.consul.discovery.ConsulCatalogWatch源码的时候,误以为是这里进行服务列表的更新,没有继续追源码。直到今天发现:这个API并没有更新服务列表,只是在最后发布了一个事件。猿Why理解,可以借助这个事件的发布,自行扩展服务列表更新的逻辑。
既然不是org.springframework.cloud.consul.discovery.ConsulCatalogWatch更新服务列表,那么更新服务列表的初始化配置在哪里呢?在com.netflix.loadbalancer.ServerList#getUpdatedListOfServers打断点,看到调用栈的信息后发现:
这样,就明白服务列表更新,是由一个scheduler来做的,默认周期是30s一次。
服务列表更新算法是否支持扩展?
如果支持扩展,入口应当是在这里。留下疑问,给后续补充
扩展性补充
负载均衡使用的客户端Bean自定义即可扩展,从以下的属性类中可以看出,当前支持哪些属性的自定义扩展
服务列表更新规则扩展
负载均衡扩展
有趣的事情是:可以看到负载均衡客户端Bean的初始化是在Bean被调用的时候实例化的。所以大胆猜想:@ConditionalOnMissingBean的应用有两个意义:①当指定类型的Bean不存在是后,做兜底使用;②该注解标注的Bean的实例化,会在Bean被用到的时候进行实例化。