Ribbon源码笔记

Posted by zjh on May 8, 2023

两年前的笔记了,Ribbon早就停止维护了,挂着纪念一下吧

注册中心如何与Ribbon整合

我们知道,我们在使用RestTemplate发送HTTP请求调用远端服务的时候,Ribbon需要获取请求URL中的服务名去注册中心拿到对应服务的所有实例信息,再负载均衡选择一个实例,用该实例的ip:port替换掉服务名 因此,Ribbon需要具备服务发现的功能,换句话说服务注册中心需要给Ribbon提供API让Ribbon具备针对该注册中心的服务发现的功能 很明显,Ribbon肯定会提供一个公共接口来让注册中心去实现 (面向接口编程),这个接口就是ServerList

package com.netflix.loadbalancer;

public interface ServerList<T extends Server> {

    List<T> getInitialListOfServers();

    List<T> getUpdatedListOfServers();   

}

这里主要看Nacos是如何整合Ribbon的,进入NacosDiscovery包下的spring.factories可以看到,NacosDiscovery利用SpringBoot的自动配置SPI帮我们添加了一个配置类: com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnRibbonNacos
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
public class RibbonNacosAutoConfiguration {

}

这个配置类中并没有任何实现,为啥呢,我们不是说Nacos要提供一个ServerList的实现类嘛? 因为Ribbon为了支持不同服务的负载均衡策略单独定制,就意味着会有多个不同类型的IRule同时存在,如果只用一个容器来放IRule的话,就会很乱,为每一个需要调用服务单独都创建了一个Spring容器,容器实现为AnnotationConfigApplicationContext,并指定SpringBoot容器为其父容器,如果直接在上面的配置类中配置ServerList的话,那ServerList就会在父容器中,

重点在这个自动配置类上的@RibbonClients注解,这个注解指定了一个默认配置类NacosRibbonClientConfiguration 注意:RibbonAutoConfiguration上也有@RibbonClients

@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {}

可以看到,这个注解利用@Import注解向容器中导入了一个RibbonClientConfigurationRegistrar类,而这个类实现了ImportBeanDefinitionRegistrar接口重写了registerBeanDefinitions()方法,该方法就会在容器解析配置类的时候被调用

public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
	    // 拿到注解元信息
		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(RibbonClients.class.getName(), true);
		if (attrs != null && attrs.containsKey("defaultConfiguration")) {
		    // 获取到注解所在类的全类名,即org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
            // 再给全类名前面加一个default.得到新的name = default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
			String name = "default." + metadata.getClassName();
			// registry = DefaultListableBeanFactory
            // attrs.get("defaultConfiguration") = NacosRibbonClientConfiguration.class / null
			registerClientConfiguration(registry, name,
					attrs.get("defaultConfiguration"));
		}
        
	}
	
	private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
			Object configuration) {
	    // 创建一个RibbonClientSpecification的beanDefinition
		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.genericBeanDefinition(RibbonClientSpecification.class);
		// 将name = default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration
        // configuration = NacosRibbonClientConfiguration.class / null设置到其构造方法的参数中
		builder.addConstructorArgValue(name);
		builder.addConstructorArgValue(configuration);
		// 注册beanDefinition
		registry.registerBeanDefinition(name + ".RibbonClientSpecification",
				builder.getBeanDefinition());
	}

}

我们再来看看该RibbonClientSpecification的类定义:

public class RibbonClientSpecification implements NamedContextFactory.Specification {

    private String name;

    private Class<?>[] configuration;
    
    public RibbonClientSpecification(String name, Class<?>[] configuration) {
        this.name = name;
        this.configuration = configuration;
    }
    
    // ...
}

因此,NacosDiscovery整合Reibbon其实就是利用@RibbonClients注解创建一个RibbonClientSpecification这个bean,然后给该bean的name属性赋值为default.org.springframework.cloud.netflix.ribbon.RibbonNacosAutoConfiguration / RibbonAutoConfiguration,同时configuration属性赋值为NacosRibbonClientConfiguration.class / null

SpringCloud对外提供的为RestTemplate添加拦截器的接口

