一、@LoadBalanced+RestTemplate实现负载原理
首先了解下@LoadBalanced注解
除了元注解,就多了一个@Qualifier注解 。该注解主要搭配@Autowired使用,用于引入不同name但同一type的bean对象。
当使用@Autowired+@Qualifier引入一个list<Person>或者map<Person>时,会将所有标注@Qualifier的Person对象注入到list或map中(注入map是,key是Person对象的String类型的名称)。
LoadBalancerAutoConfiguration 中就是使用@Autowired+@LoadBalanced注入了一个List对象:
即将所有标有@LoadBalanced注解的RestTemplate对象注入到restTemplates里。
在spring-cloud-common里通过自动装配引入LoadBalancerAutoConfiguration配置类
该类其他功能:
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
SmartInitializingSingleton 类,在springboot启动时调用,
RibbonAutoConfiguration
Ribbon的负载均衡从RibbonAutoConfiguration类说起,netflix-ribbon包里自动装配该类,如下:
该类具体内容:
SpringClientFactory对象:
继承了NamedContextFactory对象,构造方法调用父类构造方法,并将RibbonClientConfiguration(这是一个很重要的类)以参数的形式传进去。
RibbonClientConfiguration是个配置对象,以参数形式传入到NamedContextFactory对象后,怎么转化成Bean对象呢?
RibbonClientConfiguration转成Bean对象两种方式:
1、SpringBoot启动时,加载RibbonClientConfiguration对象
通过RibbonApplicationContextInitializer.java对象,在RibbonAutoConfiguration中将RibbonApplicationContextInitializer对象放入Spring容器:
@Bean
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
return new RibbonApplicationContextInitializer(springClientFactory(),
ribbonEagerLoadProperties.getClients());
}
==========================
yml文件中需要配置:
ribbon.eager-load.enabled=true
ribbon.eager-load.clients=service_id1,service_id2
SpringBoot启动时就会对service_id1和service_id2 的RibbonClient进行配置
实现了ApplicationListener对象,并监听ApplicationReadyEvent事件,在SpringBoot启动的时候会publisher发布该事件,然后执行onApplicationEvent方法(Spring监听器原理,稍后说下SpringBoot发布ApplicationEvent事件过程)。
继续onApplicationEvent方法,调用initialize方法:
protected void initialize() {
if (clientNames != null) {
for (String clientName : clientNames) {
// 调用springClientFactory.getContext(clientName)
this.springClientFactory.getContext(clientName);
}
}
}
SpringClientFactory的getContext 调用父类NamedContextFactory的getContext(这一块是公共的,只要是读取RibbonClientConfiguration配置类为bean对象,都会走这里):
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
// createContext方法就是RibbonClient配置的地方
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()) {
// 1
context.register(configuration);
}
}
。。。。。。。。。。。。。。。。。。
。。。。。。。。。。。。。。。。。。
}
1位置的context是AnnotationConfigApplicationContext对象,也是容器,主要是读取java配置对象
==========================================
public void register(Class<?>... annotatedClasses) {
Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
// 此处就是我们熟悉的Spring将配置对象放入Spring容器的方法
this.reader.register(annotatedClasses);
}
第一种依靠Spring监听机制加载RibbonClientConfiguration对象方式结束。
2.第一次Ribbon调用时,加载对应的RibbonClientConfiguration
以feign调用为例:调用CachingSpringLoadBalancerFactory的create方法(feign如何开启调用参考Openfeign源码解析)
一直点下去,会调用SpringClientFactory的getContext,和上面的步骤一致了,最后将RibbonClientConfiguration 解析成bean对象。
RibbonClientConfiguration
上面已经介绍了如何将该配置类转换成bean对象,现在介绍下这个类主要做了什么操作
1.构建IloadBalancer
看下百度翻译对IloadBalancer的介绍:
简单说,IloadBalancer是一个负载均衡接口,定义了从服务列表中选择服务调用的方法。(如:
chooseServer方法)。
这里 new 了一个ZoneAwareLoadBalancer,看下该类的类图:
new ZoneAwareLoadBalancer的时候会去调用DynamicServerListLoadBalancer的构造方法:
restOfInit方法中有两个很重要的方法:
先说下方法2:
判断定时任务时间时,开启debug日志后,观察多长时间打印一次:
List of Servers for {} obtained from Discovery client: {}
即可。
方法3:
为什么会去调用DiscoveryEnabledNIWSServerList 的方法呢?
因为EurekaRibbonClientConfiguration 在Spring中放入了DiscoveryEnabledNIWSServerList 对象
方法6 从Eureka 缓存中获取服务列表的方法:
localRegionApps存放的是Eureka client获取到的服务列表。
Ribbon从Eureka Client中获取服务列表的过程就是:2-->3-->6-->7
方法4--updateAllServerList 更新Ribbon缓存服务列表:
BaseLoadBalancer实现了setServersList方法:
public void setServersList(List lsrv) {
Lock writeLock = allServerLock.writeLock();
logger.debug("LoadBalancer [{}]: clearing server list (SET op)", name);
ArrayList<Server> newServers = new ArrayList<Server>();
writeLock.lock();
try {
ArrayList<Server> allServers = new ArrayList<Server>();
for (Object server : lsrv) {
if (server == null) {
continue;
}
if (server instanceof String) {
server = new Server((String) server);
}
if (server instanceof Server) {
logger.debug("LoadBalancer [{}]: addServer [{}]", name, ((Server) server).getId());
allServers.add((Server) server);
} else {
throw new IllegalArgumentException(
"Type String or Server expected, instead found:"
+ server.getClass());
}
}
boolean listChanged = false;
if (!allServerList.equals(allServers)) {
listChanged = true;
if (changeListeners != null && changeListeners.size() > 0) {
List<Server> oldList = ImmutableList.copyOf(allServerList);
List<Server> newList = ImmutableList.copyOf(allServers);
for (ServerListChangeListener l: changeListeners) {
try {
l.serverListChanged(oldList, newList);
} catch (Exception e) {
logger.error("LoadBalancer [{}]: Error invoking server list change listener", name, e);
}
}
}
}
if (isEnablePrimingConnections()) {
for (Server server : allServers) {
if (!allServerList.contains(server)) {
server.setReadyToServe(false);
newServers.add((Server) server);
}
}
if (primeConnections != null) {
primeConnections.primeConnectionsAsync(newServers, this);
}
}
// This will reset readyToServe flag to true on all servers
// regardless whether
// previous priming connections are success or not
//这里是对allServerList缓存进行赋值,Feign调用时 ,就是从该缓存中获取的服务列表
allServerList = allServers;
if (canSkipPing()) {
for (Server s : allServerList) {
s.setAlive(true);
}
upServerList = allServerList;
} else if (listChanged) {
forceQuickPing();
}
} finally {
writeLock.unlock();
}
}
更新Ribbon缓存列表的过程:4-->8-->9。
最后再介绍
方法1--enableAndInitLearnNewServersFeature 定时更新Ribbon缓存列表:
会去调用PollingServerListUpdater对象中的start方法,是因为RibbonClientConfiguration:
方法11会去调用:
定时更新Ribbon缓存服务列表的过程:1-->10-->11。
Ribbon缓存服务列表总结:第一次Ribbon调用时,会去从Eureka Client缓存中获取服务列表,然后定时任务启动1s后去获取一次,启动后每隔30s获取一次。