SpringCloud Ribbon结合RestTemplate 源码学习

一:需要用到的服务

首先,我们需要启动几个服务,分别为 eureka-sever, eureka-client-8762, eureka-client-8763 (同一个项目,两个不同的端口), eureka-bibbon-client ,让我们可以更好地学习Ribbon 运行 。
在这里插入图片描述

在这里插入图片描述

二:Ribbon是如何通过一个@LoadBalanced注解就实现负载均衡的

首先看下我们的接口入口代码:
controller:

package com.example.eurekaribbonclient.web;

import com.example.eurekaribbonclient.service.RibbonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/RibbonController")
public class RibbonController {

   @Autowired
    private RibbonService ribbonService;
    @GetMapping("/hi/{name}")
    public String helloRibbon(@PathVariable("name") String name) {
        StringBuffer sb  = new StringBuffer();
        //模仿 10次用户请求
        for (int i=0;i<10;i++){
            if(i==9){
                sb.append(ribbonService.helloRibbon(name));
            }else {
                sb.append(ribbonService.helloRibbon(name)).append("---");
            }
        }
        return sb.toString();
    }

}

service 的代码:

package com.example.eurekaribbonclient.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class RibbonService {
    @Autowired
    private RestTemplate restTemplate;

    public String helloRibbon(String name) {
        return restTemplate.getForObject("http://eureka-client/HiController/hi/" + name, String.class);
    }
}

RestTemplate 的关系图:
在这里插入图片描述
进入 restTemplate.getForObject() 这个方法,一直往里进入会找到这样一段代码:

/**
	 * Execute the given method on the provided URI.
	 * 翻译一下: 对提供的URI执行给定的方法。
	 * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
	 * the response with the {@link ResponseExtractor}.
	 * @param url the fully-expanded URL to connect to
	 * @param method the HTTP method to execute (GET, POST, etc.)
	 * @param requestCallback object that prepares the request (can be {@code null})
	 * @param responseExtractor object that extracts the return value from the response (can be {@code null})
	 * @return an arbitrary object, as returned by the {@link ResponseExtractor}
	 */
	@Nullable
	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 {
			ClientHttpRequest request = createRequest(url, method);
			if (requestCallback != null) {
				requestCallback.doWithRequest(request);
			}
			response = request.execute();
			handleResponse(url, method, response);
			return (responseExtractor != null ? responseExtractor.extractData(response) : null);
		}
		catch (IOException ex) {
			String resource = url.toString();
			String query = url.getRawQuery();
			resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
			throw new ResourceAccessException("I/O error on " + method.name() +
					" request for \"" + resource + "\": " + ex.getMessage(), ex);
		}
		finally {
			if (response != null) {
				response.close();
			}
		}
	}

这是看不出跟 Ribbon 有毛线关系,不急,在浏览器中 输入http://localhost:8764/RibbonController/hi/java ,debug看下:
在这里插入图片描述

发现 这个ClientHttpRequest 的 对象 request 中 有个 拦截器 ,从截图中可以看出这是LoadBalancerInterceptor ,终于看到一个 跟 Ribbon 的负载均衡 LoadBalancer 有关系的了。那这个LoadBalancerInterceptor 是怎么来的?我们点进去createRequest(url,method); 这个方法,最终会调用 HttpAccessor#createRequest方法,HttpAccessor是个什么东西呢,我们在RestTemplate类中按下 ctrl+alt+u查看他们的层级关系如下:
在这里插入图片描述
请注意看,RestTemplate继承了InterceptingHttpAccessor(看名字像http请求的访问拦截的意思,后续再说) 而 InterceptingHttpAccessor又继承了HttpAccessor,而createRequest方法如下:

这里调用了 getRequestFactory()方法获取到一个请求工厂,然后去创建 ClientHttpRequest ,而getRequestFactory()方法是在InterceptingHttpAccessor中进行复写实现的,跟踪interceptingHttpAccessor#getRequestFactory方法如下:

/**
	 * Overridden to expose an {@link InterceptingClientHttpRequestFactory}
	 * if necessary.
	 * @see #getInterceptors()
	 */
	@Override
	public ClientHttpRequestFactory getRequestFactory() {
		List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
		if (!CollectionUtils.isEmpty(interceptors)) {
			ClientHttpRequestFactory factory = this.interceptingRequestFactory;
			if (factory == null) {
				factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
				this.interceptingRequestFactory = factory;
			}
			return factory;
		}
		else {
			return super.getRequestFactory();
		}
	}

现在我们重新 在浏览器中调用 http://localhost:8764/RibbonController/hi/java,debug后可以发现,这个List interceptors = getInterceptors(); 获取了LoadBalancerInterceptor这个东西,
在这里插入图片描述
进入 getInterceptors(); 在 这个方法:

/**
	 * Return the request interceptors that this accessor uses.
	 * <p>The returned {@link List} is active and may get appended to.
	 */
	public List<ClientHttpRequestInterceptor> getInterceptors() {
		return this.interceptors;
	}

发现 只有this.interceptors 这个 ,那这个 interceptors 是怎么被set 进去的 呢? 在InterceptingHttpAccessor 这个类中有setInterceptors 这个 set 的方法:

/**
	 * Set the request interceptors that this accessor should use.
	 * <p>The interceptors will get sorted according to their order
	 * once the {@link ClientHttpRequestFactory} will be built.
	 * @see #getRequestFactory()
	 * @see AnnotationAwareOrderComparator
	 */
	public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors) {
		// Take getInterceptors() List as-is when passed in here
		if (this.interceptors != interceptors) {
			this.interceptors.clear();
			this.interceptors.addAll(interceptors);
			AnnotationAwareOrderComparator.sort(this.interceptors);
		}
	}

ctrl+鼠标左键 这个方法,看在哪里调用:
在这里插入图片描述
我们可以发现,LoadBalancerAutoConfiguration 类中有调用 setInterceptors 这个方法,类的代码如下:

/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.client.loadbalancer;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.retry.backoff.BackOffPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.web.client.RestTemplate;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Auto-configuration for Ribbon (client-side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 * @author Gang Li
 */
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

	@Bean
	//维护了 一个被@LoadBalanced 注解修饰的RestTemplate对象列表, 并在这里进行初始化, 通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerinterceptor拦截器
	public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
			final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
		return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
                    customizer.customize(restTemplate);
                }
            }
        });
	}

	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(
			LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
	}

	@Configuration
	@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
	static class LoadBalancerInterceptorConfig {
		@Bean
		//创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截, 以实现客户端负载均衡。
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		//创建了 一个RestTemplateCustomizer的Bean, 用于给RestTemplate增加LoadBalancerInterceptor拦截器。
		public RestTemplateCustomizer restTemplateCustomizer(
				final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
		}
	}

	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryAutoConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LoadBalancedRetryFactory loadBalancedRetryFactory() {
			return new LoadBalancedRetryFactory() {};
		}
	}

	@Configuration
	@ConditionalOnClass(RetryTemplate.class)
	public static class RetryInterceptorAutoConfiguration {
		@Bean
		@ConditionalOnMissingBean
		public RetryLoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
				LoadBalancerRequestFactory requestFactory,
				LoadBalancedRetryFactory loadBalancedRetryFactory) {
			return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
					requestFactory, loadBalancedRetryFactory);
		}

		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(
				final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                //设置拦截器
                restTemplate.setInterceptors(list);
            };
		}
	}
}

LoadBalancerAutoConfiguration类头上的注解可以知道, Ribbon实现的负载均衡自动化配置 需要满足下面两个条件。
• @ConditionalOnClass(RestTemplate.class): RestTemplate 类必须存在于当前工程的环境中。
• @ConditionalOnBean(LoadBalancerClient.class): 在Spring的Bean工程中必须有LoadBalancerClient的实现 Bean。