SpringCloud-common包的spring.factories中帮我们自动配置了org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,其结构如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    // 依赖注入容器中所有添加了@LoadBalanced注解的RestTemplate
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();

    // 依赖注入容器中所有的LoadBalancerRequestTransformer(接口内部定义了一个transformRequest方法,之后在使用ip:port替换URL服务名的时候会用到)
    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    // 向容器中添加了一个SmartInitializingSingleton接口的实现类,该实现类的afterSingletonsInstantiated()方法会在实例化所有的非懒加载单实例bean之后调用
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            // 依赖注入RestTemplateCustomizer(下面会配置)
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        // 遍历上面依赖注入进来的每一个RestTemplate,调用RestTemplateCustomizer#customize定制化每一个RestTemplate
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    // 主要是用来实现 ip:port 替换 URL中的服务名的
    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
    }

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {

        // 创建一个LoadBalancerInterceptor用于拦截RestTemplate的每一个请求
        // loadBalancerClient -> Ribbon包下会配置一个实现:RibbonLoadBalancerClient
        @Bean
        public LoadBalancerInterceptor loadBalancerInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }

        // 创建一个RestTemplateCustomizer,即RestTemplate的定制化器,作用是给RestTemplate添加上面配置的loadBalancerInterceptor
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }

    }
    
    // ... 由于没有导入RetryTemplate类,因此下面的配置都不会生效
}

总结:SpringCloud-Common包对外提供了一个接口,可以给容器中所有添加了@LoadBalanced注解的RestTemplate添加一个LoadBalancerInterceptor,该LoadBalancerInterceptor会从容器中拿LoadBalancerClient,对于Ribbon这样的框架来说,只需要实现一个LoadBalancerClient即可对RestTemplate的请求进行拦截增强

Ribbon包提供的自动配置

Ribbon包的spring.factories中只指定了一个自动配置类org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
        name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
        AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
public class RibbonAutoConfiguration {

    // 依赖注入注册中心整合Ribbon所创建的RibbonClientSpecification
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    // Ribbon懒加载的配置信息类
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    // 创建一个springClientFactory并传入了注册中心整合Ribbon所创建的RibbonClientSpecification
    // 该类就用来为每一个服务创建单独的Spring容器
    // 注意:
    // 1. 其父类为NamedContextFactory
    // 2. 其构造方法中会指定父类的defaultConfigType属性为RibbonClientConfiguration.class
    // 3. 其构造方法中会指定propertySourceName = "ribbon",propertyName = "ribbon.client.name"
    @Bean
    @ConditionalOnMissingBean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

    // 创建一个RibbonLoadBalancerClient,该bean就会被添加到LoadBalancerInterceptor中,执行Ribbon对RestTemplate的拦截逻辑
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    // 处理ribbon是否懒加载,默认为懒加载(这也是为啥Ribbon默认对一个服务的第一次调用很慢的原因)
    @Bean
    @ConditionalOnProperty("ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }
    
    // ...
}

总结:Ribbon包下的自动配置类主要帮我们做了三件事:

  1. 创建了一个SpringClientFactory并注入了注册中心整合Ribbon所创建的RibbonClientSpecification
  2. 创建一个RibbonLoadBalancerClient,该bean就会被添加到LoadBalancerInterceptor中,执行Ribbon对RestTemplate的拦截逻辑
  3. 处理Ribbon是否懒加载

Ribbon对RestTemplate的拦截

我们知道SpringCloud-Common包向RestTemplate中添加了一个拦截器,因此RestTemplate在发送HTTP请求之前肯定会先走这个拦截器的逻辑,进入LoadBalancerInterceptor.interceptor()方法

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    // Ribbon自动配置的 RibbonLoadBalancerClient
    private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;
    
    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
                                        final ClientHttpRequestExecution execution) throws IOException {
        // 拿到URI
        final URI originalUri = request.getURI();
        // 从URI中拿到服务名
        String serviceName = originalUri.getHost();
        // 调用RibbonLoadBalancerClient的execute()方法
        return this.loadBalancer.execute(serviceName,
                this.requestFactory.createRequest(request, body, execution));
    }

}

可以看到,拦截逻辑的核心就是调用RibbonLoadBalancerClient的execute()方法

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
		throws IOException {
	return execute(serviceId, request, null);
}

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
        throws IOException {
    // 最主要的逻辑,拿到负载均衡器,负载均衡器里面就有对应服务在注册中心的所有实例信息
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    
    // 通过负载均衡算法选择一个服务实例
    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);
}

