我不怎么喜欢说废话,能看到这篇文章的人多少都要知道ribbon是干什么的,这个不做赘述,我只讲一下我在使用ribbon的疑惑以及跟源码的过程。
1.ribbon的配置类到底是怎么一步一步注册进去的?
2.ribbon到底是初始化的时候去注册中心取服务列表还是第一次调用服务的时候去获取服务列表?
1. ribbon有两个重要的配置类RibbonAutoConfiguration和RibbonClientConfiguration
RibbonAutoConfiguration:这个配置类初始化了负载均衡的实现类LoadBalancerClient
RibbonClientConfiguration:这个配置类里初始化了你的ribbon在yml文件里的配置信息,还有路由规则,IPing实现,ILoadBalancer对象,这里提前说一句LoadBalancerClient最终也是要从ILoadBalancer里获取信息的,接下来找到这两个配置类
相信springBoot的加载原理大家是懂的,那么咱们先看spring.factories
只有一个RibbonAutoConfiguration,看到只有一个是不是很爽,已经找到了一个,再看一下另一个配置类
我们发现他只是个配置类,却没有发现他是怎么注册进spring容器的,别急,这里先留个悬念。等到第二个问题自然明白
2.第二个问题,刚开始我一直以为ribbon是启动的时候就已经把服务列表拉取过来了,其实不是,验证这个问题其实很简单BaseLoadBalancer.setServersList(List lsrv)方法就是给从注册把一个服务列表存入本地缓存的,我们在这个方法打一个断点,启动ribbon,发现启动的时候并没有调用这个方法,那么既然不是在启动的时候拉取的服务列表,就肯定是在第一次调用服务的时候拉取的白,接下来,我们就分析ribbon调用服务的过程
LoadBalancerClient里定义了ribbon调用服务的过程
public interface LoadBalancerClient extends ServiceInstanceChooser {
//调用服务方法
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
//调用服务方法的实现
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
//重构url
URI reconstructURI(ServiceInstance instance, URI original);
}
这个接口又继承了ServiceInstanceChooser接口,ServiceInstanceChooser接口里定义了choose选取服务的方法
public interface ServiceInstanceChooser {
/**
* Choose a ServiceInstance from the LoadBalancer for the specified service
* @param serviceId the service id to look up the LoadBalancer
* @return a ServiceInstance that matches the serviceId
*/
ServiceInstance choose(String serviceId);
}
而RibbonLoadBalancerClient类实现了LoadBalancerClient接口,所以咱们看RibbonLoadBalancerClient类
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer();
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
} else {
server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
uri = updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
}
return context.reconstructURIWithServer(server, uri);
}
@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
。。。省略
}
咱们看choose(String serviceId)方法,ribbon调用服务的就是通过这个方法选一个服务进行调用,咱么点进getServer(serviceId);
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
在跟进this.clientFactory.getLoadBalancer(serviceId),发现他在
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
/**
* Get the rest client associated with the name.
* @throws RuntimeException if any error occurs
*/
public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
return getInstance(name, clientClass);
}
//这里
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
}
一录跟进发现他调用的NamedContextFactory.getInstance(String name, Class<T> type)的方法
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
getInstance又调用getContext(String name)方法
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}
分析getContext(String name)方法发现他是根据服务名称从一个ConcurrentHashMap中取出AnnotationConfigApplicationContext
即spring的上下文,如果第一次ConcurrentHashMap中没有,则进行创建,下面看创建方法
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);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
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);
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
这个方法是初始化一些配置文件,和一些配置类,还记得咱们问题里RibbonClientConfiguration这个配置类是什么时候加载进容器的吗的问题吗,就是在这里,当调用context.refresh()方法的时候就把RibbonClientConfiguration丢进了ioc容器。当然还包括一些其他的类。
说到这里你们是不是晕了,是否已经不记得我们的最终目的是干嘛,那么我再强调下,我们的最终目的是要证明第2个问题,即服务列表是在ribbon第一次调用服务的时候加到自己的本地缓存的,好,带着这个问题,咱们继续往下看,上面讲到RibbonClientConfiguration这个配置类是在ribbon第一次调用的时候被加载进ioc容器的,这里提前说一下,ribbon服务列表的方法就是通过这个配置类加载进去的。打开RibbonClientConfiguration,找打下面的方法,继续一路跟进
@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
return this.propertiesFactory.get(ILoadBalancer.class, config, name);
}
return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
serverListFilter, serverListUpdater);
}
public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
}
public DynamicServerListLoadBalancer(IClientConfig clientConfig, IRule rule, IPing ping,
ServerList<T> serverList, ServerListFilter<T> filter,
ServerListUpdater serverListUpdater) {
super(clientConfig, rule, ping);
this.serverListImpl = serverList;
this.filter = filter;
this.serverListUpdater = serverListUpdater;
if (filter instanceof AbstractServerListFilter) {
((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
}
restOfInit(clientConfig);
}
这里看restOfInit(clientConfig)方法,
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
// turn this off to avoid duplicated asynchronous priming done in BaseLoadBalancer.setServerList()
this.setEnablePrimingConnections(false);
enableAndInitLearnNewServersFeature();
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
这里看updateListOfServers()方法
@VisibleForTesting
public void updateListOfServers() {
List<T> servers = new ArrayList<T>();
if (serverListImpl != null) {
servers = serverListImpl.getUpdatedListOfServers();
LOGGER.debug("List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
if (filter != null) {
servers = filter.getFilteredListOfServers(servers);
LOGGER.debug("Filtered List of Servers for {} obtained from Discovery client: {}",
getIdentifier(), servers);
}
}
updateAllServerList(servers);
}
这里看serverListImpl.getUpdatedListOfServers()方法,点击getUpdatedListOfServers发现他是个接口,我采用的注册中心是eureka,所以我选择DiscoveryEnabledNIWSServerList的实现方法
@Override
public List<DiscoveryEnabledServer> getUpdatedListOfServers(){
return obtainServersViaDiscovery();
}
private List<DiscoveryEnabledServer> obtainServersViaDiscovery() {
List<DiscoveryEnabledServer> serverList = new ArrayList<DiscoveryEnabledServer>();
if (eurekaClientProvider == null || eurekaClientProvider.get() == null) {
logger.warn("EurekaClient has not been initialized yet, returning an empty list");
return new ArrayList<DiscoveryEnabledServer>();
}
EurekaClient eurekaClient = eurekaClientProvider.get();
if (vipAddresses!=null){
for (String vipAddress : vipAddresses.split(",")) {
// if targetRegion is null, it will be interpreted as the same region of client
List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);
for (InstanceInfo ii : listOfInstanceInfo) {
if (ii.getStatus().equals(InstanceStatus.UP)) {
if(shouldUseOverridePort){
if(logger.isDebugEnabled()){
logger.debug("Overriding port on client name: " + clientName + " to " + overridePort);
}
// copy is necessary since the InstanceInfo builder just uses the original reference,
// and we don't want to corrupt the global eureka copy of the object which may be
// used by other clients in our system
InstanceInfo copy = new InstanceInfo(ii);
if(isSecure){
ii = new InstanceInfo.Builder(copy).setSecurePort(overridePort).build();
}else{
ii = new InstanceInfo.Builder(copy).setPort(overridePort).build();
}
}
DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);
des.setZone(DiscoveryClient.getZone(ii));
serverList.add(des);
}
}
if (serverList.size()>0 && prioritizeVipAddressBasedServers){
break; // if the current vipAddress has servers, we dont use subsequent vipAddress based servers
}
}
}
return serverList;
}
发现没,List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);这一行是从eureka取出服务列表,在for循环里
DiscoveryEnabledServer des = new DiscoveryEnabledServer(ii, isSecure, shouldUseIpAddr);
des.setZone(DiscoveryClient.getZone(ii));
serverList.add(des);
把服务列表设置进集合,最后返回这个集合。那么这个集合是被谁持有的呢?这个应该很容易找到BaseLoadBalancer类,BaseLoadBalancer这个类是ILoadBalancer接口的实现类,既然找到了服务列表,那么就可以从这个列表里选一个服务调用了。
总结:ribbon获取服务列表是在第一次调用服务接口的时候将服务列表存入本地缓存,步骤为:
1. 初始化RibbonAutoConfiguration配置类,在这个配置类里将服务调用,服务选择,构建url的Bean装在进IOC容器(LoadBalancerClient/RibbonLoadBalancerClient)
2.在第一次调用服务的时候,给服务生成一个AnnotationConfigApplicationContext上下文,这个上下文了又持有ILoadBalancer对象,ILoadBalancer又是在RibbonClientConfiguration这个配置里被装在进IOC容器的,RibbonClientConfiguration这个配置类是在初始化AnnotationConfigApplicationContext上下文的时候被装载进IOC容器的,所以AnnotationConfigApplicationContext有了ILoadBalancer对象,ILoadBalancer又有服务列表,就可以从服务列表里选择一个服务进行调用了