Ribbon 学习(二):Spring Cloud Ribbon 加载配置原理

说明

在上一篇博文《Ribbon 学习(一):脱离 Eureka 使用 Ribbon》一文中,我简单介绍了在脱离 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
@Qualifier
public @interface LoadBalanced {
}

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

@LoadBalanced
@Autowired(
    required = false
)
private List<RestTemplate> 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:

可以通过该注解自定义注解,以此实现特定依赖注入。更多内容请看官方文档 《1.9.4. Fine-tuning annotation-based autowiring with qualifiers》

在简单了解了 @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)
@Documented
public @interface RibbonClient {
    String value() default "";

    String name() default "";

    Class<?>[] configuration() default {};
}

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

关于 @Configuration 注解中设置 proxyBeanMethods 的原因,可以看这篇文章《Spring5.2-proxyBeanMethods作用》

RibbonClientConfigurationRegistrar

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

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

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 获取 @RibbonClients 注解属性
        Map<String, Object> 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<String, Object> 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<RibbonClientSpecification> configurations = new ArrayList();
@Autowired
private RibbonEagerLoadProperties ribbonEagerLoadProperties;

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

@Bean
@ConditionalOnMissingBean
public 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
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
    return new PropertiesFactory();
}

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

SpringClientFactory

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

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
    static final String NAMESPACE = "ribbon";

    public SpringClientFactory() {
        super(RibbonClientConfiguration.class, "ribbon", "ribbon.client.name");
    }
    
    .....
}

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

ConfigurationClassName
IRuleZoneAvoidanceRule
IPingDummyPing
ServerListConfigurationBasedServerList
ServerListUpdaterPollingServerListUpdater
ILoadBalancerZoneAwareLoadBalancer
ServerListFilterZonePreferenceServerListFilter

同时,该类将 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> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;

<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> 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<RestTemplate> restTemplates = Collections.emptyList();

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

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

LoadBalancerRequestFactory

@Bean
@ConditionalOnMissingBean
public 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<ClientHttpRequestInterceptor> 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 设置拦截器。

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> 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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值