整个Ribbon最主要也是最核心的逻辑就是在第一行代码中:getLoadBalancer(serviceId)。这里我们可以抛出三个问题:

  • 负载均衡器是什么?
  • 负载均衡器是什么时候创建的?
  • 负载均衡器里面的服务实例是什么时候从注册中心获取到的? ```java protected ILoadBalancer getLoadBalancer(String serviceId) { private SpringClientFactory clientFactory;

    public RibbonLoadBalancerClient(SpringClientFactory clientFactory) { this.clientFactory = clientFactory; }

    // clientFactory就是创建RibbonLoadBalancerClient时传入的SpringClientFactory(注意SpringClientFactory在创建的时候还注入了注册中心整合Ribbon所创建的RibbonClientSpecification) return this.clientFactory.getLoadBalancer(serviceId); }

public ILoadBalancer getLoadBalancer(String name) { return getInstance(name, ILoadBalancer.class); }

@Override public C getInstance(String name, Class type) { C instance = super.getInstance(name, type); if (instance != null) { return instance; } IClientConfig config = getInstance(name, IClientConfig.class); return instantiateWithConfig(getContext(name), type, config); }

public T getInstance(String name, Class type) { // name = URI中指定的服务名 AnnotationConfigApplicationContext context = getContext(name); try { return context.getBean(type); // 从容器中获取 ILoadBalancer.class 类型的bean } catch (NoSuchBeanDefinitionException e) { // ignore } return null; }

protected AnnotationConfigApplicationContext getContext(String name) { // Ribbon会为每一个服务都创建一个容器 // key为服务名,value为该服务所对应的容器 private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

// 如果该当前服务所对应的容器还没有创建,就创建,否则就直接返回
if (!this.contexts.containsKey(name)) {
    synchronized (this.contexts) { // 双重检测锁
        if (!this.contexts.containsKey(name)) {
            this.contexts.put(name, createContext(name));
        }
    }
}
return this.contexts.get(name); } ```

createContext(name)为服务创建容器

public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {

    // Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,构造方法指定 = "ribbon"
    private final String propertySourceName;

    // Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,构造方法指定 = "ribbon.client.name"
    private final String propertyName;

    // 用来保存每一个服务的容器
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    // Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory的时候,调用setConfigurations(this.configurations)传入进来的注册中心配置的RibbonClientSpecification
    private Map<String, C> configurations = new ConcurrentHashMap<>();

    // 实现了ApplicationContextAware,拿到SpringBoot容器,作为每个服务容器的父容器
    private ApplicationContext parent;

    // Ribbon包的RibbonAutoConfiguration中创建SpringClientFactory时,构造方法指定 = RibbonClientConfiguration.class
    private Class<?> defaultConfigType;

    /**
     * public SpringClientFactory() {
     *     super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
     * }
     */
    // Ribbon自动配置类中创建SpringClientFactory时会指定属性值
    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
                               String propertyName) {
        this.defaultConfigType = defaultConfigType;
        this.propertySourceName = propertySourceName;
        this.propertyName = propertyName;
    }

    @Override
    public void setApplicationContext(ApplicationContext parent) throws BeansException {
        this.parent = parent;
    }

    protected AnnotationConfigApplicationContext createContext(String name) {
        // 创建一个空容器(注意,用的无参构造函数,容器并没有刷新)
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();

        // 遍历每一个RibbonClientSpecification,此时就会遍历到之前Nacos自动配置所导入的RibbonClientSpecification,其内部的configuration属性 = NacosRibbonClientConfiguration
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                // entry.getValue() -> RibbonClientSpecification -> RibbonClientSpecification.getConfiguration() - > 
                //              从RibbonClientSpecification中拿到configuration,里面就有Nacos整合Ribbon注入的NacosRibbonClientConfiguration
                // NacosRibbonClientConfiguration才是真正自动配置ServerList的配置类,也就说,每一个服务容器都有一个ServerList
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    // 注册NacosRibbonClientConfiguration
                    context.register(configuration);
                }
            }
        }
        // 注册 defaultConfigType = RibbonClientConfiguration
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        // 服务容器的Environment指定"ribbon.client.name" = name = 服务名
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.<String, Object>singletonMap(this.propertyName, name)));
        if (this.parent != null) {
            // 设置服务容器的父容器为SpringBoot容器
            context.setParent(this.parent);
            context.setClassLoader(this.parent.getClassLoader());
        }
        context.setDisplayName(generateDisplayName(name));
        // 刷新容器,此时就会解析NacosRibbonClientConfiguration和RibbonClientConfiguration配置类
        context.refresh();
        return context;
    }
}

