在第二节《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;
}
}
它是一个接口,我们可以从实现该接口的实现类对象中获取到服务ID
,HOST
,PORT
,URI
,其他元数据以及是否启用HTTPS
等基本信息,从这些基本信息中我们完全可以根据服务名来组装真实的调用地址了,特此说明。
通过进一步阅读代码,我们在接口LoadBalancerClient
所在的包org.springframework.cloud.client.loadbalancer
中发现两个重要的类:LoadBalancerInterceptor
和LoadBalancerAutoConfiguration
,从名称可知,第一个类是一个负载均衡器拦截器,第二个是负载均衡器的自动化配置类,它们之间的关系可以从下面的类图中得知:
我们首先来查看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);
};
}
}
}
更多干货分享,欢迎关注我的微信公众号:爪哇论剑(微信号:itlemon)