在该自动化配置类中, 主要做了下面三件事:
• 创建了一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截, 以实现客户端负载均衡。
• 创建了 一个RestTemplateCustomizer的Bean, 用于给RestTemplate增加LoadBalancerInterceptor拦截器。
• 维护了 一个被@LoadBalanced 注解修饰的RestTemplate对象列表, 并在这里进行初始化, 通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerinterceptor拦截器。

通过 list.add(loadBalancerInterceptor)和restTemplate.setInterceptors(list)两段代码可以看出,这是要给restTemplate加上loadBalancerInterceptor拦截器。

知道了LoadBalancerInterceptor 怎么获取之后,接下来, 我们看看LoadBalancerinterceptor 拦截器做了什么,是如何将 一个普通的RestTemplate变成客户端负载均衡的,进入LoadBalancerInterceptor 这个类:代码如下:

/*
 * Copyright 2013-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.client.loadbalancer;

import java.io.IOException;
import java.net.URI;

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;

/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 * @author William Tran
 */
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

	private LoadBalancerClient loadBalancer;
	private LoadBalancerRequestFactory requestFactory;

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
		this.loadBalancer = loadBalancer;
		this.requestFactory = requestFactory;
	}

	public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
		// for backwards compatibility
		this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
	}

	@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);
		return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
	}
}

通过LoadBalancerAutoConfiguration 的

@Bean
		public LoadBalancerInterceptor ribbonInterceptor(
				LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}

在这里插入图片描述
可以发现:在拦截器中注入了 LoadBalancerClient ,这个LoadBalancerClient 跟@LoadBalanced 有很大的联系
点进去@LoadBalanced 可以看到:

/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.client.loadbalancer;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient.
 * @author Spencer Gibb
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

从@LoadBalanced注解 源码的注释中可以知道,该注解 用于标记要配置作为使用LoadBalancerClient的restTemplate bean的注释 。
所以,得出一个结论:
当一个被@LoadBalanced 注解修饰的 RestTemplate 对象向外发起 HTTP 请求时, 会被 LoadBalancerinterceptor 类的 intercept 函数所拦截。(也可以这么理解,当RestTemplate 的对象加上@LoadBalanced 注解时,发送请求就会被拦截,请求交给 Ribbon的负载均衡器LoadBalancerCliet去执行负载均衡的逻辑 )

接着,我们看下这个LoadBalancerInterceptor 类的 intercept 方法,
在这里插入图片描述
点进去到这个 RibbonLoadBalancerClient 类,这个是Ribbon的核心。

/*
 * Copyright 2013-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cloud.netflix.ribbon;

import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.Map;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

import static org.springframework.cloud.netflix.ribbon.RibbonUtils.updateToSecureConnectionIfNeeded;

/**
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Ryan Baxter
 * @author Tim Ysewyn
 */
public class RibbonLoadBalancerClient implements LoadBalancerClient {

	private SpringClientFactory clientFactory;

