十四.SpringCloud源码剖析-Ribbon的初始化配置

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 前面我们分析了Eureka的源码,接下来这一章我们来研究一下Ribbon,本篇文章主要是对Ribbon的相关组件做一个认识,以及它的初始化配置做一个分析。

前言

前面我们分析了Eureka的源码,接下来这一章我们来研究一下Ribbon,本篇文章主要是对Ribbon的相关组件做一个认识,以及它的初始化配置做一个分析。

Ribbon的自动配置RibbonAutoConfiguration

在spring-cloud-netflix-ribbon-2.0.1.RELEASE.jar包的META-INF目录中有这么一个文件spring.factories,内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration

当SpringBoot程序启动,自动配置扫描会加载该类RibbonAutoConfiguration注册到Spring容器,于是该自动配置类中的配置生效,那么该类配置了什么呢?

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Biju Kunjummen
 */
@Configuration
//条件
@ConditionalOnClass({
   
    IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
//在EurekaClientAutoConfiguration之后配置
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
//在LoadBalancerAutoConfiguration之前配置
@AutoConfigureBefore({
   
   LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
//载入迫切加载配置
@EnableConfigurationProperties({
   
   RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
   
   
    //RibbonClient客户端指定的配置,有多少个Ribbon客户端这里就会有多少个RibbonClientSpecification
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    //饥饿加载配置
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    @Bean
    public HasFeatures ribbonFeature() {
   
   
        return HasFeatures.namedFeature("Ribbon", Ribbon.class);
    }

    // 创建RibbonClient的ApplicationContext上下文,并创建RibbonClient相关组件如IClient、ILoadbalancer等
    @Bean
    public SpringClientFactory springClientFactory() {
   
   
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
    //注册了LoadBalancerClient ,负载均衡客户端,很重要的一个类
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
   
   
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    //创建LoadBalancedRetryPolicy的工厂,负载均衡重试功能
    @Bean
    //重试条件,必须配置RetryTemplate
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
   
   
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }

    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
   
   
        return new PropertiesFactory();
    }

    //如果配置了eager-load饥饿加载,就注册RibbonApplicationContextInitializer 上下文初始化对象,
    //然后在ApplicationReadyEvent事件之后会立马初始化上下文
    @Bean
    @ConditionalOnProperty(value = "ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
   
   
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }

    //Ribbon的http请求配置
    @Configuration
    @ConditionalOnClass(HttpRequest.class)
    @ConditionalOnRibbonRestClient
    protected static class RibbonClientHttpRequestFactoryConfiguration {
   
   

        @Autowired
        private SpringClientFactory springClientFactory;

        //RestTemplate定制器
        @Bean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory) {
   
   
            return restTemplate -> restTemplate.setRequestFactory(ribbonClientHttpRequestFactory);
        }

        //注册RibbonClientHttpRequestFactory ,听过它来创建ClientHttpRequest用来发http请求的,后续Ribbon执行流程中会用到ClientHttpRequest
        @Bean
        public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
   
   
            return new RibbonClientHttpRequestFactory(this.springClientFactory);
        }
    }

    //TODO: support for autoconfiguring restemplate to use apache http client or okhttp

    @Target({
   
    ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(OnRibbonRestClientCondition.class)
    @interface ConditionalOnRibbonRestClient {
   
    }

    private static class OnRibbonRestClientCondition extends AnyNestedCondition {
   
   
        public OnRibbonRestClientCondition() {
   
   
            super(ConfigurationPhase.REGISTER_BEAN);
        }

        @Deprecated //remove in Edgware"
        @ConditionalOnProperty("ribbon.http.client.enabled")
        static class ZuulProperty {
   
   }

        @ConditionalOnProperty("ribbon.restclient.enabled")
        static class RibbonProperty {
   
   }
    }
}

解释一下:

  • RibbonClient: Ribbon的客户端,比如A服务调用了B服务和C服务,那么Ribbon就会创建B服务的客户端和C服务的客户端

  • SpringClientFactory:用来给Ribbon客户端创建上下文和配置,它为每个客户机名称创建一个Spring ApplicationContext,并创建好RibbonClient客户端相关的bean和配置,如:ILoadBalancer,IClientConfig,RibbonLoadBalancerContext。注意:是会为每个客户端都会做一个套配置

  • LoadBalancerClient :负载均衡客户端,RibbonLoadBalancerClient是对LoadBalancerClient的实现,exec方法包含了负载均衡的功能,Ribbon做负载均衡时用的就是它【重要】
  • RibbonEagerLoadProperties:用来加载“饥饿加载”配置ribbon.eager-load.enabled,指定些哪些RibonClient需要迫切初始化
  • RibbonApplicationContextInitializer:配置了ribbon.eager-load.enabled的客户端在系统启动的时候就会初始化RibbonClient上下文和配置信息,默认情况下RibbonClient的上下文和配置是在调用的时候才进行初始化的,配置了饥饿加载,项目刚启动时可以减少服务调用失败的情况。

  • PropertiesFactory:加载配置的工厂,主要负责加载Ribbon客户端相关的配置类:ILoadBalancer,IPing,IRule,,ServerList,ServerListFilter:

      public PropertiesFactory() {
         
         
          classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
          classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
          classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
          classToProperty.put(ServerList.class, "NIWSServerListClassName");
          classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
      }
    

    SpringClientFactory Ribbon上下文初始化

    SpringClientFactory用来给Ribbon客户端创建上下文和配置,它为每个客户机名称创建一个Spring ApplicationContext,并创建好RibbonClient客户端相关的bean和配置,如:ILoadBalancer,IClientConfig,RibbonLoadBalancerContext。
    ```java
    public class SpringClientFactory extends NamedContextFactory {

    static final String NAMESPACE = "ribbon";

    public SpringClientFactory() {

      //ribbon的客户端配置RibbonClientConfiguration,Ribbon默认配置类的类型
      super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    

    }

    /**

      这个方法的作用是从Ribbon的客户端上下文对象中(AnnotationConfigApplicationContext)根据name和type获取一个Bean的实例,
      比如:IloadBalancer , 
    
    • Get the rest client associated with the name.
    • @throws RuntimeException if any error occurs
      */
      public > C getClient(String name, Class clientClass) {
      return getInstance(name, clientClass);
      }

      /**
      根据Ribbon客户端的服务名,获取客户端的ILoadBalancer对象

    • Get the load balancer associated with the name.
    • @throws RuntimeException if any error occurs
      */
      public ILoadBalancer getLoadBalancer(String name) {
      return getInstance(name, ILoadBalancer.class);
      }

      /**
      根据Ribbon客户端的服务名,获取客户端的配置对象,默认实现是DefaultClientConfigImpl
      就是家长Ribbon开头的配置

    • Get the client config associated with the name.
    • @throws RuntimeException if any error occurs
      */
      public IClientConfig getClientConfig(String name) {
      return getInstance(name, IClientConfig.class);
      }

      /**
      根据Ribbon客户端的服务名,获取RibbonLoadBalancerContext 负载均衡器的上下文对象

    • Get the load balancer context associated with the name.
    • @throws RuntimeException if any error occurs
      */
      public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
      return getInstance(serviceId, RibbonLoadBalancerContext.class);
      }

      //根据配置,创建一个实例,clazz是要创建的对象的字节码,config是对象需要的 配置
      static C instantiateWithConfig(Class clazz, IClientConfig config) {
      return instantiateWithConfig(null, clazz, config);
      }
      //实例化对象,根据配置,创建一个实例
      static C instantiateWithConfig(AnnotationConfigApplicationContext context,

                                   Class<C> clazz, IClientConfig config) {
      

      C result = null;

      try {

       //获取构造器
       Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
       //创建实例
       result = constructor.newInstance(config);
      

      } catch (Throwable e) {

       // Ignored
      

      }

      if (result == null) {

       //如果创建失败,调用无参构造创建实例
       result = BeanUtils.instantiate(clazz);
       //初始化配置
       if (result instanceof IClientConfigAware) {
           ((IClientConfigAware) result).initWithNiwsConfig(config);
       }
       //自动注入
       if (context != null) {
           context.getAutowireCapableBeanFactory().autowireBean(result);
       }
      

      }

      return result;
      }

      //获取实例,name是Ribbon客户端的服务名,type是类型,
      //创建IClientConfig,ILoadBalancer,getClient,RibbonLoadBalancerContext都要调用这个方法
      @Override
      public C getInstance(String name, Class type) {
      //调用父类NamedContextFactory的getInstance实例化对象,如果为空,就根据IClientConfig客户端配置创建一个实例
      C instance = super.getInstance(name, type);
      if (instance != null) {

       return instance;
      

      }
      IClientConfig config = getInstance(name, IClientConfig.class);
      //得到获取上下文对象,然后创建实例
      return instantiateWithConfig(getContext(name), type, config);
      }

      //获取Spring上下文对象
      @Override
      protected AnnotationConfigApplicationContext getContext(String name) {
      return super.getContext(name);
      }

这里配置了什么呢?
- 1.SpringClientFactory中提供了 getClient,getLoadBalancer,getClientConfig,getLoadBalancerContext 等方法,
- 2.这些方法都需要调用`getInstance`得到相关实例,而`getInstance`方法中向会去从super.getInstance(其实就是通过AnnotationConfigApplicationContext.getBean 上下文对象)获取Ben
- 3.如果`super.getInstance`获取不到bean,就先调用getContext方法得到上下文对象,然后调用`instantiateWithConfig`方法根据IClientConfig配置使用反射创建一个实例:`instantiateWithConfig(getContext(name), type, config);`

我们看下其父类NamedContextFactory做了些什么事情


```java
//父类源码
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {

    public interface Specification {
        String getName();

        Class<?>[] getConfiguration();
    }
    //保存上下文对象的map
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    //保存配置的map
    private Map<String, C> configurations = new ConcurrentHashMap<>();



    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
            String propertyName) {
            //defaultConfigType是默认的配置类型
        this.defaultConfigType = defaultConfigType;
        this.propertySourceName = propertySourceName;
        this.propertyName = propertyName;
    }
    //设置上下文对象
    @Override
    public void setApplicationContext(ApplicationContext parent) throws BeansException {
        this.parent = parent;
    }
    //添加配置到configurations map中,
    public void setConfigurations(List<C> configurations) {
        for (C client : configurations) {
            this.configurations.put(client.getName(), client);
        }
    }
    //获取上下文名字集合
    public Set<String> getContextNames() {
        return new HashSet<>(contexts.keySet());
    }
    //销毁,上下文对象关闭,清理上下文map
    @Override
    public void destroy() {
        Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
        for (AnnotationConfigApplicationContext context : values) {
            // This can fail, but it never throws an exception (you see stack traces
            // logged as WARN).
            context.close();
        }
        this.contexts.clear();
    }
    //【重要】根据Ribbon客户端名字获取上下文对象,通过这个上下文对象就能获取Ribbon客户端所需要的Bean(IClicentConfig,ILoadBalancer )
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    //如果contexts map中没有当前Ribbon客户端对应的上下文,就调用createContext先创建
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        //根据名字获取contexts中的上下对象
        return this.contexts.get(name);
    }

    //根据名字创建上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //注册指定的的RibbonClient的配置类,比如通过@RibbonClient注解指定的配置类
        if (this.configurations.containsKey(nam) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        //注册所有default开头的Ribbon配置到上下文对象,即所有RibbonClient的默认配置
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        //注册了默认的配置类,defaultConfigType就是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);
        }
        context.setDisplayName(generateDisplayName(name));
        //刷新容器
        context.refresh();
        return context;
    }

    protected String generateDisplayName(String name) {
        return this.getClass().getSimpleName() + "-" + name;
    }
    //从上下文对象中获取实例
    public <T> T getInstance(String name, Class<T> type) {
        //根据名字先得到上下文对象,从上下文对象中根据type获取Bean
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }
    //从上下文对象中获取实例
    public <T> Map<String, T> getInstances(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
        }
        return null;
    }

NamedContextFactory主要提供了

  • getContext(String name) : 根据客户端名字从 Map<String, AnnotationConfigApplicationContext> contexts 中获取上下文对象的方法,如果contexts中没有上下文会调用createContext(name) 创建上下文,然后放入contexts中再返回
  • createContext(String name) :根据客户端名字创建上下文对象,方法中分别注册了RibbonClient客户端配置类,default开头的所有RibbonClient的默认配置,以及RibbonClientConfiguration全局默认配置,然后刷新容器,返回上下文对象
  • getInstance(String name, Class type) :根据名字和类型返回实例,方法先根据名字得到上下文对象然后从上下文对象中获取Bean

这里有两个问题,一是configurations配置是从哪儿来的,二是createContext在什么时候调用?

一是configurations配置是从哪儿来的?这个我们看哪儿在给这个Map添加数据

    //存储配置的map
    private Map<String, C> configurations = new ConcurrentHashMap<>();

    //添加配置
    public void setConfigurations(List<C> configurations) {
   
   
        for (C client : configurations) {
   
   
            //map添加配置
            this.configurations.put(client.getName(), client);
        }
    }

这个setConfigurations方法是在RibbonAutoConfiguration配置被调用

public class RibbonAutoConfiguration {
   
   

    //注入RibbonClientSpecification
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();

    ...省略...

    @Bean
    public SpringClientFactory springClientFactory() {
   
   
        SpringClientFactory factory = new SpringClientFactory();
        //给SpringClientFactory 指定配置集合
        factory.setConfigurations(this.configurations);
        return factory;
    }

RibbonClientConfigurationRegistrar Ribbon客户端配置注册器

configurations 是在RibbonAutoConfiguration 配置类中注入进来的,那么RibbonClientSpecification是在什么时候注册到Spring容器中的呢?有个RibbonClientConfigurationRegistrar专门负责注册RibbonClientConfiguration

//RibbonClientConfiguration注册器
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
   
   

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
   
   
        //获取@RibbonClients的注解属性,value对应的是@RibbonClient
        Map<String, Object> attrs = metadata.getAnnotationAttributes(
                RibbonClients.class.getName(), true);
        if (attrs != null && attrs.containsKey("value")) {
   
   
            AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
            for (AnnotationAttributes client : clients) {
   
   
                //获取到每一个@RibbonClient,从注解中获取configuration配置,进行注册
                registerClientConfiguration(registry, getClientName(client),
                        client.get("configuration"));
            }
        }
        //这里是判断是否有默认的配置@RibbonClients注解的defaultConfiguration
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
   
   
            String name;
            if (metadata.hasEnclosingClass()) {
   
   
                name = "default." + metadata.getEnclosingClassName();
            } else {
   
   
                name = "default." + metadata.getClassName();
            }
            //注册配置
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }
        //这里获取的是@RibbonClient的configuration配置
        Map<String, Object> client = metadata.getAnnotationAttributes(
                RibbonClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
   
   
            registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }

    private String getClientName(Map<String, Object> client) {
   
   
        if (client == null) {
   
   
            return null;
        }
        String value = (String) client.get("value");
        if (!StringUtils.hasText(value)) {
   
   
            value = (String) client.get("name");
        }
        if (StringUtils.hasText(value)) {
   
   
            return value;
        }
        throw new IllegalStateException(
                "Either 'name' or 'value' must be provided in @RibbonClient");
    }

    //注册了客户端的配置RibbonClientSpecification
    private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration) {
   
   
        //生成RibbonClientSpecification
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }

}