我们直接来看服务容器所解析的这两个配置类的内容: (1)先看RibbonClientConfiguration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
		RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {

    /**
     * Ribbon client default connect timeout.
     */
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;

    /**
     * Ribbon client default read timeout.
     */
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    /**
     * Ribbon client default Gzip Payload flag.
     */
    public static final boolean DEFAULT_GZIP_PAYLOAD = true;

    // @Value("${ribbon.client.name}"),在调用createContext创建当前容器的时候就已经在服务容器的Environment指定"ribbon.client.name" = name = 服务名
    @RibbonClientName
    private String name = "client";

    // TODO: maybe re-instate autowired load balancers: identified by name they could be
    // associated with ribbon clients

    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        // 创建一个IClientConfig的默认实现,用来保存当前服务的Ribbon参数
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        // 加载配置:1.设置clientName=name=服务名 2.加载ribbon的默认参数 3.拿到我们在yaml中配置的自定义参数(例如指定服务使用的IRule)对默认参数进行覆盖
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
        return config;
    }

    @Bean
    @ConditionalOnMissingBean // 条件注解,如果我们自己配置了负载均衡算法,就不会再帮我们配置
    public IRule ribbonRule(IClientConfig config) {
        // 如果我们在yaml中指定了服务使用的负载均算法就会走到这里(因此,如果手动配置了全局的负载均衡算法,yaml中的手动配置是不会生效的,要么全部全局,要么全部手动配置)
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            // 这里会通过yaml中配置的全类名反射创建负载均衡算法对象
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        // 可以看到,默认配置的负载均衡算法是ZoneAvoidanceRule
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

    @Bean
    @ConditionalOnMissingBean
    public IPing ribbonPing(IClientConfig config) {
        if (this.propertiesFactory.isSet(IPing.class, name)) {
            return this.propertiesFactory.get(IPing.class, config, name);
        }
        return new DummyPing();
    }

    // 这里创建的ServerList是针对我们直接在yaml中手动指定被调用服务的地址的方式(Ribbon可以独立于注册中心使用)
    @Bean
    @ConditionalOnMissingBean // 条件注解
    public ServerList<Server> ribbonServerList(IClientConfig config) {
        if (this.propertiesFactory.isSet(ServerList.class, name)) {
            return this.propertiesFactory.get(ServerList.class, config, name);
        }
        ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
        serverList.initWithNiwsConfig(config);
        return serverList;
    }
    

    // LoadBalancer的自动配置,之前说过的最重要的一步getLoadBalancer()就是获取它
    @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);
        }
        // 这里的serverList由NacosDiscovery包中的NacosRibbonClientConfiguration配置
        return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                serverListFilter, serverListUpdater);
    }
    // ...
}

(2)再来看NacosRibbonClientConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnRibbonNacos
public class NacosRibbonClientConfiguration {
    
	@Bean
	@ConditionalOnMissingBean
	public ServerList<?> ribbonServerList(IClientConfig config,
			NacosDiscoveryProperties nacosDiscoveryProperties) {
	    // 创建一个NacosServerList并传入nacos在application.yaml中的配置
		NacosServerList serverList = new NacosServerList(nacosDiscoveryProperties);
        // 这个里面只有一步:this.serviceId = iClientConfig.getClientName(),给NacosServerList中的serviceId属性设置为服务名
		serverList.initWithNiwsConfig(config);
		return serverList;
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosServerIntrospector nacosServerIntrospector() {
		return new NacosServerIntrospector();
	}

}

我们来看看负载均衡器ZoneAwareLoadBalancer创建的过程中都做了什么

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) {
    // supper主要是赋值
    super(clientConfig, rule, ping);
    this.serverListImpl = serverList;
    this.filter = filter;
    this.serverListUpdater = serverListUpdater;
    if (filter instanceof AbstractServerListFilter) {
        ((AbstractServerListFilter) filter).setLoadBalancerStats(getLoadBalancerStats());
    }
    // 重点
    restOfInit(clientConfig);
}