	public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
		this.clientFactory = clientFactory;
	}

	@Override
	public URI reconstructURI(ServiceInstance instance, URI original) {
		Assert.notNull(instance, "instance can not be null");
		String serviceId = instance.getServiceId();
		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);

		URI uri;
		Server server;
		if (instance instanceof RibbonServer) {
			RibbonServer ribbonServer = (RibbonServer) instance;
			server = ribbonServer.getServer();
			uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
		} else {
			server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
			IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
			ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
			uri = updateToSecureConnectionIfNeeded(original, clientConfig,
					serverIntrospector, server);
		}
		return context.reconstructURIWithServer(server, uri);
	}

	@Override
	public ServiceInstance choose(String serviceId) {
	    return choose(serviceId, null);
	}

	/**
	 * New: Select a server using a 'key'.
	 */
	public ServiceInstance choose(String serviceId, Object hint) {
		Server server = getServer(getLoadBalancer(serviceId), hint);
		if (server == null) {
			return null;
		}
		return new RibbonServer(serviceId, server, isSecure(server, serviceId),
				serverIntrospector(serviceId).getMetadata(server));
	}

	@Override
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
	    return execute(serviceId, request, null);
	}

	/**
	 * New: Execute a request by selecting server using a 'key'.
	 * The hint will have to be the last parameter to not mess with the `execute(serviceId, ServiceInstance, request)`
	 * method. This somewhat breaks the fluent coding style when using a lambda to define the LoadBalancerRequest.
	 */
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

	@Override
	public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
		Server server = null;
		if(serviceInstance instanceof RibbonServer) {
			server = ((RibbonServer)serviceInstance).getServer();
		}
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}

		RibbonLoadBalancerContext context = this.clientFactory
				.getLoadBalancerContext(serviceId);
		RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

		try {
			T returnVal = request.apply(serviceInstance);
			statsRecorder.recordStats(returnVal);
			return returnVal;
		}
		// catch IOException and rethrow so RestTemplate behaves correctly
		catch (IOException ex) {
			statsRecorder.recordStats(ex);
			throw ex;
		}
		catch (Exception ex) {
			statsRecorder.recordStats(ex);
			ReflectionUtils.rethrowRuntimeException(ex);
		}
		return null;
	}

	private ServerIntrospector serverIntrospector(String serviceId) {
		ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
				ServerIntrospector.class);
		if (serverIntrospector == null) {
			serverIntrospector = new DefaultServerIntrospector();
		}
		return serverIntrospector;
	}

	private boolean isSecure(Server server, String serviceId) {
		IClientConfig config = this.clientFactory.getClientConfig(serviceId);
		ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
		return RibbonUtils.isSecure(config, serverIntrospector, server);
	}

	/**
	 * Note: This method could be removed?
	 */
	protected Server getServer(String serviceId) {
		return getServer(getLoadBalancer(serviceId), null);
	}

	protected Server getServer(ILoadBalancer loadBalancer) {
	    return getServer(loadBalancer, null);
	}

	protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

	protected ILoadBalancer getLoadBalancer(String serviceId) {
		return this.clientFactory.getLoadBalancer(serviceId);
	}

	public static class RibbonServer implements ServiceInstance {
		private final String serviceId;
		private final Server server;
		private final boolean secure;
		private Map<String, String> metadata;

		public RibbonServer(String serviceId, Server server) {
			this(serviceId, server, false, Collections.emptyMap());
		}

		public RibbonServer(String serviceId, Server server, boolean secure,
				Map<String, String> metadata) {
			this.serviceId = serviceId;
			this.server = server;
			this.secure = secure;
			this.metadata = metadata;
		}

		@Override
		public String getInstanceId() {
			return this.server.getId();
		}

		@Override
		public String getServiceId() {
			return this.serviceId;
		}

		@Override
		public String getHost() {
			return this.server.getHost();
		}

		@Override
		public int getPort() {
			return this.server.getPort();
		}

		@Override
		public boolean isSecure() {
			return this.secure;
		}

		@Override
		public URI getUri() {
			return DefaultServiceInstance.getUri(this);
		}

		@Override
		public Map<String, String> getMetadata() {
			return this.metadata;
		}

		public Server getServer() {
			return this.server;
		}

		@Override
		public String getScheme() {
			return this.server.getScheme();
		}

		@Override
		public String toString() {
			final StringBuilder sb = new StringBuilder("RibbonServer{");
			sb.append("serviceId='").append(serviceId).append('\'');
			sb.append(", server=").append(server);
			sb.append(", secure=").append(secure);
			sb.append(", metadata=").append(metadata);
			sb.append('}');
			return sb.toString();
		}
	}

}

一步步点进去:

/**
	 * New: Execute a request by selecting server using a 'key'.
	 * The hint will have to be the last parameter to not mess with the `execute(serviceId, ServiceInstance, request)`
	 * method. This somewhat breaks the fluent coding style when using a lambda to define the LoadBalancerRequest.
	 */
	public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
		ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
		Server server = getServer(loadBalancer, hint);
		if (server == null) {
			throw new IllegalStateException("No instances available for " + serviceId);
		}
		RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
				serviceId), serverIntrospector(serviceId).getMetadata(server));

		return execute(serviceId, ribbonServer, request);
	}

这个方法就是拦截器做的事情,debug看下:
在这里插入图片描述
这个看到 loadBalancer 有 两个 server,name 是 eureka-client ,端口 分别为 8762,8763 ,这两个就是 在注册中心中服务提供者的两个实例。然后看下 Server server = getServer(loadBalancer, hint);,点进去,

protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
		if (loadBalancer == null) {
			return null;
		}
		// Use 'default' on a null hint, or just pass it on?
		return loadBalancer.chooseServer(hint != null ? hint : "default");
	}

我们先来认识一下这个 ILoadBalancer 接口:

public interface ILoadBalancer {
    public void addServers(Lis七<Server> newServers);
    public Server chooseServer(Objec七key);
    public void markServerDown(Server server);
    public List<Server> getReachableServers () ;
    public List<Server> getA11Servers()
}

• addServers: 向负载均衡器中维护的实例列表增加服务实例。
• chooseServer: 通过某种策略, 从负载均衡器中挑选出 一个具体的服务实例。
• rnarkServerDown: 用来通知和标识负载均衡器中某个具体实例已经停止服务, 不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服务的。
• getReachableServers: 获取当前正常服务的实例列表。
• getA11Servers: 获取所有已知的服务实例列表, 包括正常服务和停止服务的实例。

loadBalancer.chooseServer(hint != null ? hint : “default”); 通过负载均衡策略, 从负载均衡器中挑选出 一个具体的服务实例。这个就可以获取我们具体是调哪个服务提供者的链接了。
在这里插入图片描述
从截图可以看出,通过负载均衡策略 选出了 8762 这个端口的链接 。
接着跟进execute方法:
在这里插入图片描述

看代码 T returnVal = request.apply(serviceInstance); 而request其实是execute方法传进来的参数,追溯到源头,发现是LoadBalancerInterceptor类的intercept方法里requestFactory.createRequest(request, body, execution)生成了LoadBalancerRequest,然后作为参数传入,之后再调用了apply方法
在这里插入图片描述
跟进createRequest方法里:
在这里插入图片描述

可以看出通过requestFactory.createRequest(request.getURI(), method)方法生成了ClientHttpRequest类的实例delegate,它的url就是我们最后真正要请求的,最后正常调用delegate.execute()方法取得返回ClientHttpResponse就好了。
而这里产生了一个疑问,url是怎么产生的?重新发起请求断点试下发现关键在LoadBalancerRequestFactory类中的createRequest方法中的这句:
跟进ServiceRequestWrapper类中,发现它继承了HttpRequestWrapper 类,同时重写了getURI方法
在这里插入图片描述

可以看到该方法返回了我们最后需要的url。

结语

总结 Ribbon 结合 RestTemplate 做了什么?
1.@LoadBalanced开启了RibbonLoadBalancerClient负载均衡支持
2.给RestTemplate 对象添加拦截器。(当 restTemplate 发送请求 ,如:restTemplate.getForObject() ),就会被拦截,请求交给 Ribbon的负载均衡器LoadBalancerCliet去执行负载均衡的逻辑 。
3. 通过负载均衡的策略 获取 url 后去执行相应的方法,从而达到了负载均衡的目的。

发布了33 篇原创文章 · 获赞 42 · 访问量 3166

猜你喜欢

转载自blog.csdn.net/weixin_40991408/article/details/103753070