这里解析了@RibbonClients标签和@RibbonClient 标签,得到相关配置类后注册到Spring容器中,生成RibbonClientSpecification对象,那么在RibbonAutoConfiguration 中就可以@Autowired进来,然后添加到NamedContextFactory的configurations Map中,从而在常见RibbonClient上下文的时候就可以设置相应的Config配置。

RibbonApplicationContextInitializer Ribbon饥饿初始化

还有一个问题就是NamedContextFactory.createContext上下文在什么时候调用?一方面是在获取上下文的时候,如果获取不到会先调用createContext方法创建,还有一个地方是在RibbonApplicationContextInitializer#initialize 方法中会去调用createContext创建上下文,而RibbonApplicationContextInitializer就是负责Ribbon上下文的初始化,他在 RibbonAutoConfiguration中被定义

@EnableConfigurationProperties({
   
   RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
   
   
    //初始化Ribbon上下文,如果配置了ribbon.eager-load.enabled=true,在系统启动时就会初始化
        @Bean
    @ConditionalOnProperty(value = "ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
   
   
        //从ribbonEagerLoadProperties配置文件中获取Ribbon客户端名字
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }
}

RibbonApplicationContextInitializer是怎么做的呢

public class RibbonApplicationContextInitializer
        implements ApplicationListener<ApplicationReadyEvent> {
   
   
    //创建Ribbon客户端上下文的工厂类
    private final SpringClientFactory springClientFactory;

    //ribbon的客户端名字结合
    //List of Ribbon client names
    private final List<String> clientNames;

    public RibbonApplicationContextInitializer(SpringClientFactory springClientFactory,
            List<String> clientNames) {
   
   
        this.springClientFactory = springClientFactory;
        //Ribbon客户端名字集合
        this.clientNames = clientNames;
    }
    //初始化,为每个客户端初始化,springClientFactory.getContext方法中会先调用createContext方法
    protected void initialize() {
   
   
        if (clientNames != null) {
   
   
            for (String clientName : clientNames) {
   
   
                this.springClientFactory.getContext(clientName);
            }
        }
    }
    //Spring容器事件,容器准备好就会调用该方法,然后调用initialize();
初始化
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
   
   
        initialize();
    }
}