void restOfInit(IClientConfig clientConfig) {
    boolean primeConnection = this.isEnablePrimingConnections();
    this.setEnablePrimingConnections(false);
    // 创建一个线程,默认每隔30s去NacosClient的serviceInfoMap中同步最新的服务节点信息(可以在配置文件配置ServerListRefreshInterval)
    // 线程内就是调用updateListOfServers():将同步到的最新的服务节点信息赋值给当前ZoneAwareLoadBalancer的allServerList
    enableAndInitLearnNewServersFeature();
    // 可以看到,在创建同步线程之后,会立刻从serviceInfoMap中获取一次服务节点信息
    updateListOfServers();
    if (primeConnection && this.getPrimeConnections() != null) {
        this.getPrimeConnections().primeConnections(getReachableServers());
    }
    this.setEnablePrimingConnections(primeConnection);
}

@VisibleForTesting
public void updateListOfServers() {
    List<T> servers = new ArrayList<T>();
    if (serverListImpl != null) {
        // serverListImpl = NacosServerList(注意:NacosServerList是一个服务容器一个的,其serviceId属性代表服务名)
        // ServerList<T> serverListImpl
        // 调用NacosServerList获取到对应服务的节点信息
        servers = serverListImpl.getUpdatedListOfServers();
        if (filter != null) {
            servers = filter.getFilteredListOfServers(servers);
        }
    }
    updateAllServerList(servers);
}

@Override
public List<NacosServer> getUpdatedListOfServers() {
    return getServers();
}

private List<NacosServer> getServers() {
    try {
        String group = discoveryProperties.getGroup();
        // 拿到NacosNamingService,调用其selectInstances()
        // selectInstances(String serviceName, String groupName, boolean healthy),注意这里传入的healthy = true,表示Ribbon只会同步健康的节点信息
        List<Instance> instances = discoveryProperties.namingServiceInstance()
                    .selectInstances(serviceId, group, true);
        // 将拿到的instance封装成一个NacosServer
        return instancesToServerList(instances);
    } catch (Exception e) {
    }
}

// NacosNamingService.class  这里其实已经进入Nacos的服务发现的逻辑了
@Override
public List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,boolean subscribe) throws NacosException {

    ServiceInfo serviceInfo;
    if (subscribe) { // 传入的subscribe默认都会true,这里就会从serviceInfoMap中拿数据,没有就从注册中心拿并创建NacosClient的同步线程
        serviceInfo = hostReactor.getServiceInfo(NamingUtils.getGroupedName(serviceName, groupName),StringUtils.join(clusters, ","));
    } else {
        serviceInfo = hostReactor.getServiceInfoDirectlyFromServer(NamingUtils.getGroupedName(serviceName, groupName),StringUtils.join(clusters, ","));
    }
    // 过滤掉不健康的实例,仅保留健康的实例
    return selectInstances(serviceInfo, healthy);
}

至此,ZoneAwareLoadBalancer的创建就结束了,之后随着服务容器Refresh()的结束,createContext()也就结束了,会到调用createContext()的地方

protected AnnotationConfigApplicationContext getContext(String name) {
    if (!this.contexts.containsKey(name)) {
        synchronized (this.contexts) {
            if (!this.contexts.containsKey(name)) {
                // createContext()结束
                this.contexts.put(name, createContext(name)); 
            }
        }
    }
    // 拿到服务所对应的服务容器
    return this.contexts.get(name);
}

其实也可以思考一下,如果我们提前将服务的context创建好,是不是在第一次调用的时候,就不需要再去走上面的创建逻辑了,因为contexts中已经有对应服务的context了,这里直接就返回(这个就是之后会说的关闭Ribbon懒加载的逻辑)

public <T> T getInstance(String name, Class<T> type) {
    // 拿到服务对应的容器
    AnnotationConfigApplicationContext context = getContext(name);
    try {
        // 从容器中拿bean,type = ILoadBalancer.class,拿到的就是ZoneAwareLoadBalancer
        return context.getBean(type);
    }
    catch (NoSuchBeanDefinitionException e) {
        // ignore
    }
    return null;
}

