Spring Cloud Loadbalancer (一) 如何对 RestTemplate 进行客户端负载均衡

由于 Ribbon 已经进入维护的状态,因此 Spring Cloud 自己研发了 Spring Cloud LoadBalancer 用于替代 Ribbon,相比较于Ribbon,Spring Cloud LoadBalancer 不仅能够支持 RestTemplate,还支持 WebClient。WeClient是Spring Web Flux中提供的功能,可以实现响应式异步请求。因为平常开发,我们主要用到 RestTemplate(AsyncRestTemplate 底层也是RestTemplate)进行开发,因此本文主要分析如何实现 RestTemplate 的负载均衡。本文基于Spring Cloud Commons 3.1.3 进行分析。

如果我们去下载看Spring Cloud Commons源码查看项目结构,会发现涉及 LoadBalancer 的模块有两个,分别是 spring-cloud-commons 模块和 spring-cloud-loadbalancer 模块。可以认为 spring-cloud-commons 是对 AsyncRestTemplate,RestTemplate 和 WebClient 客户端的负载均衡器进行了抽象,但并未实现,最终是由spring-cloud-loadbalancer 模块,或者spring cloud netflix ribbon项目进行实现的。

查看 spring-cloud-commons 模块下的 spring.factories,我们发现自动配置类为 LoadBalancerAutoConfiguration。这个类并不复杂,为了方便阅读,我对源码顺序进行了少许调整。

@Configuration(proxyBeanMethods = false)
// 1. 这个配置时基于  RestTemplate 类存在和 RestTemplate 存在才会启用的。
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
public class LoadBalancerAutoConfiguration {

    // 2. 只有标注了 @LoadBalanced 的 RestTemplate,才会被注入到当前的List中。这个是 Spring 的特性。
	@LoadBalanced
	@Autowired(required = false)
	private List<RestTemplate> restTemplates = Collections.emptyList();

    // 3. 注入 LoadBalancerRequestTransformer 这个用于对 RestTemplate 做一些配置
	@Autowired(required = false)
	private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    
    // 4. 对 RestTemplate 的请求包装成为 LoadBalancerRequest,以便被 LoadBalancerClient 调用。
	@Bean
	@ConditionalOnMissingBean
	public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
		return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
	}
    
    // 5. 核心配置,这里生成  RestTemplate 拦截器的具体实现类。
	@Configuration(proxyBeanMethods = false)
	@Conditional(RetryMissingOrDisabledCondition.class)
	static class LoadBalancerInterceptorConfig {
        // 6. RestTemplate 拦截器
		@Bean
		public LoadBalancerInterceptor loadBalancerInterceptor(LoadBalancerClient loadBalancerClient,
				LoadBalancerRequestFactory requestFactory) {
			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
		}
        // 7. RestTemplate 的个性化类。
		@Bean
		@ConditionalOnMissingBean
		public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
			return restTemplate -> {
				List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());
				list.add(loadBalancerInterceptor);
				restTemplate.setInterceptors(list);
			};
		}

	}

    // 8. SmartInitializingSingleton 的实现类,所有的单例初始化完成后触发,也就是对需要负载均衡的 RestTemplate 实例进行个性化配置。看7步,这里实际上是将拦截器加入到RestTemplate实例中。
    @Bean
    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);
                }
            }
        });
    }

	// 省略了重试机制的代码

}

按顺序将以上代码看下来,最终我们可以聚焦在 LoadBalancerInterceptor 和 LoadBalancerClient 两个类上。 其中 LoadBalancerInterceptor 在 spring-cloud-commons 模块已经提供实现:

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();
        // 这里可以看到,restTemplate 的 host 为服务名,通过这个服务名来获取真正的 URL
		Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        // 最终委托给 LoadBalancerClient 的实现类去发起请求。这里的目的很简单,就是修改 HttpRequest 里面的 URI 属性即可。
		return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
	}
}

通过以上分析,RestTemplate 的拦截器最终将请求委托给了 LoadBalancerClient ,具体的实现,由其他模块去拓展。 在看 LoadBalancerClient 的定义前。 我们回忆一下,在 Spring Cloud Common 依赖包中定义了服务和服务发现的两个抽象类。

public interface ServiceInstance {
    default String getInstanceId() {return null;}
    String getServiceId();
    String getHost();
    int getPort();
    boolean isSecure();
    URI getUri();
    Map<String, String> getMetadata();
    default String getScheme() {return null;}
}
public interface DiscoveryClient extends Ordered {
	int DEFAULT_ORDER = 0;
	String description();
	List<ServiceInstance> getInstances(String serviceId);
	List<String> getServices();
	default void probe() {getServices();}
	@Override
	default int getOrder() {return DEFAULT_ORDER;}
}

