SpringCloud源码解析 -- RestTemplate与@LoadBalanced

本文主要分享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实现的整体思路,有兴趣的同学可以自行深入学习具体的实现细节。

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


你可能感兴趣的:(SpringCloud源码解析 -- RestTemplate与@LoadBalanced)