再回到我们之前的execute()方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
        // 从调用服务所对应的容器中拿到ZoneAwareLoadBalancer
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		// 根据负载均衡算法从ZoneAwareLoadBalancer的allServerList中选择一个服务节点
		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);
	}

现在我们再来看,是如何从ZoneAwareLoadBalancer中选节点的

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
    if (loadBalancer == null) {
        return null;
    }
    // Use 'default' on a null hint, or just pass it on?
    // hint默认传的是null,因此传入的参数 = "default"
    return loadBalancer.chooseServer(hint != null ? hint : "default");
}

@Override
public Server chooseServer(Object key) {
    // 我们一般不会配置zone,因此第二个条件getAvailableZones().size() <= 1成立
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
        return super.chooseServer(key);
    }
    // ... 这里还有很多关于ZoneRegion的逻辑,我们一般不会用这个东西
    return super.chooseServer(key);
}

public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            // 关键,调用rule.choose(key),key = "default"
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}

// 这里我们只看Ribbon默认配置的rule -> ZoneAvoidanceRule
@Override
public Server choose(Object key) {
    // 拿到ZoneAwareLoadBalancer
    ILoadBalancer lb = getLoadBalancer();
    // lb.getAllServers() -> 返回allServerList
    Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
    if (server.isPresent()) {
        return server.get();
    } else {
        return null;
    }
}

public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
    // Zone的一些过滤,我们不会用这些东西
    List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
    if (eligible.size() == 0) {
        return Optional.absent();
    }
    // 因此直接走轮训的逻辑,可以看到,默认的负载均衡算法类,在不使用zone的情况下,就是轮训
    return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}

再次回到之前的execute()

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
    ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    // 通过负载均衡算法已经选择出来了一个服务节点
    Server server = getServer(loadBalancer, hint);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    }
    // 封装成一个RibbonServer
    RibbonServer ribbonServer = new RibbonServer(serviceId, server,
        isSecure(server, serviceId),
        serverIntrospector(serviceId).getMetadata(server));

    return execute(serviceId, ribbonServer, request);
}

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
    // ...

    try {
        // 这一步,这个request是什么时候创建的呢?
        T returnVal = request.apply(serviceInstance);
        return returnVal;
    } catch (IOException ex) {
        // ..
    }
    return null;
}

再之前调用LoadBalancerInterceptor.intercept()方法的时候,我们通过requestFactory创建了一个request

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
			final ClientHttpRequestExecution execution) throws IOException {
    final URI originalUri = request.getURI();
    String serviceName = originalUri.getHost();
    // loadBalancer -> RibbonLoadBalancerClient
    return this.loadBalancer.execute(serviceName,
				// 创建Request(注意不是HttpRequest)
				this.requestFactory.createRequest(request, body, execution));
}

public LoadBalancerRequest<ClientHttpResponse> createRequest(
        final HttpRequest request, final byte[] body,
        final ClientHttpRequestExecution execution) {
    // lambda表达式,创建的是一个LoadBalancerRequest,重写了里面的apply()方法
    return instance -> {
        HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
                this.loadBalancer);
        if (this.transformers != null) {
            // 使用transformRequest将serviceRequest中带服务名的URL中的服务名替换为ip:port
            for (LoadBalancerRequestTransformer transformer : this.transformers) {
                serviceRequest = transformer.transformRequest(serviceRequest,
                    instance);
            }
        }
        // 继续往后执行(这之后就是调用下一个拦截器了,就是RestTemplate的逻辑了)
        return execution.execute(serviceRequest, body);
    };
}

因此,requset.app()实际上就是调用上面的方法,上面的方法主要做了两件事,一个就是替换URL中的服务名为选中的ip:port,另一个就是继续调用

Ribbon实现非懒加载儿原理

至此,整个完整的调用逻辑也就结束了,再来说说Ribbon的懒加载问题 我们知道,通过Ribbon第一次调用一个服务是很慢的,解决方式是在yaml中配置ribbon.eager-load.enabled=true,为什么配置了就能解决第一次调用慢的问题呢? 我们再进入Ribbon包的自动配置类