首先在RibbonAutoConfiguration 中通过RibbonEagerLoadProperties获取到配置的Ribbon客户端名字集合,交给RibbonApplicationContextInitializer初始化器,RibbonApplicationContextInitializer监听是Spring的ApplicationReadyEvent 事件,调用initialize方法,通过springClientFactory.getContext(clientName);
为每个客户端初始化上下文对象。

到这里我们就搞清楚了RibbonClient客户端的初始化流程了。

LoadBalancerAutoConfiguration 负载均衡器配置

LoadBalancerAutoConfiguration是针对于LoadBalancer负载均衡的配置类,它是在spring-cloud-commons-2.0.1.RELEASE.jar/META-INF/spring.factories 文件中被定义,SpringBoot启动自动配置时被加载
在这里插入图片描述
源码如下

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

    //被注解了@LoadBalanced的RestTemplate 
    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();
    //给RestTemplate初始化,customizer.customize(restTemplate)方法来给RestTemplate增加拦截器LoadBalancerInterceptor
    //该方法会调用下面的restTemplateCustomizer方法
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
   
   
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
   
   
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
   
   
                for (RestTemplateCustomizer customizer : customizers) {
   
   
                //给RestTemplate增加拦截器LoadBalancerInterceptor
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
    //LoadBalancerRequestFactory用来创建LoadBalancerRequest,后续Ribbon发请求会用到LoadBalancerRequestFactory
    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory loadBalancerRequestFactory(
            LoadBalancerClient loadBalancerClient) {
   
   
        return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
    }

    @Configuration
    @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
    static class LoadBalancerInterceptorConfig {
   
   
        //LoadBalancerInterceptor 负载均衡器拦截器配置,
        //用来拦截RestTemplate的请求,从而执行exec方法实现负载均衡的功能
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRequestFactory requestFactory) {
   
   
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
        //给 RestTemplate 设置拦截器loadBalancerInterceptor,执行RestTemplate的时候会执行该拦截器
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
   
   
            return restTemplate -> {
   
   
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }

这里定义了

  • LoadBalancerRequestFactory :LoadBalancerRequest 负载均衡请求工厂,用来创建LoadBalancerRequest
  • LoadBalancerInterceptor :请求拦截器,拦截RestTemplate请求,调用exec方法实现负载均衡
  • RestTemplateCustomizer :定制RestTmplate,设置拦截器LoadBalancerInterceptor

我们看一下拦截器LoadBalancerInterceptor 的源码

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
   
   

    private LoadBalancerClient loadBalancer;
    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
   
   
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
   
   
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }
    //拦截方法,这里会调用loadBalancer.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, requestFactory.createRequest(request, body, execution));
    }
}

