博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Ribbon 学习(二):Spring Cloud Ribbon 加载配置原理
阅读量:4096 次
发布时间:2019-05-25

本文共 12846 字,大约阅读时间需要 42 分钟。

说明

在上一篇博文一文中,我简单介绍了在脱离 Eureka 时 Ribbon 的简单使用方式。在本篇博文中,我将继续通过源码来介绍 Spring Cloud Ribbon 的加载配置原理,了解 Ribbon Client 如何创建,以及 RequestTemplate 如何具有负载均衡的功能特性。

在正文开始前,我们先回忆下在上篇博文中是如何使用 Ribbon 的。首先使用 @LoadBalanced 注解标注创建了 ResetTemplate,接着使用 @RibbonClient 注解创建了名为 test-server 的 Ribbon Client,最后在 application.yml 配置文件中,配置 test-server.ribbon.eureka.enabled 值为 false,并通过 test-server.listOfSevers 属性配置了最终使用的服务器地址。本文基于之前使用示例进行源码阅读学习,而忽略了一些内容。

正文

@LoadBalanced

使用该注解配置 RestTemplate Bean,通过注解源码可以看到,该注解的本质是一个 @Qualifier 注解

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}

在配置时通过 @Autowired 进行集合依赖注入:

@LoadBalanced@Autowired(    required = false)private List
restTemplates = Collections.emptyList();

关于 @Qualifier 注解的使用,官方文档中有以下表述:

Qualifiers also apply to typed collections, as discussed above, for example, to Set. In this case, all matching beans according to the declared qualifiers are injected as a collection. This implies that qualifiers do not have to be unique; they rather simply constitute filtering criteria. For example, you can define multiple MovieCatalog beans with the same qualifier value “action”, all of which would be injected into a Set annotated with @Qualifier(“action”).

You can create your own custom qualifier annotations. Simply define an annotation and provide the @Qualifier annotation within your definition:

可以通过该注解自定义注解,以此实现特定依赖注入。更多内容请看官方文档 。

在简单了解了 @LoadBalanced 注解后,再来看 @RibbonClient 注解。

@RibbonClient

在之前的示例中,通过 @RibbonClient 注解创建了名为 test-server 的 Ribbon Client:

@RestController@RibbonClient(name = "test-server", configuration = RibbonConfiguration.class)public class HelloClient {    ...}

在该注解的源码中,可以看到通过注解参数 value 或 name 我们可以为 Ribbon Client 自定义名称,通过 configuration 可以设置多个自定义配置类:

@Configuration(    proxyBeanMethods = false)@Import({RibbonClientConfigurationRegistrar.class})@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface RibbonClient {    String value() default "";    String name() default "";    Class
[] configuration() default {};}

同时该注解使用了 @Configuration 并通过 @Import 引入了 RibbonClientConfigurationRegistrar 配置类。

关于 @Configuration 注解中设置 proxyBeanMethods 的原因,可以看这篇文章。

RibbonClientConfigurationRegistrar

该类实现了 ImportBeanDefinitionRegistrar 接口,实现 Bean 的动态注入。其用法的相关介绍,请看这篇文章。

在该方法中,通过 AnnotationMetadata 的 getAnnotationAttributes 方法分别获取注解 @RibbonClients 和 @RibbonClient 的配置属性值,由此进行 Ribbon Client bean 的动态注入。

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {        // 获取 @RibbonClients 注解属性        Map
attrs = metadata.getAnnotationAttributes(RibbonClients.class.getName(), true); // 对使用属性 value 配置的多个 RibbonClient 进行依次注入 if (attrs != null && attrs.containsKey("value")) { AnnotationAttributes[] clients = (AnnotationAttributes[])((AnnotationAttributes[])attrs.get("value")); AnnotationAttributes[] var5 = clients; int var6 = clients.length; for(int var7 = 0; var7 < var6; ++var7) { AnnotationAttributes client = var5[var7]; // 获取 RibbonClient 的自定义配置类进行 Bean 的注册 this.registerClientConfiguration(registry, this.getClientName(client), client.get("configuration")); } } if (attrs != null && attrs.containsKey("defaultConfiguration")) { String name; if (metadata.hasEnclosingClass()) { name = "default." + metadata.getEnclosingClassName(); } else { name = "default." + metadata.getClassName(); } // 获取 @RaibbonClients 配置的对所有 RibbonClient 的默认配置类进行 Bean 的注册 this.registerClientConfiguration(registry, name, attrs.get("defaultConfiguration")); } // 获取 @RibbonClient 注解属性 Map
client = metadata.getAnnotationAttributes(RibbonClient.class.getName(), true); String name = this.getClientName(client); if (name != null) { this.registerClientConfiguration(registry, name, client.get("configuration")); }}
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RibbonClientSpecification.class);    builder.addConstructorArgValue(name);    builder.addConstructorArgValue(configuration);    registry.registerBeanDefinition(name + ".RibbonClientSpecification", builder.getBeanDefinition());}