// RibbonAutoConfiguration
@Bean
// 可以看到,通过条件注解,如果我们在配置文件中配置ribbon.eager-load.enabled=true,那么就会在容器中添加一个Bean:RibbonApplicationContextInitializer
@ConditionalOnProperty("ribbon.eager-load.enabled")
public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
    // 创建bean的时候,会传入SpringClientFactory以及我们在配置文件中配置的非懒加载的服务
    return new RibbonApplicationContextInitializer(springClientFactory(),
        ribbonEagerLoadProperties.getClients());
}

查看这个类的类图可以发现,RibbonApplicationContextInitializer实现了ApplicationListener并且关注ApplicationReadyEvent事件,springboot容器刷新完之后就会发布该事件:

refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context); // 发布ApplicationStartedEvent事件

我们再来看其onApplicationEvent方法

@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
    initialize();
}

protected void initialize() {
    if (clientNames != null) {
        // 遍历我们在配置文件中指定的服务名
        for (String clientName : clientNames) {
            // 调用getContext()
            this.springClientFactory.getContext(clientName);
        }
    }
}

可以看到,关闭懒加载其实就是在SpringBoot容器刷新了之后去将指定的服务容器创建好,同时服务容器刷新的过程中就会去注册中心拉取服务节点信息

NacosDiscovery包做的事情:
NacosDiscovery包的spring.factories中导入了RibbonNacosAutoConfiguration,上面有@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
实际上是向容器中添加了一个RibbonClientSpecification类,这个类的configuration属性 = NacosRibbonClientConfiguration.class,因此可以看到,实际上NacosRibbonClientConfiguration
不会被Sringboot的容器解析,因为没有创建其beanDefinition,而是直接设置到RibbonClientSpecification这个bean中,这个bean并不是配置类

注意:NacosRibbonClientConfiguration中帮我们配置了ServerList = NacosServerList

Ribbon包做的事情:
Ribbon包的spring.factories中导入了RibbonAutoConfiguration,里面首先通过@Autowired直接帮我们注入了所有的RibbonClientSpecification(上面创建的)
同时帮我们配置了SpringClientFactory(构造函数中还会指定defaultConfigType = RibbonClientConfiguration)并将所有的RibbonClientSpecification设置到其中
同时还帮我们配置了一个LoadBalancerClient的实现RibbonLoadBalancerClient,同时创建RibbonLoadBalancerClient的时候传入了上一步配置的SpringClientFactory

common包做得事情
common包的spring.factories中导入了LoadBalancerAutoConfiguration,里面首先通过@Autowired+@LoadBalanced拿到容器中所有标注了@LoadBalanced注解的RestTemplate
1. 帮我们配置了一个LoadBalancerInterceptor并且从容器中拿到loadBalancerClient(上面ribbon包有帮我们配置RibbonLoadBalancerClient并设置到其中
2. 帮我们配置了一个RestTemplateCustomizer即RestTemplate的定制化器,这个定制化器中的void customize(RestTemplate restTemplate)方法会给传入的restTemplate设置上一步配置好的loadBalancerInterceptor
3. 帮我们配置了一个SmartInitializingSingleton的实现类并重写了afterSingletonsInstantiated()方法,在容器启动时,在所有非懒加载的单实例bean的getBean()方法调用完之后会调用其afterSingletonsInstantiated(),方法内会拿到上一步配置的RestTemplateCustomizer调用其customize方法,方法的参数就是当前配置类中注入的所有标注了@LoadBalanced注解的RestTemplate
因此,实际上common包配置类的作用就是给所有标注了@LoadBalanced注解的RestTemplate中设置一个LoadBalancerInterceptor,这个LoadBalancerInterceptor在创建的时候引用了RibbonLoadBalancerClient

因此整个结构就比较明确了:
每一个@LoadBalanced注解的RestTemplate中有一个LoadBalancerInterceptor,这个LoadBalancerInterceptor中有RibbonLoadBalancerClient,RibbonLoadBalancerClient中有SpringClientFactory,SpringClientFactory中有RibbonClientSpecification和RibbonClientConfiguration,RibbonClientSpecification中有NacosRibbonClientConfiguration,NacosRibbonClientConfiguration中又有ServerList的nacos实现NacosServerList,NacosServerList中引用了NacosDiscoveryProperties,NacosDiscoveryProperties中依赖注入了nacosServiceManager,nacosServiceManager中就可以拿到NacosNamingService