这个拦截器很重要,当RestTmplate发请求时,会调用该拦截器,intercept方法执行,该方法会通过requestFactory.createRequest创建LoadBalancerRequest请求对象,调用ILoadBalancer.exec方法实现负载均衡请求

RibbonClientConfiguration Ribbon客户端配置

除此之外还有一个配置类我们需要去关注一下:RibbonClientConfiguration Ribbon的客户端配置

/**
 * @author Dave Syer
 * @author Tim Ysewyn
 */
@SuppressWarnings("deprecation")
@Configuration
@EnableConfigurationProperties
//这里的顺序很重要,last应该是默认值,first应该是可选的
//Order is important here, last should be the default, first should be optional
// see https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653

//1.HttpClientConfiguration:定义了ApacheHttpClient 和 OkHttpClient
//2.OkHttpRibbonConfiguration :OkHttpClient配置
//3.RestClientRibbonConfiguration:创建一个 RestClient
//4.HttpClientRibbonConfiguration:Ribbon的http配置,定义了CloseableHttpClient;RibbonLoadBalancingHttpClient;RetryableRibbonLoadBalancingHttpClient

@Import({
   
   HttpClientConfiguration.class, OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {
   
   
    //默认链接超时
    public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
    //默认读取超时
    public static final int DEFAULT_READ_TIMEOUT = 1000;

    @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;

    //ribbon的配置,默认配置实现:DefaultClientConfigImpl
    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
   
   
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
        config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
        return config;
    }

    //配置负载均衡算法类,如果有自定义,使用自定义的,否则使用的是ZoneAvoidanceRule 区域选择
    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
   
   
        //是否设置算法
        if (this.propertiesFactory.isSet(IRule.class, name)) {
   
   
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

    //定义IPing,用来检查服务是否可用的,默认使用DummyPing
    @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();
    }

    //服务列表,定义用于获取服务器列表的方法的接口,默认实现是ConfigurationBasedServerList,很重要
    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    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;
    }

    //服务器列表更新器,默认实现是PollingServerListUpdater
    //是动态服务器列表更新程序更新的默认策略 
    @Bean
    @ConditionalOnMissingBean
    public ServerListUpdater ribbonServerListUpdater(IClientConfig config) {
   
   
        return new PollingServerListUpdater(config);
    }

    //负载均衡器ILoadBalancer ,
    //默认实现ZoneAwareLoadBalancer,继承于DynamicServerListLoadBalancer
    @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);
    }

    //服务列表过滤器,默认ZonePreferenceServerListFilter 区域首选
    @Bean
    @ConditionalOnMissingBean
    @SuppressWarnings("unchecked")
    public ServerListFilter<Server> ribbonServerListFilter(IClientConfig config) {
   
   
        if (this.propertiesFactory.isSet(ServerListFilter.class, name)) {
   
   
            return this.propertiesFactory.get(ServerListFilter.class, config, name);
        }
        ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter();
        //根据配置创建
        filter.initWithNiwsConfig(config);
        return filter;
    }

    //负载均衡上下文
    @Bean
    @ConditionalOnMissingBean
    public RibbonLoadBalancerContext ribbonLoadBalancerContext(ILoadBalancer loadBalancer,
                                                               IClientConfig config, RetryHandler retryHandler) {
   
   
        return new RibbonLoadBalancerContext(loadBalancer, config, retryHandler);
    }
    //默认负载均衡器重试处理程序
    @Bean
    @ConditionalOnMissingBean
    public RetryHandler retryHandler(IClientConfig config) {
   
   
        return new DefaultLoadBalancerRetryHandler(config);
    }

    //服务内省器
    @Bean
    @ConditionalOnMissingBean
    public ServerIntrospector serverIntrospector() {
   
   
        return new DefaultServerIntrospector();
    }

    @PostConstruct
    public void preprocess() {
   
   
        setRibbonProperty(name, DeploymentContextBasedVipAddresses.key(), name);
    }

    //覆盖其他Rest客户端
    static class OverrideRestClient extends RestClient {
   
   

        private IClientConfig config;
        private ServerIntrospector serverIntrospector;

        protected OverrideRestClient(IClientConfig config,
                ServerIntrospector serverIntrospector) {
   
   
            super();
            this.config = config;
            this.serverIntrospector = serverIntrospector;
            initWithNiwsConfig(this.config);
        }
        //使用服务器重构URI
        @Override
        public URI reconstructURIWithServer(Server server, URI original) {
   
   
            URI uri = updateToSecureConnectionIfNeeded(original, this.config,
                    this.serverIntrospector, server);
            return super.reconstructURIWithServer(server, uri);
        }

        //初始化ApacheHttpClient4  客户端
        @Override
        protected Client apacheHttpClientSpecificInitialization() {
   
   
            ApacheHttpClient4 apache = (ApacheHttpClient4) super.apacheHttpClientSpecificInitialization();
            apache.getClientHandler().getHttpClient().getParams().setParameter(
                    ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES);
            return apache;
        }

    }

}