通过以上代码,我们可以知道对于注解 @RibbonClients 和 @RibbonClient,最终都是根据其定义的配置类注册为 RibbonClientSpecification 的 BeanDefinition,所以对于每个 RibbonClient 都有一个 RibbonClientSpecification 与之对应

在了解了 @RibbonClient 作用后,再来看 Spring Cloud 对 Ribbon 的自动装配。spring 在 spring-cloud-netfilx-ribbon 项目的 spring.factories 配置文件中配置了 org.springframework.boot.autoconfigure.EnableAutoConfiguration = org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,以此实现自动装配。

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 {    ...}

通过源码可以看到,在该类上使用了 @RibbonClients 注解,也就是说该类也会被注册为一个 RibbonClientSpecification BeanDefinition,使用 @AutoConfigureAfter 和 @AutoConfigureBefore 注解,表示该类先于 LoadBalancerAutoConfiguration 和 AsyncLoadBalancerAutoConfiguration 进行配置,而在 EurekaClientAutoConfiguration 之后进行配置。同时注意,这两个注解只对 spring.factories 配置文件中的类进行排序

@Autowired(    required = false)private List
configurations = new ArrayList();@Autowiredprivate RibbonEagerLoadProperties ribbonEagerLoadProperties;

该类依赖注入了之前注册了 RibbonClientSpecification 和 Ribbon Client 饥饿加载相关属性配置类 RibbonEagerLoadProperties。默认情况下不进行饥饿加载,所以会导致第一次请求速度较慢

@Bean@ConditionalOnMissingBeanpublic SpringClientFactory springClientFactory() {    SpringClientFactory factory = new SpringClientFactory();    factory.setConfigurations(this.configurations);    return factory;}@Bean@ConditionalOnMissingBean({LoadBalancerClient.class})public LoadBalancerClient loadBalancerClient() {    return new RibbonLoadBalancerClient(this.springClientFactory());}@Bean@ConditionalOnMissingBeanpublic PropertiesFactory propertiesFactory() {    return new PropertiesFactory();}

紧接着,在该类中配置了 SpringClientFactory,LoadBalancerClient 和 PropertiesFactory。

SpringClientFactory

该类是 Spring 创建 Ribbon 客户端、负载均衡器、客户端配置实例的工厂,并且为每个 client name 创建对应的 Spring ApplicationContext。

public class SpringClientFactory extends NamedContextFactory
{ static final String NAMESPACE = "ribbon"; public SpringClientFactory() { super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name"); } .....}

该类继承了 NamedContextFactory,并且在构造函数中,将 RibbonClientConfiguration 类作为所有 Ribbon Client 的 默认配置类。通过 RibbonClientConfiguration 类的源码可知,Spring 为 Ribbon Client 提供了以下默认配置:

Configuration ClassName
IRule ZoneAvoidanceRule
IPing DummyPing
ServerList ConfigurationBasedServerList
ServerListUpdater PollingServerListUpdater
ILoadBalancer ZoneAwareLoadBalancer
ServerListFilter ZonePreferenceServerListFilter

同时,该类将 RibbonClientSpecification 集合作为参数传递到 NamedContextFactory ,作为创建 Ribbon Client 上下文的依据。

RibbonLoadBalancerClient

public class RibbonLoadBalancerClient implements LoadBalancerClient {    private SpringClientFactory clientFactory;    public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {        this.clientFactory = clientFactory;    }    ....}

通过源码可以看到,该类实现了 LoadBanlancerClient 接口,并且封装了 SpringClientFactory。作为 Ribbon 实际的负载均衡客户端,该类实现了以下方法:

ServiceInstance choose(String serviceId);
T execute(String serviceId, LoadBalancerRequest
request) throws IOException;
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest
request) throws IOException;URI reconstructURI(ServiceInstance instance, URI original);

该类可用来根据 serverId 选取服务实例,达到负载均衡的目的,或者直接通过执行请求来进行负载均衡。详细用法我将在之后的请求过程中进行详细说明。

PropertiesFactory

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

该类主要用来进行客户端配置类的获取,在之前的示例演示了如何通过配置文件为 名为 test-server 的 Ribbon Client 配置负载均衡器 LoadBalancer 和服务检测规则 Ping。

通过该类的构造函数可知,我们可以在配置文件为 Ribbon Client 配置 ILoadBalancer, IPing, IRule, ServerList, ServerListFilter 等配置。

在这里插入图片描述

除以上介绍配置的三个 Bean,RibbonAutoConfiguration 还配置了其他如重试,饥饿加载,restClient 相关 Bean,这里暂时忽略,接下来继续阅读 LoadBalancerAutoConfiguration 的源码。

LoadBalancerAutoConfiguration

该类作为负载均衡器的自动配置类,在 RibbonAutoConfiguration 之后配置。

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

从源码可以看到该类进行自动配置的前提是存在 RestTemplate 类和 LoadBalancerClient Bean实例。

@LoadBalanced@Autowired(    required = false)private List
restTemplates = Collections.emptyList();

在该类中注入了我们之前使用 @LoadBalanced 注解标注的 RestTemplate。

这里我们主要关注以下几个配置的 Bean:

LoadBalancerRequestFactory

@Bean@ConditionalOnMissingBeanpublic LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {    return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);}

