本文主要分享SpringCloud中RestTemplate与@LoadBalanced的实现原理。
源码分析基于Spring Cloud Hoxton
RestTemplate处理请求
先看一下RestTemplate是怎么处理Http请求的。熟悉RestTemplate的同学可以跳过这一部分。
RestTemplate#doExecute
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
创建一个ClientHttpRequest,ClientHttpRequest代表一个Http请求
#2
使用RequestCallback处理request,参数拼接,转化等
#3
发起Http请求
#4
处理Http请求结构,转化对象
#5
异常处理,关闭连接
#1
步骤 -> HttpAccessor#createRequest -> ClientHttpRequestFactory#createRequest
这里由不同的ClientHttpRequestFactory创建Request,我们配置RestTemplate通常会指定ClientHttpRequestFactory,例如
@Bean
public RestTemplate restTemplate() {
return new RestTemplate(new SimpleClientHttpRequestFactory());
}
默认的ClientHttpRequestFactory也是SimpleClientHttpRequestFactory。
SimpleClientHttpRequestFactory#createRequest
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,该方法由bstractClientHttpRequest子类实现。
RestTemplate拦截机制
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();
}
}
#1
InterceptingClientHttpRequest#interceptors是一个ClientHttpRequestInterceptor集合,ClientHttpRequestInterceptor是一个拦截器接口,负责定义对Http请求的拦截操作,InterceptingRequestExecution是Http请求执行器,InterceptingRequestExecution#iterator就是拦截器集合迭代器。
#2
执行拦截操作,注意方法最后的this参数,ClientHttpRequestInterceptor#intercept方法中必须继续调用AbstractBufferingClientHttpRequest#executeInternal,才能将调用链继续。
#3
InterceptingClientHttpRequest#interceptingRequestFactory就是ClientHttpRequestFactory,这一步才构造真正的请求HttpRequest,并发起http请求。
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();
}
}
#1
如果RestTemplate中存在拦截器,这里会创建一个InterceptingClientHttpRequestFactory,该factory生成InterceptingClientHttpRequest。
注意InterceptingClientHttpRequestFactory构造方法,将原始的RequestFactory和拦截器列表interceptors作为参数,InterceptingRequestExecution#execute方法会用到这些数据。
#2
如果RestTemplate中没有interceptor,直接使用原始的RequestFactory。
@LoadBalanced的实现原理
@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实现原理
LoadBalancerInterceptor#intercept
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));
}
#1
通过LoadBalancerRequestFactory#createRequest方法生成构造一个LoadBalancerRequest,LoadBalancerRequest代表一个LoadBalancer请求。
而loadBalancer是一个LoadBalancerClient,他是一个loadBalancer客户端,用于执行LoadBalancerRequest。
RibbonLoadBalancerClient#execute
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);
}
#1
通过SpringClientFactory构造一个ILoadBalancer负载均衡器,SpringClientFactory类是一个用来创建客户端负载均衡器的工厂类,该工厂会为每一个serviceId生成不同的Spring上下文。
#2
通过ILoadBalancer负载均衡器选择一个Server
#3
构建一个RibbonServer,它实现了ServiceInstance接口,ServiceInstance代表一个服务实例,提供getHost/getPort等方法。
#4
执行LoadBalancerRequest
#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);
};
}
#1
ServiceRequestWrapper重写了HttpRequest的getURI方法,将serviceId转化为真正的服务实例url,有兴趣的同学可以阅读ServiceRequestWrapper源码
#2
对HttpRequest做转化处理
#3
发起Http请求
回到RibbonLoadBalancerClient#execute方法#2
步骤,
RibbonLoadBalancerClient#getServer -> BaseLoadBalancer#chooseServer -> IRule#choose。
IRule代表不同的负载均衡策略,有BestAvailableRule,AvailabilityFilteringRule,RetryRule,RoundRobinRule,RandomRule等策略。
负载均衡器ILoadBalancer
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());
}
#1
通过PollingServerListUpdater定时从注册中心中拉取最新的可用的服务实例数据,
PollingServerListUpdater实现了ServerListUpdater接口,ServerListUpdater接口定义定时从注册中心更新数据的执行策略。
#2
通过ServerList获取服务实例数据并缓存,ServerList是ribbon提供的接口,其中getInitialListOfServers方法可以获取初始化的服务实例列表,而getUpdatedListOfServers方法可以获取最新的服务实例列表。由具体的注册中心实现该接口,如DiscoveryEnabledNIWSServerList,ConsulServerList等。
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实现的整体思路,有兴趣的同学可以自行深入学习具体的实现细节。
如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!