引言
书接上篇 负载均衡组件Ribbon核心-@LoadBalanced-上 我们讲完了理解@LoadBalanced注解的知识准备,接下来就是@LoadBalanced注解详解。
LoadBalancerAutoConfiguration 激活
@LoadBalanced 注解功能起点来至LoadBalancerAutoConfiguration自动配置类,SpringBoot容器启动的时,根据加入的依赖(spring-web/spring-cloud-ribbon),LoadBalancerAutoConfiguration 配置类会被激活
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
}
激活条件:
@ConditionalOnClass(RestTemplate.class):spring-web 依赖引入RestTemplate类
@ConditionalOnBean(LoadBalancerClient.class):要求容器内有LoadBalancerClient Bean实例
这是,肯定有朋友疑惑:@LoadBalanced功能起点不是这个配置类么,哪来LoadBalancerClient Bean实例。有疑惑是对的,@LoadBalanced 注解主宰的是Ribbon负载均衡功能,而不是整个Ribbon操作,LoadBalancerClient Bean 在Ribbon 加载前就已经配置被创建了。且看
这个就是Ribbon组件引入的启动器,里面有个Ribbion自动配置类: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 {
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
}
再看头顶贴上的注解:
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
真相来了,RibbonAutoConfiguration 配置类的启动早于LoadBalancerAutoConfiguration,也就是说,RibbonAutoConfiguration 配置的LoadBalancerClient 实例在LoadBalancerAutoConfiguration启动前就已经实例化好了。
RestTemplate 负载均衡能力集成
当了解了LoadBalancerAutoConfiguration配置类如何进行激活,接下就看如何将负载均衡的能力集成在RestTemplate实例中。总结一下需要走下面3步:
1>RestTemplate 收集 2>RestTemplate 定制器 3>RestTemplate拦截注入
步骤1:RestTemplate 收集
我们从LoadBalancerAutoConfiguration配置类restTemplates集合属性发现端倪。
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
...
}
从源码上看,Spring容器会收集所有RestTemplate实例,并注入restTemplates 中。此时问题来了,它怎么知道哪些RestTemplate 类需要添加负载均衡功能?这是就用到@Qualifier 知识点了(不了解朋友可以看上篇)。
先看@LoadBalanced注解的源码
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
发现它头上有个元注解:@Qualifier, 意味着@LoadBalanced 拥有@Qualifier 注解所有功能,
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
-------------------------------------------------------------------------------
//@LoadBalanced
@Qualifier //等价
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
很明显,它用上了@Qualifier 注解 Bean筛选标记功能。
步骤2:RestTemplate 定制器
步骤1完成后,可以得到待加负载均衡逻辑的restTemplates 集合。接下来就是集成了,Spring提供了XxxCustomizer类实现实例定制,下面是RestTemplate定制器接口源码
public interface RestTemplateCustomizer {
void customize(RestTemplate restTemplate);
}
接口只有一个方法:customize, 参数为RestTemplate,很明显看出它就用来改造RestTemplate 实例的。
继续往下,LoadBalancerAutoConfiguration配置类下有个LoadBalancerInterceptorConfig 静态内部类
public class LoadBalancerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
....
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
}
类内部,定义了用于构建RestTemplateCustomizer 定制器实例方法:restTemplateCustomizer(loadBalancerInterceptor),参数是loadBalancerInterceptor 拦截器。后续将该拦截器添加到restTemplate 实例中。
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
定制逻辑完成,接下来回答2个问题,RestTemplate 类负载均衡逻辑就通了
问题1:RestTemplate 定制器怎么执行的?
问题2:LoadBalancerInterceptor 拦截器做啥逻辑?
这里先回答问题1,问题2在步骤3详解
RestTemplate 定制器使用 SmartInitializingSingleton 接口 (不理解/忘记可以看上篇)实现调用。
当Spring容器所有单例Bean实例初始化成功之后,实现SmartInitializingSingleton 接口Bean实例afterSingletonsInstantiated 方法会被回调。
public class LoadBalancerAutoConfiguration {
....
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
....
}
源码逻辑:收集容器中所有RestTemplateCustomizer 定制器,对步骤1收集到的LoadBalancerAutoConfiguration.this.restTemplates RestTemplate对象集合,一一定制。
步骤3:RestTemplate 拦截器
还是LoadBalancerAutoConfiguration配置类下有个LoadBalancerInterceptorConfig 静态内部类
public class LoadBalancerAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
....
}
}
}
类内部,定义ribbonInterceptor 实例方法,参数为loadBalancerClient,requestFactory ,目的地是创建LoadBalancerInterceptor 拦截器实例。
步骤2中问题:LoadBalancerInterceptor 拦截器做了啥操作?
上篇文章我们了解过ClientHttpRequestInterceptor拦截器,当RestTemplate 调用getForEntity/getForObject方法发起http请求时,会执行拦截逻辑,LoadBalancerInterceptor 拦截器就是ClientHttpRequestInterceptor 实现类
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
....
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
大白话结论
综上3个步骤,得出结论:
当贴有@LoadBalanced 注解的RestTemplate发起getForEntity/getForObject 会执行RestTemplateCustomizer 定制器绑定LoadBalancerInterceptor 拦截器 intercept 方法,然后返回ClientHttpResponse 结果。
负载均衡后续
到这,肯定有好学的小伙伴问,最后一步的负载均衡怎么调用啊?这个问题已经超出本篇文章的讨论了,如果想深究的朋友,还可以继续:
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
LoadBalancerInterceptor 拦截器 intercept 方法执行,调用了this.loadBalancer.execute 实现远程服务调用,负载均衡的调用效果,就由这个方法完成。再深入,就进到:
public class RibbonLoadBalancerClient implements LoadBalancerClient {
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
return execute(serviceId, ribbonServer, request);
}
}
最核心逻辑:
Server server = getServer(loadBalancer, hint);
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
if (loadBalancer == null) {
return null;
}
// Use 'default' on a null hint, or just pass it on?
return loadBalancer.chooseServer(hint != null ? hint : "default");
}
这里的chooseServer方法,就是根据Ribbon配置的负载均衡策略来调用服务了,默认是
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {
public Server chooseServer(Object key) {
...
}
}
好了,到这,就真的结束了~