该类为 LoadBalancerInterceptor 和 RetryLoadBalancerInterceptor 创建 LoadBalancerRequest,并且使用 LoadBalancerRequestTransformer 拦截 HttpRequest。

LoadBalancerInterceptor & RestTemplateCustomizer

@Configuration(    proxyBeanMethods = false)@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})static class LoadBalancerInterceptorConfig {    LoadBalancerInterceptorConfig() {    }    @Bean    public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {        return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);    }    @Bean    @ConditionalOnMissingBean    public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {        return (restTemplate) -> {            List
list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; }}

在内部配置类 LoadBalancerInterceptorConfig 中,配置了 LoadBalancerInterceptor 和 RestTemplateCustomizer。

LoadBalancerInterceptor 拦截了 HTTP 请求,在 intercept 方法中,使用 LoadBalancerRequestFactory 创建 LoadBalancerRequest 并使用 LoadBalancerClient 的 execute 方法进行请求的负载均衡

RestTemplateCustomizer 则是为所有的 RestTemplate 设置了 LoadBalancerInterceptor 拦截器。而该动作的发生是通过配置实现 SmartInitializingSingleton 接口的 Bean实施的

SmartInitializingSingleton

在所有 Bean 初始化完成后,Spring 容器会回调该接口的 afterSingletonsInstantiated 方法,这里正是使用这个特性对所有的 RestTempalte 设置拦截器。

@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider
> restTemplateCustomizers) { return () -> { restTemplateCustomizers.ifAvailable((customizers) -> { Iterator var2 = this.restTemplates.iterator(); while(var2.hasNext()) { RestTemplate restTemplate = (RestTemplate)var2.next(); Iterator var4 = customizers.iterator(); while(var4.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next(); customizer.customize(restTemplate); } } }); };}

在这里插入图片描述

至此,Spring Cloud Ribbon 的自动装配过程以及 RestTemplate 的请求负载均衡能力的设置已经介绍完毕,之后我会继续根据源码,对 RestTemplate 请求流程进行梳理。

总结

通过以上源码介绍我们可以了解到,Spring Cloud Ribbon 先通过 RibbonClientConfigurationRegistrar 类将所有的使用 @RibbonClient 和 @RibbonClients 中配置的 Ribbon Client 注册为 RibbonClientSpecification 的 Bean。再通过 RibbonAutoConfiguration 自动配置类,创建了 SpringClientFactory 和 RibbonLoadBalancerClient,分别用来创建 Ribbon Client 的 application context 和 Ribbon 的负载客户端。最后通过 LoadBalancerAutoConfiguration 自动配置类为所有使用 @LoadBalanced 注解的 RestTemplate 设置了 LoadBalancerInterceptor 拦截器,使其具有了请求负载均衡的功能。

在这里插入图片描述

参考资料

https://www.jianshu.com/p/1bd66db5dc46

https://blog.csdn.net/baidu_36327010/article/details/82109069

https://blog.csdn.net/qq_27529917/article/details/80981109

https://juejin.im/post/6844904151780966407#heading-15

https://blog.csdn.net/wuqinduo/article/details/103261367

你可能感兴趣的文章
Linux系统编程——线程池
查看>>
基于Visual C++2013拆解世界五百强面试题--题5-自己实现strstr
查看>>
Linux 线程信号量同步
查看>>
C++静态成员函数访问非静态成员的几种方法
查看>>
类中的静态成员函数访问非静态成员变量
查看>>
C++学习之普通函数指针与成员函数指针
查看>>
C++的静态成员函数指针
查看>>
Linux系统编程——线程池
查看>>
yfan.qiu linux硬链接与软链接
查看>>
Linux C++线程池实例
查看>>
shared_ptr简介以及常见问题
查看>>
c++11 你需要知道这些就够了
查看>>
c++11 你需要知道这些就够了
查看>>
shared_ptr的一些尴尬
查看>>
C++总结8——shared_ptr和weak_ptr智能指针
查看>>
c++写时拷贝1
查看>>
C++ 写时拷贝 2
查看>>
Linux网络编程---I/O复用模型之poll
查看>>
Java NIO详解
查看>>
单列模式-编写类ConfigManager读取属性文件
查看>>