RibbonClientConfiguration 配置了几个比较关键的组件

  • IClientConfig :Ribon的客户端配置对象
  • IRule:Ribbon的负载均衡策略,如果没有指定策略类,默认使用ZoneAvoidanceRule ,拥有zone选择和轮询算法
  • IPing :检查服务器是否可用,可用的服务器将作为Ribbon负载均衡的候选服务器
  • ServerList : 用来获取所有server的注册列表的接口,提供了初始化服务列表和更新服务列表的方法,DynamicServerListLoadBalancer通过它来获取服务注册表
  • ServerListUpdater : 服务列表更新器默认实现PollingServerListUpdater ,维护了一个定时器30s/次更新
  • ServerListFilter :服务列表过滤器,通过该接口过滤后的server列表作为负载均衡候选的服务列表,默认实现ZonePreferenceServerListFilter 首选根据区域过滤。
  • ILoadBalancer :负载均衡器,默认实现ZoneAwareLoadBalancer,Ribobn通过它实现负载均衡
  • LoadBalancerContext: Ribbon负载均衡器上下文对象,默认实现RibbonLoadBalancerContext
  • RetryHandler:重试处理器,它确定负载均衡器是否可重试,维护了Ribbon的重试次数
  • ServerIntrospector :服务器自省,可以确定服务器是安全访问,和获取服务的元数据

ILoadBalancer 负载均衡器

负载均衡器接口,Ribbon比较核心的一个组件负责调用IRule从候选的服务里列表选择一个服务

/**
接口定义了软件的负载均衡器的操作
 * Interface that defines the operations for a software loadbalancer. A typical
 * loadbalancer minimally need a set of servers to loadbalance for, a method to
 * mark a particular server to be out of rotation and a call that will choose a
 * server from the existing list of server.
 * 
 * @author stonse
 * 
 */
public interface ILoadBalancer {
   
   

