Spring Cloud LoadBalancer负载均衡策略中的RestTemplate为什么打上@LoadBalanced注解就能以服务名访问到服务
为什么要学这个?你不想面试的时候多装两个逼多拿两千块钱?Robbin负载均衡分析,请看我的Robbin负载均衡文章。
**.
SpringBoot启动时,我们知道会根据工厂机制对自动化配置类进行加载。那么对于@LoadBalanced而言,对应的加载类是LoadBalancerAutoConfiguration。我们进入到此类看看,注意看我的注释说明!
@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired(
required = false
)
//1.获取到上下文对象中所有被@LoadBalanced注解修饰的RestTemplate!
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(
required = false
)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
public LoadBalancerAutoConfiguration() {
}
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
//2.List<RestTemplateCustomizer>是上下文对象中存在的RestTemplateCustomizerBean的集合
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();
//3.遍历步骤1中获取的RestTemplate集合,并使用RestTemplateCustomizer集合给每个RestTemplate定制。
customizer.customize(restTemplate);
}
}
});
};
}
那这个RestTemplateCustomizer在定制的时候做了什么操作呢? 好,继续看,此类中的LoadBalancerInterceptorConfig配置类定义了RestTemplateCustomizer,下面是源码
@Configuration
//1.条件注解,LoadBalancerInterceptorConfig 只有在ClassLoader不存在RetryTemolate时才会生效(我们不管它)
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
LoadBalancerInterceptorConfig() {
}
@Bean
//2.定义LoadBalancerInterceptor Bean 这个拦截器继承ClientHttpRequestInterceptor,可以被添加到RestTemplate的拦截器列表中。
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
//3.定义RestTemplateCustomizer Bean 会在LoadBalancerAutoConfiguration 配置类中的restTemplateCustomizers存在
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor); //4.在RestTemplate的拦截器中添加LoadBalanceInterceptor拦截器!
restTemplate.setInterceptors(list);
};
}
}
好,到这里总结一下,用@LoadBalanced直接修饰的RestTemplate会被添加一个LoadBalanceInterceptor拦截器!!! 好!那么这个拦截器做了什么呢?咱们继续往下看。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
// 1.构造器!构造器需要LoadBalancerClient 和LoadBalancerRequestFactory 参数(默认会在LoadBalancerAutoConfiguration里被构造,开发人员可以进行覆盖)。前者根据负载均衡请求(LoadBalancerRequest)和服务名做真正的服务调用;后者是个工厂,负责构造负载均衡请求LoadBalancerRequest,构造过程会使用LoadBalancerRequestTransformer对请求做一些自定义的转换。
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
//2.服务名使用URI中的Host信息。
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
//3.使用LoadBalancerClient客户端负载均衡器做真正的服务调用!!!!
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
总结,好,现在我们知道,被@LoadBalanced直接修饰的RestTemplate发出的请求,最后会被LoadBalanceInterceptor拦截!最后由LoadBalancerClient发出真正的请求。下面我们简单了解一下LoadBalancerClient。 LoadBalancerClient也叫客户端负载均衡器,会根据负载均衡请求和服务名执行真正的负载均衡操作。看看源码:
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
serviceId:服务名
request:负载均衡请求
返回值:基于选中的ServiceInstance实例在负载均衡请求下返回的结果
**/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
serviceId:服务名
serviceInstance服务实例
request:负载均衡请求
返回值:基于选中的ServiceInstance实例在负载均衡请求下返回的结果
**/
<T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
/**
使用ServiceInstance中的host和port构造出真正的URI。
比如!http://我的服务名/path/to/service这个URI里的“我的服务名”对应的hsot是127.0.0.1,port是8083,最后都会被构造成真正的URI!!!!
此方法的参数:
instance,服务实例
original,老URI
返回值:构造后的URI
**/
URI reconstructURI(ServiceInstance instance, URI original);
}
为什么有两个重载的方法呢,其中一个还没有ServiceInstance对象。(关于ServiceInstance对象是什么,里面有哪些熟悉,大家自己百度下,简单说就是咱们通过服务发现,把发现的服务通过ServiceInstance对象的形式,保存在内存中)。 如果调用没有ServiceInstance参数的方法,我们的execute方法内部就会通过choose方法使用负载均衡算法得到一个serviceInstance!!!然后再去调用有serviceInstance的execute方法!
好了 面试达到这儿其实就可以了。下面我来给大家总结一下!!!
@LoadBalanced注解修饰的RestTemplate,会被RestTemplateCustomizer进行包装,强行的为其增加一个拦截器,在使用RestTemplate+服务名发请求的时候,会被LoadBalanceIntercepto拦截器拦截。拦截之后会根据服务名去获取服务实例,也就是ServiceInstance!!! 这个ServiceInstance对象内封装了服务的所有属性,包括我们最关心的Host和Port!! 拦截器获取到服务实例之后会由LoadBalancerClient 对象进行正式的服务调用!LoadBalancerClient 内的三个接口就在上面我刚刚发出来你们应该没忘吧。好,这就是RestTemplate为什么打上@LoadBalanced注解就能以服务名访问到服务的底层原理!
关于一些其他的知识例如自定义负载均衡算法,@LoadBalancerClient注解等我在这里就赘述了。大家看完了点个赞,码字不容易啊。。。