本文共 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 属性配置了最终使用的服务器地址。本文基于之前使用示例进行源码阅读学习,而忽略了一些内容。
使用该注解配置 RestTemplate Bean,通过注解源码可以看到,该注解的本质是一个 @Qualifier 注解。
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}
在配置时通过 @Autowired 进行集合依赖注入:
@LoadBalanced@Autowired( required = false)private ListrestTemplates = 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 注解创建了名为 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 的原因,可以看这篇文章。
该类实现了 ImportBeanDefinitionRegistrar 接口,实现 Bean 的动态注入。其用法的相关介绍,请看这篇文章。
在该方法中,通过 AnnotationMetadata 的 getAnnotationAttributes 方法分别获取注解 @RibbonClients 和 @RibbonClient 的配置属性值,由此进行 Ribbon Client bean 的动态注入。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { // 获取 @RibbonClients 注解属性 Mapattrs = 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,以此实现自动装配。
@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 Listconfigurations = 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。
该类是 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 上下文的依据。
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 选取服务实例,达到负载均衡的目的,或者直接通过执行请求来进行负载均衡。详细用法我将在之后的请求过程中进行详细说明。
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 的源码。
该类作为负载均衡器的自动配置类,在 RibbonAutoConfiguration 之后配置。
@Configuration( proxyBeanMethods = false)@ConditionalOnClass({RestTemplate.class})@ConditionalOnBean({LoadBalancerClient.class})@EnableConfigurationProperties({LoadBalancerRetryProperties.class})public class LoadBalancerAutoConfiguration { .....}
从源码可以看到该类进行自动配置的前提是存在 RestTemplate 类和 LoadBalancerClient Bean实例。
@LoadBalanced@Autowired( required = false)private ListrestTemplates = Collections.emptyList();
在该类中注入了我们之前使用 @LoadBalanced 注解标注的 RestTemplate。
这里我们主要关注以下几个配置的 Bean:
@Bean@ConditionalOnMissingBeanpublic LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);}
该类为 LoadBalancerInterceptor 和 RetryLoadBalancerInterceptor 创建 LoadBalancerRequest,并且使用 LoadBalancerRequestTransformer 拦截 HttpRequest。
@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) -> { Listlist = 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实施的。
在所有 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