SpringCloud 服务注册与发现 源码分析(二)

  • SpringCloud 版本Hoxton.SR1
  • SpringBoot 版本2.2.1.RELEASE
  • 本文适用于对SpringBoot有一定基础得人,主要讲解Eureka 客户端得相关底层实现,讲解方式:场景驱动
  • 关键词 :客户端源码分析、客户端负载均衡源码分析
  • 上一篇 SpringCloud 服务注册与发现 源码分析(一) 分析了 Eureka Server 端的设计,本篇将会介绍客户端及负载均衡的相关设计。
  • 文末会留有两个思考题

2. Eureka Client

同服务端一样,我们分析的入口也是spring.factories文件。如下面:

图1 eureka-client.spring.factories文件
虽然文件里面配置了这么多自动装配类,但是由于boot提供的一些条件装配Condition控制,实际装配的类并不一定是这么多,条件装配过程本文不重点分析,下面给出的装配类都是作者根据装配关系筛选出来的。

EurekaClientAutoConfiguration
  • 此类类似于服务端的核心自动装配类EurekaServerAutoConfiguration配置类,根据顺序我们先来看一下头部的注释类:
    图2 EurekaClientAutoConfiguration类的头部注解类
  • 要装载此类,类路径下需要有EurekaClientConfig类,此类就类似于我们服务端分析的EurekaServerConfig类
  • 紧接着又Imort了DiscoveryClientOptionalArgsConfiguration类:
    图3 DiscoveryClientOptionalArgsConfiguration配置类
    由于路径下有ClientFilter类,所以此处会创建MutableDiscoveryClientOptionalArgs对象,用于添加额外的jersy过滤器参数设置
  • eureka.client.enabled属性为true时,才会装配当前类,由于默认为true,所以只要不显式的设置为false都会装配当前类。
  • @ConditionalOnDiscoveryEnabled:
    图4 服务发现启动注解
    ,当spring.cloud.discovery.enabled参数为true时才会自动装配当前类,由于默认为true,只要不显式设置为false都会装配。
  • @AutoConfigureAfter(name = {
    "org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
    "org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
    "org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" }): 此处的作用是在这些类装配之后再会去自动装配(相对顺序)。

    RefreshAutoConfiguration:当环境信息变化时,刷新属性。(例如某些配置参数刷新;日志级别切换等。)。其中创建的核心类包括:RefreshEventListener(1. 用于监听事件ApplicationReadyEvent(将上下文标识改完true,表示已刷新)2. 监听RefreshEvent事件,此处就是实现环境属性配置刷新的核心,监听到此事件之后会触发ContextRefresher类的refresh方法,而该刷新方法首先会判断环境配置属性是否有改变,有的话会发布一个EnvironmentChangeEvent事件,并将改变的keys集作为事件源,然后又会调用RefreshScope的refreshAll方法(销毁所有的刷新实例,并发布RefreshScopeRefreshedEvent事件),上述即是属性配置动态刷新的实现)、RefreshScope(当监听到ContextRefreshedEvent上下文刷新完成事件时,并且当bean的scope属性为refresh时,会触发早期初始化bean)
    图5 RefreshEventListener的刷新监听逻辑
    图6 ContextRefresher的refresh方法实现

    EurekaDiscoveryClientConfiguration:此类也是依赖于EurekaClientConfig.class类和eureka.client.enabled属性才会装配。另外还有其他两处作用:1. 若当前没有EurekaDiscoveryClient的bean会去创建一个实例,此类实现了DiscoveryClient(属于spring-cloud-commons抽象下的类,此包很重要,后面会单独一篇文章介绍,例如:Nacos中就实现了此抽象接口,com.alibaba.cloud.nacos.discovery.NacosDiscoveryClient),用于客户端服务发现(可以从Eureka、consul等注册中心中获取服务实例)
    图7 EurekaDiscoveryClient的核心实现
  1. 内部静态类 EurekaClientConfigurationRefresher 会监听RefreshScopeRefreshedEvent事件(该事件上面提到过,当指定的bean刷新时会发布该事件),监听到之后会有两步操作:监听到事件之后就强制调用获取实例的方法; 服务自动注册也需要重启。
    图8 EurekaClientConfigurationRefresher的逻辑处理

    AutoServiceRegistrationAutoConfiguration:服务自动注册装配类,初始化的时候会去检查是否有自动服务注册AutoServiceRegistration的bean(该bean的创建过程也是在EurekaClientAutoConfiguration中),还会Import一个AutoServiceRegistrationConfiguration配置类:
    图9 AutoServiceRegistrationConfiguration
    该类会启动创建一个服务自动注册属性配置类,将 环境中spring.cloud.service-registry.auto-registration开头的属性绑定到当前类中。
    图10 AutoServiceRegistrationProperties
  • 分析完@AutoConfigureAfter之后,我们看一下当前装配类都创建了哪些重要的组件:
    图11 EurekaClientAutoConfiguration自动装配类的关键代码
    图12 EurekaClientAutoConfiguration自动装配类的关键代码