    /**
    服务器的初始列表
     * Initial list of servers.
     * This API also serves to add additional ones at a later time
     * The same logical server (host:port) could essentially be added multiple times
     * (helpful in cases where you want to give more "weightage" perhaps ..)
     * 
     * @param newServers new servers to add
     */
    public void addServers(List<Server> newServers);

    /**
        从负载均衡器选择一个服务
     * Choose a server from load balancer.
     * 
     * @param key An object that the load balancer may use to determine which server to return. null if 
     *         the load balancer does not use this parameter.
     * @return server chosen
     */
    public Server chooseServer(Object key);

    /**
    要通过负载平衡器的客户端调用,以通知服务器已关闭意外
     * To be called by the clients of the load balancer to notify that a Server is down
     * else, the LB will think its still Alive until the next Ping cycle - potentially
     * (assuming that the LB Impl does a ping)
     * 
     * @param server Server to mark as down
     */
    public void markServerDown(Server server);

    /**
        获取当前服务器列表,过时了
     * @deprecated 2016-01-20 This method is deprecated in favor of the
     * cleaner {@link #getReachableServers} (equivalent to availableOnly=true)
     * and {@link #getAllServers} API (equivalent to availableOnly=false).
     *
     * Get the current list of servers.
     *
     * @param availableOnly if true, only live and available servers should be returned
     */
    @Deprecated
    public List<Server> getServerList(boolean availableOnly);

    /**
    获取可用的服务列表,只有那些启动并可以连接的服务器
     * @return Only the servers that are up and reachable.
     */
    public List<Server> getReachableServers();

    /**
    return所有已知服务器,可访问和不可访问
     * @return All known servers, both reachable and unreachable.
     */
    public List<Server> getAllServers();
}

ILoadBalancer 负载均衡器接口包括了如下方法

  • chooseServer 根据服务名选择一个服务
  • addServers添加服务集合,
  • markServerDown标记服务下线,
  • getServerList获取可用的服务列表
  • getReachableServers获取可用的服务列表,
  • getAllServers获取所有的服务列表

至于Server对象是用来封装一个可寻址得到目标服务器

看一下ILoadBalancer 接口的继承体系如下
在这里插入图片描述

  • DynamicServerListLoadBalancer是动态服务器列表负载均衡器,具有动态获取服务器的候选列表的功能,即便注册表在修改也可以动态获取有效的服务
  • ZoneAwareLoadBalancer是区域感知负载均衡器,选择服务器时可以根据区域zone进行选择

在这里插入图片描述

IRule负载均衡策略

IRule就是具体的负载均衡算法接口,包括了存取负载均衡器方法和选择服务方法,它很多的实现,一个实现代表一种算法,是ribbon实现负载均衡的核心算法,IRule源码

/**
接口定义了负载均衡器“规则”。,众所周知的负载均衡策略,包括基于轮循,响应时间等
 * Interface that defines a "Rule" for a LoadBalancer. A Rule can be thought of
 * as a Strategy for loadbalacing. Well known loadbalancing strategies include
 * Round Robin, Response Time based etc.
 * 
 * @author stonse
 * 
 */
public interface IRule{
   
   
    /*
      选择服务的具体方法,从 allServers 或者 lb.upServers选择一个服务
     * choose one alive server from lb.allServers or
     * lb.upServers according to key
     * 
     * @return choosen Server object. NULL is returned if none
     *  server is available 
     */

    public Server choose(Object key);
    //设置负载均衡器
    public void setLoadBalancer(ILoadBalancer lb);
    //获取负载均衡器
    public ILoadBalancer getLoadBalancer();    
}

他的实现类如下
在这里插入图片描述

  • ClientConfigEnabledRoundRobinRule :它使用的是RoundRobinRule的算法,即轮询
  • BestAvailableRule 跳过“短路”的服务器并选择并发请求最少的服务器的规则
  • PredicateBaseRule:基于Predicate的策略,继承ClientConfigEnabledRoundRobinRule 轮询算法
  • RandomRule :随机选择一个server
  • RoundRobinRule :轮询选择server
  • RetryRule :根据轮询的方式重试
  • WeightedResponseTimeRule : 根据响应时间去分配一个weight ,weight越低,被选择的可能性就越低
  • ZoneAvoidanceRule :根据server的zone区域和可用性来轮询选择

ServerList 服务列表获取接口

用来获取负载均衡候选的server的注册列表的接口,提供了初始化服务列表和更新服务列表的方法

/**
 * Interface that defines the methods sed to obtain the List of Servers
 * @author stonse
 *
 * @param <T>
 */
public interface ServerList<T extends Server> {
   
   

    //初始化服务列表
    public List<T> getInitialListOfServers();

    /**
     * Return updated list of servers. This is called say every 30 secs
     * (configurable) by the Loadbalancer's Ping cycle
     * 
     */
    public List<T> getUpdatedListOfServers();   

}

ServerList有一个比较重要的实现DiscoveryEnabledNIWSServerList,它可以通过服务发现的方式从获取服务注册表,后续会讲到。
在这里插入图片描述

ServerListUpdater 服务列表更新器

它提供了更新服务列表的功能,它有两个实现分别是 EurekaNotificationServerListUpdater 它利用eureka的事件侦听器触发服务列表更新,和 PollingServerListUpdater 采用定时任务定时更新服务列表,后面详细说。
在这里插入图片描述

ServerListFilter 服务列表过滤

它负责过滤负载均衡器候选的服务列表,在Rbbon加载到servers服务列表后就会使用ServerListFilter进行过滤,比如ZonePreferenceServerListFilter就是根据区域进行过滤

在这里插入图片描述

