Ribbon负载均衡源码分析

一、@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获取一次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值