SpringCloud源码解析 -- RestTemplate与@LoadBalanced

900 阅读6分钟

SpringCloud源码解析 -- RestTemplate与@LoadBalanced
SpringCloud源码解析 -- Spring Cloud Config与@RefreshScope
SpringCloud源码解析 -- Zuul实现原理
SpringCloud源码解析 -- Spring Cloud Sleuth原理探究
SpringCloud源码解析 -- Eureka原理探究

本文主要分享SpringCloud中RestTemplate与@LoadBalanced的实现原理。

RestTemplate处理请求
先看一下RestTemplate是怎么处理Http请求的。熟悉RestTemplate的同学可以跳过这一部分。

RestTemplate#doExecute

protected <T> T doExecute(URI url, @Nullable HttpMethod method, @Nullable RequestCallback requestCallback,
		@Nullable ResponseExtractor<T> 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<ClientHttpRequestInterceptor> 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<RestTemplate> 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> T execute(String serviceId, LoadBalancerRequest<T> 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<ClientHttpResponse> 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实现的整体思路,有兴趣的同学可以自行深入学习具体的实现细节。

如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!