在上一篇博文《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 属性配置了最终使用的服务器地址。本文基于之前使用示例进行源码阅读学习,而忽略了一些内容。
使用该注解配置 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 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 注解创建了名为 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作用》。
该类实现了 ImportBeanDefinitionRegistrar 接口,实现 Bean 的动态注入。其用法的相关介绍,请看这篇文章《借助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,以此实现自动装配。
@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();
@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。
该类是 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 List restTemplates = Collections.emptyList();
在该类中注入了我们之前使用 @LoadBalanced 注解标注的 RestTemplate。
这里我们主要关注以下几个配置的 Bean:
@Bean
@ConditionalOnMissingBean
public 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) -> {
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实施的。
在所有 Bean 初始化完成后,Spring 容器会回调该接口的 afterSingletonsInstantiated 方法,这里正是使用这个特性对所有的 RestTempalte 设置拦截器。
@Bean
public 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