提示:本文内容较长,且涉及了很多源码,请谨慎阅读。
本文为Ribbon系列第一篇,主要分析了RestTemplate在Ribbon实现负载均衡的流程。
前言
Spring Cloud Netflix Ribbon是一种客户端负载均衡的组件。在微服务系统中,不同的微服务之间往往需要通信。例如在商城系统中,用户在访问产品微服务的时候,如果下单某个产品,那么产品微服务就需要调用订单微服务,为用户生成订单。在调用某个微服务的时候,这个微服务经常会有多个实例,如何选取其中的一个实例去调用,则需要通过负载均衡算法来实现了。这就是Ribbon的作用。
提示:以下是本篇文章正文内容
一、负载均衡概述
负载均衡分为硬件负载均衡和软件负载均衡。不过我们关心的是软件负载均衡。
负载均衡有两个需要解决的基本问题:
- 从哪里选取服务实例。在Spirng Cloud微服务系统中,Eureka服务治理中心维护了一个微服务实例清单,具体微服务实例会执行服务获取,得到实例清单,缓存到本地,同时按照一个事件间隔更新这个实例清单(因为实例清单也在不断维护和变化)。
- 如何选择服务实例。这个通过具体的负载均衡策略来解决。
二、关于Ribbon
Rinbbon的使用主要和RestTemplate(微服务用来调用Rest风格请求的模板,详情请见另一篇文章关于Spring中调用Rest风格请求的模板——RestTemplate)有关,此处不赘述了,本文的重点是从源码来弄清楚Ribbon的具体原理。
我们知道,在RestTemplate的类上加上@LoadBalanced,就开启了负载均衡。所以我们从这个注解开始,对Ribbon展开探究。
打开这个注解的源码,可以看到这样一个注释:
Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
这句话的意思是加上这个注解,接口对象LoadBalancerClient就会对RestTemplate进行处理。所以我们接下来就研究LoadBalancerClient接口,打开这个接口:
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接口,有三个方法,再查看这个接口的实现类,有两个:
ServiceInstanceChooser定义了一个方法,如下:
ServiceInstance choose(String serviceId);
这个方法有一个参数serviceId,这个微服务的id,实例的配置项spring.application.name,这个方法返回的是一个微服务实例。
我们现在来看上面提到的接口LoadBalancerClient(把源码再复制一遍):
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方法,因此这个接口共有4个方法,现在具体分析一下上面三个方法:
- execute(String serviceId, LoadBalancerRequest request)
来看一下这个方法的注释:
execute request using a ServiceInstance from the LoadBalancer for the specified service
意思是用一个通过负载均衡得到的服务实例来执行请求。
- execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request)
这个方法的注释和上个方法一样,不过两个方法的参数不同,此方法多了一个ServiceInstance serviceInstance。 - URI reconstructURI(ServiceInstance instance, URI original),此方法的注释:
Create a proper URI with a real host and port for systems to utilize.Some systems use a URI with the logical serivce name as the host, such as http://myservice/path/to/service. This will replace the service name with the host:port from the ServiceInstance.
这段读起来有点拗口,通俗点说就是原来的url请求里面写的是微服务名字,这个方法在得到具体实例的信息以后,会对原来的url进行重写,将微服务名字换成具体实例的主机和端口号。
下面我们来看看上面的两个execute方法的具体实现,前面我们说过,LoadBalancerClient有两个实现类,现在看一下第2个RibbonLoadBalancerClient,我们先来看看第一个execute方法在这个类中的实现:
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException {
return execute(serviceId, request, null);
}
可以看到这个方法只是调用了另一个execute方法,我们来看一下这个被调用的execute方法:
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);
}
这个方法在接口中没有,相比上个execute多了个一个参数Object hint。
首先通过微服务seviceId获取负载均衡器loadBalancer,然后通过负载均衡器和hint获得具体的微服务实例,如果实例为空,抛出异常,然后新建一个RibbonServer对象,将实例信息包装到这个对象里,再调用最后一个execute方法来执行请求。
我们现在来看一下最后一个execute方法,如下:
@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;
}
首先获取服务实例,然后创建结果分析器,再调用request的apply方法处理请求,最后将处理结果放入结果分析器中,返回结果。
我们从来没有创建过LoadBalancerClient接口对象的实例,却能够实现负载均衡的效果,说明了Spring Boot为我们自动装配了相关的对象,打开类RibbonAutoConfiguration,就可以看到创建对象的代码,如下:
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
看到这里,请大家思考一个问题,为什么在RestTemplate上加上注解,就能实现负载均衡的效果呢,又是如何找到那三个execute方法的呢?答案是使用了拦截器LoadBanlancerInterceptor,我们来看看这个拦截器的代码,如下:
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();
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
这段代码并不复杂,核心是intercept方法,而intercept方法又调用了LoadBalancerClient对象的execute方法。看到这儿我们就明白了,只要在RestTemplate上加上@LoadBalanced,这个拦截器就会起作用,就会找到LoadBalancerClient对象的execute方法去执行。
但是,拦截器LoadBanlancerInterceptor的对象又是如何创建的呢?答案还是SpringBoot的自动装配,我们可以看一下LoadBalancerAutoConfiguration的代码,如下:
@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 loadBalancedRestTemplateInitializer(
final List<RestTemplateCustomizer> customizers) {
return new SmartInitializingSingleton() {
@Override
public void afterSingletonsInstantiated() {
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 new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
@Configuration
@ConditionalOnClass(RetryTemplate.class)
static class RetryAutoConfiguration {
@Bean
public RetryTemplate retryTemplate() {
RetryTemplate template = new RetryTemplate();
template.setThrowLastExceptionOnExhausted(true);
return template;
}
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
}
@Bean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
LoadBalancerRequestFactory requestFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,
lbRetryPolicyFactory, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return new RestTemplateCustomizer() {
@Override
public void customize(RestTemplate restTemplate) {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
}
};
}
}
}
代码虽多,但我们只需要注意创建拦截器对象的部分,并不复杂。
总结
以上是Ribbon实现负载均衡的流程:首先通过LoadvBalancerInterceptor拦截RestTemplate,然后在其intercept方法调用LoadBalancerClient接口的execute方法来执行负载均衡。
关于Ribbon负载均衡器和具体的策略在下一篇文章中分析。
本文参考杨开振的《Spring Cloud微服务和分布式系统实践》