①是通过构造器注入当前上下文
②是如果当前容器中不存在EurekaClientConfig将会创建一个EurekaClientConfigBean实现,而且当环境中的 spring.config.name属性值为 bootstrap时,当前客户端实例不注册到Eureka中(即默认启动的时候不注册,但是后面还会有一次注册的机会)
③是若容器中不存在EurekaInstanceConfig将会创建一个EurekaInstanceConfigBean实现,用于保存服务端的相关配置,可以供其他服务查阅或者服务之间互相发现。
④是创建一个 EurekaServiceRegistry注册器Bean实例,该类实现了ServiceRegistry(spring-cloud-commons下的服务注册抽象,此外Nacos中也对该抽象进行了相应得扩展:com.alibaba.cloud.nacos.registry.NacosServiceRegistry,感兴趣的读者可以看看SpringCloudAlibaba的源码
⑤由于前面分析的会创建AutoServiceRegistrationProperties的属性配置类,所以@ConditionalOnBean(AutoServiceRegistrationProperties.class)是满足的,然后会通过构造器注入到当前实例中:

图13 EurekaAutoServiceRegistration的实现逻辑

该类实现了AutoServiceRegistration(也是spring-cloud-commons下的抽象,用于保存注册得元数据,例如 Nacos中也有相应的实现:com.alibaba.cloud.nacos.registry.NacosAutoServiceRegistration),还同时实现了 SmartLifecycle生命周期类,容器启动之后会通过生命周期回调start方法,注册实例(本质是将实力状态改为上线UP),将运行状态改为false并发布InstanceRegisteredEvent事件;容器关闭会回调stop方法,方法会调用注册器的deregister方法,将实例状态改为下线DOWN,并将运行状态改为false

⑥内部静态类RefreshableEurekaClientConfiguration,当路径下有RefreshScope并且容器中有RefreshAutoConfiguration时,会自动装配当前类,看名字就可以感觉到当前配置类可以实现属性配置刷新机制。
⑦创建EurekaClient实例,服务发现、获取实例的底层依赖(Eureka原生)。由于默认情况下spring容器会为对象生成一个代理对象,此处有一个使用目标对象的转化逻辑,最后生成CloudEurekaClient实例(springcloud的服务发现的实现,继承了com.netflix.discovery.DiscoveryClient类,DiscoveryClient类里实现具体的服务发现与相关逻辑。)
⑧当容器中不存在ApplicationInfoManager实例bean时,会创建一个,此类用来管理实例信息与实例配置(服务端也创建了当前实例管理器)
⑨创建一个EurekaRegistration实例,该实例实现了Registration(此接口仅代表一个可以注册的标志:服务元数据),而Registration又继承自ServiceInstance ( spring-cloud-commons抽象接口,表示一个服务实例), 到此核心装配类EurekaClientAutoConfiguration基本分析完毕!

3. 负载均衡 Ribbon

  • 负载均衡是分布式应用中重要的一个环节,将服务提供分摊到不同节点以实现更高的负载能力。负载均衡通常分为硬负载和软负载,软负载又分为客户端负载均衡和服务端负载均衡。 提到客户端就不得不提客户端负载均衡 Ribbon,Ribbon 属于一个客户端负载均衡框架。
RibbonAutoConfiguration
  • 配置类的头部信息
    图14 RibbonAutoConfiguration
    从上到下依次为
    @RibbonClients:
    图15 RibbonClients注解

    一个Configuration配置类,标注有@Import(RibbonClientConfigurationRegistrar.class),而RibbonClientConfigurationRegistrar注册器中会创建 RibbonClientConfigurationRegistrar类的bean定义,此类中会保存客户端配置实例(defaultConfiguration参数指定的配置)。

    @AutoConfigureAfter(name="EurekaClientAutoConfiguration")指定在EurekaClientAutoConfiguration之后装配(也可以看出Ribboon是基于客户端)
    @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,AsyncLoadBalancerAutoConfiguration.class })指定在LoadBalancerAutoConfiguration和AsyncLoadBalancerAutoConfiguration之前装配
    @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class })生成属性配置bean,并绑定属性
  • 配置类的成员变量
    图16 成员变量

    注入客户端配置类集合
    注入RibbonEagerLoadProperties
  • 配置类创建Bean,如下:
public class RibbonAutoConfiguration {
    // 1.创建客户端、负载均衡、客户端配置实例的工厂,其中this.configurations就是@RibbonClients主键中指定的配置参数
    // 2. 另外无参构造器中会调用 super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    // 设置默认的配置类型字段defaultConfigType为RibbonClientConfiguration.class,此处的字段作用是创建上下文时会为每个资源创建一个RibbonClientConfiguration类型的bean,且会将属性名称添加到环境中。
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
    // 负载均衡客户端,Eureka的实现是 RibbonLoadBalancerClient,
    // 包括重组URL方法( reconstructURI() )、选择服务实例方法( choose() )、选择实例之后的执行方法( execute() )
    // getServer()方法( 会调用对应ILoadBalancer实现的chooseServer()方法)
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }
        // 创建重试策略 RibbonLoadBalancedRetryPolicy
    @Bean
    @ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
    @ConditionalOnMissingBean
    public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(
            final SpringClientFactory clientFactory) {
        return new RibbonLoadBalancedRetryFactory(clientFactory);
    }
        // 创建属性工厂Bean,类中回初始化一个Map结合用于存放一些类与属性的映射关系
        // 例如键为Iping.class,值为NFLoadBalancerRuleClassName的映射(Iping是用来检查服务端是否存活)。
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }
}
  • 由于RibbonAutoConfiguration是在LoadBalancerAutoConfiguration配置类和AsyncLoadBalancerAutoConfiguration配置类之前先解析,所以接下来我依次分析这两个配置类,并且这两个配置类也是负载均衡的核心配置类。
