Ribbon – 初始化中提到了,@LoadBalanced
注解的 RestTemplate
会注入拦截器 LoadBalancerInterceptor
,咱们看看LoadBalancerInterceptor
是怎么做的。
LoadBalancerInterceptor#intercept
这里次要是通过 URL 把 serviceId 取出来,而后调用 LoadBalancerClient
的execute
办法。
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();
// 这个就是咱们注册到注册核心的服务名称
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname:" + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
RibbonLoadBalancerClient#execute
次要是获取 ILoadBalancer
和Server
,通过 Server 进行近程调用。这个 server 曾经是有对应的 IP 和端口了。
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 获取 ILoadBalancer
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过 ILoadBalancer 获取 Server
Server server = getServer(loadBalancer, hint);
if (server == null) {throw new IllegalStateException("No instances available for" + serviceId);
}
// 近程调用
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
RibbonLoadBalancerClient#getLoadBalancer 会调用 SpringClientFactory#getLoadBalancer,SpringClientFactory 的注入能够看上一章。SpringClientFactory#getLoadBalancer 调用父类的 NamedContextFactory#getInstance 办法,NamedContextFactory#getInstance 会判断之前是否创立了 serviceId 对应的 AnnotationConfigApplicationContext,如果没有则创立。
NamedContextFactory#getContext
这里关注是 createContext 办法,他注册了 RibbonEurekaAutoConfiguration 和 RibbonClientConfiguration,注册后就调用 refresh 办法。这两个值是怎么来的,能够参考上一章 SpringClientFactory 的创立。这两个有先后顺序,所以会先加载 RibbonEurekaAutoConfiguration 后加载 RibbonEurekaAutoConfiguration,而 IPing、ServerList 都有 @ConditionalOnMissingBean
注解,所以优先实例化 RibbonEurekaAutoConfiguration 的 IPing、ServerList。
protected AnnotationConfigApplicationContext getContext(String name) {
// 如果没有,则调用 createContext 创立
if (!this.contexts.containsKey(name)) {synchronized (this.contexts) {if (!this.contexts.containsKey(name)) {this.contexts.put(name, createContext(name));
}
}
}
// 返回
return this.contexts.get(name);
}
protected AnnotationConfigApplicationContext createContext(String name) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {context.register(configuration);
}
}
// 注册 org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {if (entry.getKey().startsWith("default.")) {for (Class<?> configuration : entry.getValue().getConfiguration()) {context.register(configuration);
}
}
}
// 注册 org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
context.register(PropertyPlaceholderAutoConfiguration.class,
this.defaultConfigType);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
refresh 加载比拟重要的有以下几个,代码就不贴了,这几个类就是在下面注册的 RibbonEurekaAutoConfiguration 和 RibbonClientConfiguration 外面。
- 加载 IPing,查看服务存活(NIWSDiscoveryPing)
- 加载 ServerList<Server>:服务实例列表,(DomainExtractingServerList)
- 加载 IClientConfig(DefaultClientConfigImpl)
- 加载 IRule:负载平衡规定,(ZoneAvoidanceRule)
- 加载 ServerListUpdater,服务实例更新列表,(PollingServerListUpdater)
- 加载 ServerListFilter<Server>,服务实例过滤列表,(ZonePreferenceServerListFilter)
- 加载 ILoadBalancer:用于抉择 server,(ZoneAwareLoadBalancer)
这几个 bean 加载的时候,都有相似以下的判断,这个次要是判断是否有自定义配置,如果没有,则取默认,有就取自定义的。
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
上面次要看 ZoneAwareLoadBalancer 的构造函数,他会调用父类的 BaseLoadBalancer#initWithConfig 办法和 DynamicServerListLoadBalancer#restOfInit 办法。
BaseLoadBalancer#initWithConfig
这个办法次要是开启定时工作 ping,也就是查看其余服务是否是存活的。
void initWithConfig(IClientConfig clientConfig, IRule rule, IPing ping, LoadBalancerStats stats) {
// 其余略
// 设置 ping 的工夫,默认 30 秒,也就是说服务挂了最多 30 秒就发现了
setPingInterval(pingIntervalTime);
// 设置最大次数
setMaxTotalPingTime(maxTotalPingTime);
// 设置 IRule,这里会把以后的 ILoadBalancer 赋值给 IRule
setRule(rule);
// 开启定时工作 ping
setPing(ping);
// 其余略
}
定时工作的调用过程简略为:PingTask#run–>BaseLoadBalance.Pinger#runPinger–>BaseLoadBalance.SerialPingStrategy#pingServers–>NIWSDiscoveryPing#isAlive,NIWSDiscoveryPing#isAlive 是判断。
public void runPinger() throws Exception {
// 其余略
// 通过 BaseLoadBalance.SerialPingStrategy#pingServers 调用 NIWSDiscoveryPing#isAlive,// 次要是判断服务的状态是不是 UP,如果是 UP,就是存活。results = pingerStrategy.pingServers(ping, allServers);
final List<Server> newUpList = new ArrayList<Server>();
final List<Server> changedServers = new ArrayList<Server>();
// 前面代码次要是把存活的存入到 newUpList 再到 upServerList,把变动的存入 changedServers,并调用监听告诉
// 其余略
}
DynamicServerListLoadBalancer#restOfInit
这个办法次要有两个性能,一个是调用 DynamicServerListLoadBalancer#enableAndInitLearnNewServersFeature 开启定时工作获取 Eureka 的注册表,一个是调用 DynamicServerListLoadBalancer#updateAllServerList 办法获取 Eureka 的注册表。所以次要看看 DynamicServerListLoadBalancer#updateListOfServers。
public void updateListOfServers() {List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
// 获取 Eureka 的注册表
servers = serverListImpl.getUpdatedListOfServers();
if (filter != null) {
// 过滤 zone 为 defaultZone
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
// 赋值 allServerList
updateAllServerList(servers);
}
RibbonLoadBalancerClient#getServer
咱们回到 RibbonLoadBalancerClient#execute 来,getLoadBalancer 办法曾经把该加载的加载了,获取到 ILoadBalancer 后,咱们就曾经获取到被调用的服务列表了。当初就要获取某一个服务来做近程调用了,因为注入的是 ZoneAvoidanceRule,所以默认的就是轮询来获取 Server。获取到 Server 后,就能够进行近程调用了。
private int incrementAndGetModulo(int modulo) {for (;;) {int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
总结
ribbon 在调用之前,会获取到 Eureka 的注册信息,并开启定时工作去更新 Eureka 的注册信息,以及检测是否存活,默认都是 30 秒。调用的时候,通过 serviceId,获取服务列表(此时已转为 IP+ 端口),再通过负载平衡策略,获取某个服务进行近程调用。