DynamicServerListLoadBalancer 负载均衡器候选服务列表的获取

首先说说ILoadBalancer的实现ZoneAwareLoadBalancer ,它是拥有区域选择功能的负载均衡器,继承了DynamicServerListLoadBalancer ,在负载均衡的时候Ribbon会 , 拥有服务注册表更新功能,这里我们主要来跟踪一下Ribbon是如何更新服务列表的

public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
   
   

   void setUpServerList(List<Server> upServerList) {
   
   
        this.upServerList = upServerList;
    }

  public ZoneAwareLoadBalancer(IClientConfig clientConfig, IRule rule,
                                 IPing ping, ServerList<T> serverList, ServerListFilter<T> filter,
                                 ServerListUpdater serverListUpdater) {
   
   
        //调用super初始化                     
        super(clientConfig, rule, ping, serverList, filter, serverListUpdater);
    }

在ZoneAwareLoadBalancer构造器中调用了super(DynamicServerListLoadBalancer)进行初始化 super(clientConfig, rule, ping, serverList, filter, serverListUpdater);

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
   
   
...省略...
   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);
    }

    ..省略...
    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());
    }


    ...省略...
    @VisibleForTesting
    public void updateListOfServers() {
   
   
        List<T> servers = new ArrayList<T>();
        if (serverListImpl != null) {
   
   
        //通过ServerList获取服务列表
            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);
    }

DynamicServerListLoadBalancer的构造器中把相关的对象赋值好之后,调用了 restOfInit(clientConfig);方法进行初始化,然后调用updateListOfServers更新服务注册表,updateListOfServers方法中做了这几件事情

  • servers = serverListImpl.getUpdatedListOfServers():通过ServerList方法获取服务列表
  • servers = filter.getFilteredListOfServers(servers):如果服务列表servers不为空,调用ServerListFilter过滤服务
  • 然后调用updateAllServerList(servers);方法把所有的server的Alive可用状态设置为true

对于restOfInit方法在initWithNiwsConfig方法中也会被调用,这个我们后面再说,我们重点跟踪一下ServerList#getUpdatedListOfServers方法看他是如何获取到服务注册表的 , 我这里跟踪了一下断点,serverListImpl.getUpdatedListOfServers()代码来到实现类DomainExtractingServerList#getUpdatedListOfServers方法

public class DomainExtractingServerList implements ServerList<DiscoveryEnabledServer> {
   
   

    @Override
    public List<DiscoveryEnabledServer> getUpdatedListOfServers() {
   
   
        List<DiscoveryEnabledServer> servers = setZones(this.list
                .getUpdatedListOfServers());
        return servers;
    }

DomainExtractingServerList #getUpdatedListOfServers返回了一个DiscoveryEnabledServer对象的列表,它是通过发现获得的服务器,包含 InstanceInfo形式的元数据,继续跟踪下去,代码来DiscoveryEnabledNIWSServerList#getUpdatedListOfServers

public class DiscoveryEnabledNIWSServerList extends AbstractServerList<DiscoveryEnabledServer>{
   
   
 @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>();
        }
        //Eureka客户端
        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
                //【重要】从eurekaClient#getInstancesByVipAddress获取服务注册表
                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 ,为了不破坏原本的实例
                            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对象
                        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;
    }

DomainExtractingServerList 是ServerList的实现类,它的作用是从Eureka客户端获取服务器信息的服务器列表类使用ServerList动态获取服务器列表 List<InstanceInfo> listOfInstanceInfo = eurekaClient.getInstancesByVipAddress(vipAddress, isSecure, targetRegion);,并封装成DiscoveryEnabledServer 列表返回,继续跟踪,代码来到DiscoveryClient#getInstancesByVipAddress(java.lang.String, boolean, java.lang.String)


@Singleton
public class DiscoveryClient implements EurekaClient {
   
   
...省略...
 @Override
    public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure,
                                                       @Nullable String region) {
   
   
        if (vipAddress == null) {
   
   
            throw new IllegalArgumentException(
                    "Supplied VIP Address cannot be null");
        }
        Applications applications;
        if (instanceRegionChecker.isLocalRegion(region)) {
   
   
            //【重要】获取本地缓存的服务注册表
            applications = this.localRegionApps.get();
        } else {
   
   
            applications = remoteRegionVsApps.get(region);
            if (null == applications) {
   
   
                logger.debug("No applications are defined for region {}, so returning an empty instance list for vip "
                        + "address {}.", region, vipAddress);
                return Collections.emptyList();
            }
        }

        if (!secure) {
   
   
            return applications.getInstancesByVirtualHostName(vipAddress);
        } else {
   
   
            return applications.getInstancesBySecureVirtualHostName(vipAddress);

        }

    }

这里看到,其实是从DiscoveryClient中localRegionApps本地缓存的服务注册表中获取服务,到这我们就搞清楚了Ribbon是如何去加载服务注册表作为负载均衡的候选服务列表的,获取到的服务注册表安装zone进行统计,最终是设置到LoadBalancerStats对象的Map<String, ZoneStats> zoneStatsMap中,然后交给BaseLoadBalancer维护起来,在Ribbon执行负载均衡的过程中会从负载均衡器取出服务列表作为候选的服务。

ServerListUpdater 更新负载均衡候选服务列表

上面有介绍到ServerListUpdater ,它是用来更新负载均衡的候选服务列表的,其中一个实现是PollingServerListUpdater,通过定时任务来更新服务列表

