本文主要分享SpringCloud中RestTemplate与@LoadBalanced的实现原理。
源码分析基于Spring Cloud Hoxton
先看一下RestTemplate是怎么处理Http请求的。熟悉RestTemplate的同学可以跳过这一部分。
protected T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor responseExtractor) throws RestClientException {
Assert.notNull(url, "URI is required");
Assert.notNull(method, "HttpMethod is required");
ClientHttpResponse response = null;
try {
// #1
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
// #2
requestCallback.doWithRequest(request);
}
// #3
response = request.execute();
// #4
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
// #5
...
}
步骤1 -> HttpAccessor#createRequest -> ClientHttpRequestFactory#createRequest 这里由不同的ClientHttpRequestFactory创建Request,我们配置RestTemplate通常会指定ClientHttpRequestFactory,例如
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new SimpleClientHttpRequestFactory());
}
默认的ClientHttpRequestFactory也是SimpleClientHttpRequestFactory。
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
可以看到,SimpleClientHttpRequestFactory#createRequest每次都创建一个新的连接,没有使用连接池,因而性能很差,使用RestTemplate一定要注意不要使用它。
RestTemplate#doExecute方法#3
步骤 -> AbstractClientHttpRequest#execute -> AbstractClientHttpRequest#executeInternal,该方法由AbstractClientHttpRequest子类实现。
InterceptingClientHttpRequest实现了ClientHttpRequest,并且支持对Http请求进行拦截操作。
AbstractBufferingClientHttpRequest#executeInternal -> InterceptingClientHttpRequest#executeInternal -> InterceptingRequestExecution#execute
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
// #1
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
// #2
return nextInterceptor.intercept(request, body, this);
}
else {
// #3
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
InterceptingClientHttpRequest是在哪里构造的呢?
回到RestTemplate父类InterceptingHttpAccessor#getRequestFactory
public ClientHttpRequestFactory getRequestFactory() {
List interceptors = getInterceptors();
// #1
if (!CollectionUtils.isEmpty(interceptors)) {
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
// #2
return super.getRequestFactory();
}
}
@LoadBalanced也是通过ClientHttpRequestInterceptor实现的。
LoadBalancerAutoConfiguration$LoadBalancerInterceptorConfig类中,构造了LoadBalancerInterceptor和RestTemplateCustomizer,其中LoadBalancerInterceptor就是实现LoadBalanced功能的拦截器,而RestTemplateCustomizer负责将LoadBalancerInterceptor添加到restTemplate中。
LoadBalancerAutoConfiguration#loadBalancedRestTemplateInitializerDeprecated生成了一个SmartInitializingSingleton,用于执行RestTemplateCustomizer。
比较有趣的是LoadBalancerAutoConfiguration#restTemplates定义
@LoadBalanced
@Autowired(required = false)
private List restTemplates = Collections.emptyList();
这里可以将SpringContext中使用了@LoadBalanced标注的RestTemplate注入进来,为什么呢?
因为@LoadBalanced注解上标注了@Qualifier注解。
Spring在处理@Autowired注解时,发现@LoadBalanced上有@Qualifier注解,就会检查引入的RestTemplate是否也有@LoadBalanced注解,具体实现在QualifierAnnotationAutowireCandidateResolver#checkQualifier中。这部分内容可以参考 @Value,@Autowired实现原理
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// #1
return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
通过LoadBalancerRequestFactory#createRequest方法生成构造一个LoadBalancerRequest,LoadBalancerRequest代表一个LoadBalancer请求。而loadBalancer是一个LoadBalancerClient实例,它是一个LoadBalancer客户端,用于执行LoadBalancerRequest。
public T execute(String serviceId, LoadBalancerRequest request, Object hint)
throws IOException {
// #1
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// #2
Server server = getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// #3
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
// #4
return execute(serviceId, ribbonServer, request);
}
#4
步骤 -> LoadBalancerRequest#apply -> LoadBalancerRequestFactory#createRequest(该方法返回了匿名的LoadBalancerRequest)
public LoadBalancerRequest createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
// #1
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
// #2
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
// #3
return execution.execute(serviceRequest, body);
};
}
回到RibbonLoadBalancerClient#execute方法#2
步骤,RibbonLoadBalancerClient#getServer -> BaseLoadBalancer#chooseServer -> IRule#choose。
IRule代表不同的负载均衡策略,有BestAvailableRule,AvailabilityFilteringRule,RetryRule,RoundRobinRule,RandomRule等策略。
ILoadBalancer是一个负载均衡器,它维护一个存储服务实例的Server列表以实现负载均衡操作,同时提供chooseServer选择一个服务实例。
BaseLoadBalancer提供了基础的负载均衡功能,维护了两个列表allServerLock,upServerLock,分别存储所有服务实例以及正常服务实例。它还维护了一个IPing接口,该接口的isAlive方法可以检测服务实例是否可用。而IPingStrategy则是执行ping的策略。
DynamicServerListLoadBalancer能从注册中心中获取服务实例数据,并通过ServerListFilter过滤部分不符合规则的服务实例。
DynamicServerListLoadBalancer构造方法中调用restOfInit方法完成部分初始化工作
void restOfInit(IClientConfig clientConfig) {
boolean primeConnection = this.isEnablePrimingConnections();
this.setEnablePrimingConnections(false);
// #1
enableAndInitLearnNewServersFeature();
// #2
updateListOfServers();
if (primeConnection && this.getPrimeConnections() != null) {
this.getPrimeConnections()
.primeConnections(getReachableServers());
}
this.setEnablePrimingConnections(primeConnection);
LOGGER.info("DynamicServerListLoadBalancer for client {} initialized: {}", clientConfig.getClientName(), this.toString());
}
(DiscoveryEnabledNIWSServerList可以从Eureka注册中心获取服务实例列表,实际上也是通过com.netflix.discovery.DiscoveryClient实现,有兴趣的同学可以自行阅读源码)
RibbonClientConfiguration中可以看到ILoadBalancer的默认实现为ZoneAwareLoadBalancer,IRule的默认实现为ZoneAvoidanceRule
ZoneAwareLoadBalancer可以避免因为跨Zone而导致的区域性故障,从而实现了服务的高可用,并且可以按照某种策略例如Zone的服务实例数量,故障率等等来筛选掉不符合条件的Zone区域。
ZoneAvoidanceRule能够在多Zone环境下根据某些策略(如可用性),选出最佳区域的Zone,再在Zone中通过轮询选择一个Server。zone和region是Eureka引用AWS的概念。
AsyncRestTemplate在Spring5中已经Deprecated,推荐使用WebClient。等到讲解Spring Reactive时再分享它的实现原理。
Ribbon的内容还是很多的,这里只是梳理了LoadBalanced实现的整体思路,有兴趣的同学可以自行深入学习具体的实现细节。