Spring Cloud微服务技术栈(五):客户端负载均衡Spring Cloud Ribbon部分源码分析

在第二节《Spring Cloud微服务技术栈(二):搭建高可用Eureka Server、服务注册与发现》,我们搭建服务消费者的时候,使用到了客户端负载均衡,那时候只是在创建RestTemplate对象的代码上加上了@LoadBalanced注解,这么简单的一个步骤就实现了客户端负载均衡的功能,那么它是如何实现的呢?本篇文章将从基础源码出发,来探讨一下客户端负载均衡的原理。

源码分析

为了使客户端具备负载均衡的能力,我们在代码中将RestTemplate交给Spring管理的时候,会加上@LoadBalanced注解,如下代码所示:

@Bean  @LoadBalanced  public RestTemplate restTemplate() {      return new RestTemplate();  }

加上这个注解以后,那么RestTemplate实例对象就具备了负载均衡的能力,为什么加上这个注解以后,RestTemplate实例对象就具备了负载均衡的能力了呢?我们进入到该注解的源码中一探究竟。

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 {  }

从该注解的代码中看来,它和普通的注解并没有太大的区别,我们从它的注释中可以了解到:“该注解用来给RestTemplate做标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它”。从注释中,我们捕获一个重要的关键词,那就是负载均衡的客户端——LoadBalancerClient,我们在源码中搜索LoadBalancerClient,发现它是Spring Cloud中定义的一个接口:

package org.springframework.cloud.client.loadbalancer;    import org.springframework.cloud.client.ServiceInstance;    import java.io.IOException;  import java.net.URI;    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);  }

它继承了ServiceInstanceChooser接口,该接口中有一个抽象方法choose,代码如下:

package org.springframework.cloud.client.loadbalancer;    import org.springframework.cloud.client.ServiceInstance;    public interface ServiceInstanceChooser {        ServiceInstance choose(String serviceId);  }

从两个接口中我们可以初步判定负载均衡器应该具备四个基本方法,对于四个基本方法,我们来分别了解一下:

  • choose方法:它是根据传入的服务ID,从负载均衡器中找出对应的服务实例。
  • execute方法(第一个):使用从负载均衡器中根据服务ID选择出的服务实例来执行请求内容。
  • execute方法(第二个):使用指定的服务实例来执行请求内容。
  • reconstructURI方法:根据传入的服务实例和原始的请求URI来构建一个合适的host:port形式的URI。在分布式服务调用中,我们使用的是服务名代替host:port形式来发起服务调用请求的,如:String result = restTemplate.getForEntity("http://producer-service/hello/producer", String.class).getBody();,这里使用的是服务提供方的服务名producer-service来进行调用的,那么这个方法就是在运行时将服务名称形式的调用替换为host:port形式。

我们一直在强调服务实例,难道服务实例对象存储在服务调用方这边?显然不是,我们服务调用方从Eureka Server拉取的是可用的服务实例清单,而清单中的服务实例其实就是存储了服务提供方相关元数据的对象,一起来看一下ServiceInstance源码:

package org.springframework.cloud.client;    import java.net.URI;  import java.util.Map;    public interface ServiceInstance {    	String getServiceId();    	String getHost();    	int getPort();    	boolean isSecure();    	URI getUri();    	Map<String, String> getMetadata();    	default String getScheme() {  		return null;  	}  }

它是一个接口,我们可以从实现该接口的实现类对象中获取到服务IDHOSTPORTURI,其他元数据以及是否启用HTTPS等基本信息,从这些基本信息中我们完全可以根据服务名来组装真实的调用地址了,特此说明。

通过进一步阅读代码,我们在接口LoadBalancerClient所在的包org.springframework.cloud.client.loadbalancer中发现两个重要的类:LoadBalancerInterceptorLoadBalancerAutoConfiguration,从名称可知,第一个类是一个负载均衡器拦截器,第二个是负载均衡器的自动化配置类,它们之间的关系可以从下面的类图中得知:

我们首先来查看LoadBalancerAutoConfiguration类,看看有没有哪些代码能帮助我们理解负载均衡实现原理。

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  	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  		public LoadBalancerInterceptor ribbonInterceptor(  				LoadBalancerClient loadBalancerClient,  				LoadBalancerRequestFactory requestFactory) {  			return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);  		}    		@Bean  		@ConditionalOnMissingBean  		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);              };  		}  	}  }