public class PollingServerListUpdater implements ServerListUpdater {
   
   
    //延迟时间
    private static long LISTOFSERVERS_CACHE_UPDATE_DELAY = 1000; // msecs;
    //定时任务间隔时间,更新服务列表
    private static int LISTOFSERVERS_CACHE_REPEAT_INTERVAL = 30 * 1000; // msecs;

   @Override
    public synchronized void start(final UpdateAction updateAction) {
   
   
        if (isActive.compareAndSet(false, true)) {
   
   
            final Runnable wrapperRunnable = new Runnable() {
   
   
                @Override
                public void run() {
   
   
                    if (!isActive.get()) {
   
   
                        if (scheduledFuture != null) {
   
   
                            scheduledFuture.cancel(true);
                        }
                        return;
                    }
                    try {
   
   
                        //更新动作,执行更新
                        updateAction.doUpdate();
                        lastUpdated = System.currentTimeMillis();
                    } catch (Exception e) {
   
   
                        logger.warn("Failed one update cycle", e);
                    }
                }
            };
        //定时任务
            scheduledFuture = getRefreshExecutor().scheduleWithFixedDelay(
                    wrapperRunnable,    //任务线程
                    initialDelayMs,
                    refreshIntervalMs,
                    TimeUnit.MILLISECONDS    30s/);
        } else {
   
   
            logger.info("Already active, no-op");
        }
    }

PollingServerListUpdater默认30s/次更新服务列表,定时任务执行的是updateAction.doUpdate(); 我们看一下这个方法

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
   
   
...省略...
    protected final ServerListUpdater.UpdateAction updateAction = new ServerListUpdater.UpdateAction() {
   
   
        @Override
        public void doUpdate() {
   
   
        //更新服务列表
            updateListOfServers();
        }
    };

真相大白,其实PollingServerListUpdater 最终也是通过DynamicServerListLoadBalancer#updateListOfServers去更新服务列表,只不过是定时更新的。

那么这个PollingServerListUpdater#Starter它是在哪儿被触发的呢?

在DynamicServerListLoadBalancer初始化方法restOfInit中有这么一行代码 enableAndInitLearnNewServersFeature(); 就是在开启服务列表的更新定时任务

public class DynamicServerListLoadBalancer<T extends Server> extends BaseLoadBalancer {
   
   
...省略...
//初始化
 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());
    }

//调用 serverListUpdater.start
 public void enableAndInitLearnNewServersFeature() {
   
   
        LOGGER.info("Using serverListUpdater {}", serverListUpdater.getClass().getSimpleName());
        serverListUpdater.start(updateAction);
    }

在restOfInit方法中间接触发了PollingServerListUpdater #Start定时更新逻辑

所以这里总结一下,负载均衡器的候选服务列表其实就是从EurekaClient中的本地缓存的注册表中加载的,在DynamicServerListLoadBalancer构造器中初始化的时候被加载,也会通过定时任务更新,也可以通过Eureka事件通知的方式去更新。

总结

在这里插入图片描述
整理如下

  • RibbonAutoConfiguration 创建负载均衡客户端LoadBalancerClient
  • RibbonAutoConfiguration 创建SpringClientFactory初始化Ribbon上下文,注册相关的组件
  • LoadBalancerAutoConfiguration 定义好负载均衡拦截器LoadBalancerInterceptor,添加到RestTemplate
  • RibbonClientConfiguration 注册了ILoadBalancer,IRule,IPing,ServerList,ServerListFilter,ServerListUpdater
  • ILoadBalancer 的实现类DynamicServerListLoadBalancer 通过ServerList加载服务列表,通过ServerListFilter过滤,通过IPing检查可用性,通过ServerListUpdater定时更新服务列表

但是用RestTemplate使用服务名发起请求时会走如下流程

  • RestTemplate请求调用LoadBalancerInterceptor#intercept方法执行请求
  • 接着调用LoadBalancerClient#exec,服务名和LoadBalancerRequestFactory创建的LoadBalancerRequest请求对象作为参数
  • LoadBalancerClient#exec方法中调用ILoadBalancer#chooseServer选择一个服务
  • ILoadBalancer#chooseServer又调用IRule的负载均衡算法选择服务
  • 选择到服务后,调用LoadBalancerRequest#apply对选择的服务发起请求

当然这个执行步骤我们会在下一章去详细跟踪一下

文章有点长,这篇文章主要介绍了一下Ribbon初始化的一些组件,以及Ribbon的上下文创建流程,和Ribbon负载均衡器候选服务列表的加载流程,为下一篇文章Ribbon的执行流程打下基础

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
112 3
|
4月前
|
Java 开发工具 git
实现基于Spring Cloud的配置中心
实现基于Spring Cloud的配置中心
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
335 37
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
3月前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
1151 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
25天前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
31 0
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
3月前
|
负载均衡 算法 Java
SpringCloud之Ribbon使用
通过 Ribbon,可以非常便捷的在微服务架构中实现请求负载均衡,提升系统的高可用性和伸缩性。在实际使用中,需要根据实际场景选择合适的负载均衡策略,并对其进行适当配置,以达到更佳的负载均衡效果。
55 13
|
4月前
|
负载均衡 算法 网络协议
Ribbon 负载均衡源码解读
Ribbon 负载均衡源码解读
58 15
Ribbon 负载均衡源码解读
|
3月前
|
Java 微服务 Spring
Spring Cloud全解析:配置中心之解决configserver单点问题
但是如果该configserver挂掉了,那就无法获取最新的配置了,微服务就出现了configserver的单点问题,那么如何避免configserver单点呢?