LoadBalancerAutoConfiguration
  • 正如名字一样,负载均衡自动配置类

  • 类的头部信息

    图17 LoadBalanceAutoConfiguration的头部注释信息

    由于负载均衡的配置是针对于RestTemplate的,所有路径下一定要有RestTemplate
    容器中必须要存在LoadBalancerClient的实现才会装配
    启动创建LoadBalancerRetryProperties的配置Bean

  • 类中的属性:

    图18 LoadBalancerAutoConfiguration的属性信息

    ① 带有负载均衡的(带有@LoadBalanced注解)RestTemplate的集合(重点,容器中带有@LoadBalanced注解的RestTemplate都会注入到此集合中
    ② 带有可以修改HttpRequest对象的转换器实现集合(根据被给的服务实例ServiceInstance去自定义修改指定的HttpRequest对象

  • 类的Bean创建逻辑:

public class LoadBalancerAutoConfiguration {
    // 对实例化之后的bean进行后置处理
    //(正常情况下,需要实现SmartInitializingSingleton的afterSingletonsInstantiated方法,
    // 可以对创建好的bean进行处理,此处也是同样的逻辑), 
    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
                // 遍历所有的RestTemplate实例
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                // 循环执行自定义方法,修改RestTemplate实例
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
    }

    /**
     * Auto configuration for retry intercepting mechanism.
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryInterceptorAutoConfiguration {

        // 创建重试的负载均衡拦截器实例
        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(
                LoadBalancerClient loadBalancerClient,
                LoadBalancerRetryProperties properties,
                LoadBalancerRequestFactory requestFactory,
                LoadBalancedRetryFactory loadBalancedRetryFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
                    requestFactory, loadBalancedRetryFactory);
        }
        // 创建自定义的 RestTemplateCustomizer实现
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                // 获取每个RestTemplate的所有拦截器 
                List list = new ArrayList<>(
                        restTemplate.getInterceptors());
                // 添加负载均衡拦截器( 此处是重试负载均衡拦截器 )
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }
}
AsyncLoadBalancerAutoConfiguration
  • 类的头部信息
    图19 AsyncLoadBalancerAutoConfiguration的头部信息

    当负载均衡客户端实例存在时才装配( 因为拦截器中会注入拦截器客户端实例
    当路径中存在AsyncRestTemplate类时才装配( 因为该自动装配类是针对于异步调用模板类AsyncRestTemplate的
  • 类中的属性
    图20 异步负载均衡配置类AsyncLoadBalancerAutoConfiguration的属性

    此处的注入方式跟前面提到的RestTemplate如出一辙,无论从使用方式还是配置方式两者都神似( 简直就是一模一样 ),唯一的区别就是AsyncRestTemplate调用模板返回的结果会使用ListenableFuture接口(此处Spring作者说是灵感来自Google的ListenableFuture接口)包装一下,而ListenableFuture接口继承自java.util.concurrent.Future,看到这里我们就可以知道异步调用的原因了
  • 类的Bean创建逻辑
public class AsyncLoadBalancerAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    static class AsyncRestTemplateCustomizerConfig {
                
                // 会注入带有@LoadBalanced注入的AsyncRestTemplate实例到当前集合中
        @LoadBalanced
        @Autowired(required = false)
        private List restTemplates = Collections.emptyList();

        @Bean
        public SmartInitializingSingleton loadBalancedAsyncRestTemplateInitializer(
                final List customizers) {
            return new SmartInitializingSingleton() {
                @Override
                public void afterSingletonsInstantiated() {
                    // 遍历配置的所有异步模板类
                    for (AsyncRestTemplate restTemplate : AsyncRestTemplateCustomizerConfig.this.restTemplates) {
                        for (AsyncRestTemplateCustomizer customizer : customizers) {
                            // 调用自定义方法修改模板类
                            customizer.customize(restTemplate);
                        }
                    }
                }
            };
        }

    }

    @Configuration(proxyBeanMethods = false)
    static class LoadBalancerInterceptorConfig {
        // 创建异步负载均衡拦截器(并注入负载均衡客户端)
        @Bean
        public AsyncLoadBalancerInterceptor asyncLoadBalancerInterceptor(
                LoadBalancerClient loadBalancerClient) {
            return new AsyncLoadBalancerInterceptor(loadBalancerClient);
        }
        // 创建异步调用模板类自定义实现类
        @Bean
        public AsyncRestTemplateCustomizer asyncRestTemplateCustomizer(
                final AsyncLoadBalancerInterceptor loadBalancerInterceptor) {
            return new AsyncRestTemplateCustomizer() {
                @Override
                public void customize(AsyncRestTemplate restTemplate) {
                    // 获取异步调用模板类中拦截器列表 
                    List list = new ArrayList<>(
                            restTemplate.getInterceptors());
                    // 添加异步负载均衡调用拦截器
                    list.add(loadBalancerInterceptor);
                    restTemplate.setInterceptors(list);
                }
            };
        }

    }

}
RibbonEurekaAutoConfiguration
  • 此类存在于spring.factories中,表示如果Eureka客户端启动时,会配置以Eureka为基础的Ribbon。

  • 类相关信息

    图21 RibbonEurekaAutoConfiguration

    装配时机晚于 RibbonAutoConfiguration类
    由于@RibbonClients注解的特性,会创建一个EurekaRibbonClientConfiguration的bean定义

  • 到此,客户端启动相关的核心配置类与负载均衡相关的核心配置类基本已经分析完毕,单纯的罗列源码略显枯燥无味,而且也不会加深记忆。

1. Question1:为什么图18中只会注入带有@LoadBalanced注解的RestTemplate实例,而普通的RestTemplate不会注入进去?
2. Question2:为什么RestTemplate带有@LoadBalanced注解之后就具有负载均衡能力了?作者会在下一篇讲解RestTemplate从配置到实现负载均衡的过程。
答疑连接:https://www.jianshu.com/p/cb8344039efc

  1. ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!

你可能感兴趣的:(SpringCloud 服务注册与发现 源码分析(二))