【源码】Spring Cloud —— Ribbon 4 Ribbon 与 OpenFeign、RestTemplate 的整合
前言
至此,对 Ribbon 的部分核心接口和实现类做了解读。通常的,我们使用 Spring Cloud 单独整合 Ribbon 时,可以通过在 RestTemplate 实例上添加 @LoadBalanced
注解,实现 RestTemplate 发送请求的 负载均衡。同样,在 Spring Cloud 整合 OpenFeign 使用时,默认也会将 Ribbon 整合进去
本章节,结合部分源码对 Ribbon 与 OpenFeign、RestTemplate 的整合做相关解读
版本
Spring Cloud Netflix 版本:2.2.3.RELEASE
Ribbon 与 RestTemplate
通过 @LoadBalanced
注解对 RestTemplate 实例做标记,然后赋予它 负载均衡 的能力,详情见核心配置类 LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
/**
* 收集所有的 RestTemplate 实例
*/
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
// ...
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
/**
* 使用容器中的 RestTemplateCustomizer
* 自定义处理所有的 RestTemplate 实例
*/
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
// ...
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
/**
* 容器默认提供的 LoadBalancerInterceptor 实例
*/
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
/**
* 由 LoadBalancerInterceptor 拦截处理
*/
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
- 收集 RestTemplate 实例
- 所有 RestTemplate 交由 RestTemplateCustomizer 自定义处理
- 同时,提供了 RestTemplateCustomizer 的一个实例,该实例为 RestTemplate 添加一个拦截类 LoadBalancerInterceptor
- 同时,提供了 LoadBalancerInterceptor 的实例
LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
// ...
@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);
/**
* 拦截逻辑,即交给 LoadBalancerClient#execute 方法~
*/
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
拦截逻辑即交给 LoadBalancerClient#execute
方法,于是便回到了第二章节的内容,便不再多说了
Ribbon 与 OpenFeign
在之前解读 OpenFeign 的文章中,我们了解到 FeignContext 上下文提供的 Client 实例都被包装成 LoadBalancerFeignClient
其本质是一层装饰,对于不需要 负载均衡 的场景,取
LoadBalancerFeignClient.delegate 属性即可
关于 OpenFeign 解读的传送门:
【源码】Spring Cloud —— OpenFeign 2 FeignClientFactoryBean 等
最终的请求由 LoadBalancerFeignClient#execute
方法发出
LoadBalancerFeignClient#execute
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
/**
* 此处的 FeignLoadBalancer.RibbonRequest 是基于
* this.delegate 创建的
*/
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
IClientConfig requestConfig = getClientConfig(options, clientName);
/**
* lbClient:获取 FeignLoadBalancer 缓存对象
* executeWithLoadBalancer:选择对应的 server 实例发送请求
* toResponse:返回结果
*/
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
// ...
}
}
根据方法名 executeWithLoadBalancer
不难猜测 负载均衡 逻辑由该方法实现
AbstractLoadBalancerAwareClient#executeWithLoadBalancer
该方法由 FeignLoadBalancer 的抽象父类 AbstractLoadBalancerAwareClient 提供
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 获取 LoadBalancerCommand 对象
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
/**
* LoadBalancerCommand#submit 方法传入一个 ServerOperation 对象,返回一个 Observable 实例
*/
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
/**
* 最终还是委托给 FeignLoadBalancer#execute 执行
*/
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
// ...
}
}
获取对应的 LoadBalancerCommand 对象,创建匿名 ServerOperation 对象,执行 submit
方法
LoadBalancerCommand#submit
public Observable<T> submit(final ServerOperation<T> operation) {
// ...
/**
* selectServer 方法选择对应的 Server 实例发送请求
*/
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
// ...
return operation.call(server).doOnEach(new Observer<T>() {
// ...
});
// ...
}
- 由
selectServer
方法选择对应的 Server 实例发送请求,其最终由ILoadBalancer#chooseServer
方法选取对应的 Server,即回到第二章节的内容 - 此处使用 RxJava,发射 Server 实例,执行对应逻辑,最后交由
ServerOperation#call
方法执行 - 此处的 ServerOperation 实例即
LoadBalancerCommand#submit
方法的入参,由AbstractLoadBalancerAwareClient#executeWithLoadBalancer
匿名构造,将方法委托给FeignLoadBalancer#execute
执行 FeignLoadBalancer#execute
的实现基于 FeignLoadBalancer.RibbonRequest 实现,而 FeignLoadBalancer.RibbonRequest 基于LoadBalancerFeignClient.delegate
属性构造,也就是说最终请求的发送由对应具体的 Client 负责(LoadBalancerFeignClient 作为 装饰者,仅仅拓展了 负载均衡 的能力,装饰者设计模式 的经典应用)
小结
整个调用链路比较绕,作个 时序图 总结一下:
总结
至此,Ribbon 的解读就结束了,类似于 OpenFeign 的实现,每个 Ribbon 实例都有自己的上下文,因此 自定义配置 就十分灵活了
上一篇:【源码】Spring Cloud —— Ribbon 3 IRule
参考
《Spring Cloud 微服务架构进阶》 —— 朱荣鑫 张天 黄迪璇