为了将服务发现逻辑和负载均衡请求相结合,定义了一下三个接口

public interface LoadBalancerRequest<T> {
    T apply(ServiceInstance instance) throws Exception;
}

public interface ServiceInstanceChooser {
	ServiceInstance choose(String serviceId);// 根据服务名获取具体实例
	<T> ServiceInstance choose(String serviceId, Request<T> request);
}
//根据服务名获取实例,并执行请求
public interface LoadBalancerClient extends ServiceInstanceChooser {
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
    URI reconstructURI(ServiceInstance instance, URI original);
}

其实逻辑很简单,

  1. 根据提供的 serviceId 选择出合适的 ServiceInstance,这个由 ServiceInstanceChooser 的 choose 方法实现。
  2. 将选择出的 ServiceInstance 提交给 LoadBalancerRequest 去执行。所以实际执行请求的类为 LoadBalancerRequest。
  3. 根据 ServiceInstance 修改原始的URI,这个方法将会在 LoadBalancerRequest 被使用 。为什么需要这个方法呢,因为 URI 是不可以修改的,如果需要修改host,就必须要重新构建一个新的实例。

LoadBalancerRequest 已经在 spring-cloud-commons 中提供了实现,由 LoadBalancerRequestFactory 获取,具体实现类为 BlockingLoadBalancerRequest 。

class BlockingLoadBalancerRequest implements HttpRequestLoadBalancerRequest<ClientHttpResponse> {

	private final LoadBalancerClient loadBalancer;

	private final List<LoadBalancerRequestTransformer> transformers;

	private final ClientHttpRequestData clientHttpRequestData;

	BlockingLoadBalancerRequest(LoadBalancerClient loadBalancer, List<LoadBalancerRequestTransformer> transformers,
			ClientHttpRequestData clientHttpRequestData) {
		this.loadBalancer = loadBalancer;
		this.transformers = transformers;
		this.clientHttpRequestData = clientHttpRequestData;
	}

    
	@Override
	public ClientHttpResponse apply(ServiceInstance instance) throws Exception {
        // 因为 HttpRequest 是不可以修改的类,因此需要  ServiceRequestWrapper 进行包装,主要是使用 ServiceInstance 修改了 HttpRequest 的 URI 属性。
		HttpRequest serviceRequest = new ServiceRequestWrapper(clientHttpRequestData.request, instance, loadBalancer);
		if (this.transformers != null) {
			for (LoadBalancerRequestTransformer transformer : this.transformers) {
				serviceRequest = transformer.transformRequest(serviceRequest, instance);
			}
		}
        // 最终还是提交给了拦截器上的三个参数执行逻辑
		return clientHttpRequestData.execution.execute(serviceRequest, clientHttpRequestData.body);
	}

	@Override
	public HttpRequest getHttpRequest() {
		return clientHttpRequestData.request;
	}
    // 用户包装 RestTemplate 拦截器上的 3 个参数
	static class ClientHttpRequestData {

		private final HttpRequest request;

		private final byte[] body;

		private final ClientHttpRequestExecution execution;

		ClientHttpRequestData(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
			this.request = request;
			this.body = body;
			this.execution = execution;
		}

	}

}

最后再看下 ServiceRequestWrapper 的实现

public class ServiceRequestWrapper extends HttpRequestWrapper {

	private final ServiceInstance instance;

	private final LoadBalancerClient loadBalancer;

	public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) {
		super(request);
		this.instance = instance;
		this.loadBalancer = loadBalancer;
	}
    // 这里实际委托给了 LoadBalancerClient 去获取新的 URI。
	@Override
	public URI getURI() {
		URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
		return uri;
	}
}

至此,spring-cloud-commons 模块负载均衡的代码已经梳理完毕。RestTemplate 的负载均衡是通过 ClientHttpRequestInterceptor 去实现的,本质上委托给了 LoadBalancerClient 去重写了 HttpRequest 的 getURI 方法,从而达到将 serviceId 替换成实际 host 的目的。至于 LoadBalancerClient 的具体实现,spring-cloud-commons 模块没有提供,而是由另一个模块spring-cloud-loadbalancer 来实现。这个由下一个章节来介绍。

参考

spring-tips-spring-cloud-loadbalancer
Spring框架-ObjectProvider更加宽泛的依赖注入
Spring核心接口ObjectProvider

你可能感兴趣的:(spring,cloud,loadbalancer,Spring,Cloud,spring,cloud,负载均衡,spring)