今天是这个系列的第二篇,客户端负载均衡Ribbon,它不像注册中心需要部署,它几乎存在每个Spring Cloud构建的微服务和基础设施中,微服务之间的调用,API网关的请求转发等都是通过Ribbon来实现的。与服务端负载均衡不同的是,Ribbon是一个客户端负载均衡工具,每个客户端都维护着自己要访问的服务端清单,这些清单都来自于服务中心。下面还是先来讲解一下怎么去使用Ribbon这个客户端负载均衡工具。
它的使用真的太简单了,多亏了Spring boot约定优于配置的规则,使用Ribbon只需要两步即可
一:服务提供者启动多个服务实例并注册到一个或者多个相关联的服务注册中心(这不是废话吗,要是只有一个服务实例何谈负载均衡)
二:只需要在我们请求使用的RestTemplate对象上加@LoadBalanced注解就ok了,之后的调用就正常使用RestTemplate所提供的方法就会自动实现客户端负载均衡了。
由于这一章的使用部分太过于简单,所以我下面直接对Ribbon的源码进行分析,为什么RestTemplate这个spring自己提供的请求类再加上一个注解就可以实现客户端负载均衡
我们通过@LoadBalanced注解源码可以知道,这个注解主要是给RestTemplate做标记,然后用LoadBanancerClient来配置注解的对象,LoadBalancerClient是一个在Spring Cloud中定义的接口,里面有三个方法如下(有两个重载方法):
public interface LoadBalancerClient extends ServiceInstanceChooser {T execute(String var1, LoadBalancerRequest var2) throws IOException; T execute(String var1, ServiceInstance var2, LoadBalancerRequest var3) throws IOException; URI reconstructURI(ServiceInstance var1, URI var2); }
public interface ServiceInstanceChooser { ServiceInstance choose(String var1); }
通过字面意思我们大概能猜到这三个方法都是做什么用的,choose方法是根据传入的服务名从负载均衡器中选一个对应的服务实例,execute方法就是从选出的服务实例来执行请求内容,reconstructURI方法是从服务名构建的URI转化成host:port这种格式的URI。
经在org.springframework.cloud.client.loadbalancer包观察后发现,LoadBalancerAutoConfiguration这个类是实现客户端负载均衡器的自动化配置类,我们可以查看它的源码:
@Configuration @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false ) private ListrestTemplates = Collections.emptyList(); @Autowired( required = false ) private List transformers = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List customizers) { return new SmartInitializingSingleton() { public void afterSingletonsInstantiated() { Iterator var1 = LoadBalancerAutoConfiguration.this.restTemplates.iterator(); while(var1.hasNext()) { RestTemplate restTemplate = (RestTemplate)var1.next(); Iterator var3 = customizers.iterator(); while(var3.hasNext()) { RestTemplateCustomizer customizer = (RestTemplateCustomizer)var3.next(); customizer.customize(restTemplate); } } } }; } @Configuration @ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"}) static class LoadBalancerInterceptorConfig { 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() { public void customize(RestTemplate restTemplate) { List list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
从这个类的注解可以看出,Ribbon实现的负载均衡自动化配置需要满足连个条件,一个是RestTemplate类存在与当前的工程环境中,二是必须有LoadBalancerClient实现bean。
在自动化配置类中,主要完成了三件事:一是创建了LoadBalancerInterceptor的bean,用于对客户端发起请求时拦截,以实现客户端负载均衡;二是创建了RestTemplateCustomizer的Bean,用于给RestTemplate增加拦截器;三是维护了一个被@LoadBalanced注解修饰的RestTemplate列表,并初始化,对每一个RestTemplate实例添加拦截器。
那么,现在的重点就在于LoadBalancerInterceptor拦截器是怎么把一个普通的RestTemplate变得具有客户端负载均衡能力的?我们查看一下该类的源码如下:
public class RetryLoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancedRetryPolicyFactory lbRetryPolicyFactory; private RetryTemplate retryTemplate; private LoadBalancerClient loadBalancer; private LoadBalancerRetryProperties lbProperties; private LoadBalancerRequestFactory requestFactory; /** @deprecated */ @Deprecated public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) { this.loadBalancer = loadBalancer; this.lbRetryPolicyFactory = lbRetryPolicyFactory; this.retryTemplate = retryTemplate; this.lbProperties = lbProperties; this.requestFactory = requestFactory; } public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) { this.loadBalancer = loadBalancer; this.lbRetryPolicyFactory = lbRetryPolicyFactory; this.lbProperties = lbProperties; this.requestFactory = requestFactory; } /** @deprecated */ @Deprecated public RetryLoadBalancerInterceptor(LoadBalancerClient loadBalancer, RetryTemplate retryTemplate, LoadBalancerRetryProperties lbProperties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory) { this(loadBalancer, retryTemplate, lbProperties, lbRetryPolicyFactory, new LoadBalancerRequestFactory(loadBalancer)); } public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); final String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); final LoadBalancedRetryPolicy retryPolicy = this.lbRetryPolicyFactory.create(serviceName, this.loadBalancer); RetryTemplate template = this.retryTemplate == null ? new RetryTemplate() : this.retryTemplate; template.setThrowLastExceptionOnExhausted(true); template.setRetryPolicy((RetryPolicy)(this.lbProperties.isEnabled() && retryPolicy != null ? new InterceptorRetryPolicy(request, retryPolicy, this.loadBalancer, serviceName) : new NeverRetryPolicy())); return (ClientHttpResponse)template.execute(new RetryCallback, IOException>() { public ClientHttpResponse doWithRetry(RetryContext context) throws IOException { ServiceInstance serviceInstance = null; if (context instanceof LoadBalancedRetryContext) { LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext)context; serviceInstance = lbContext.getServiceInstance(); } if (serviceInstance == null) { serviceInstance = RetryLoadBalancerInterceptor.this.loadBalancer.choose(serviceName); } ClientHttpResponse response = (ClientHttpResponse)RetryLoadBalancerInterceptor.this.loadBalancer.execute(serviceName, serviceInstance, RetryLoadBalancerInterceptor.this.requestFactory.createRequest(request, body, execution)); int statusCode = response.getRawStatusCode(); if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) { response.close(); throw new RetryableStatusCodeException(serviceName, statusCode); } else { return response; } } }); } }
这里我们主要看到在拦截器中被注入了一个LoadBalancerClient的实现,我们主要看拦截器的intercept方法,我们观察到,其实里面就是一个loadBalancer.execute方法的调用,上面我们提到了,这个方法的作用就是根据服务名去选择实例并发送实际的请求,那么下面的问题就又转移了,LoadBalancerClient这个接口的实现到底是什么呢,它是如何完成的客户端负载均衡?我们继续搜索源码,终于在org.springframework.cloud.netflix.ribbon这个包中找到了关键的实现类:RibbonLoadBalancerClient,如下所示:(这个类比较庞大,所以截取它中关键的execute方法的实现)
publicT execute(String serviceId, LoadBalancerRequest request) throws IOException { ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server)); return this.execute(serviceId, ribbonServer, request); } }
publicT execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException { Server server = null; if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) { server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer(); } if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } else { 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 var8) { statsRecorder.recordStats(var8); throw var8; } catch (Exception var9) { statsRecorder.recordStats(var9); ReflectionUtils.rethrowRuntimeException(var9); return null; } } }
这两段代码结合起来就是真正的execute方法的实现,可以看到最主要的代码是这两句
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId); Server server = this.getServer(loadBalancer);
我们再看一下getServer方法:
protected Server getServer(ILoadBalancer loadBalancer) { return loadBalancer == null ? null : loadBalancer.chooseServer("default"); }
可以看到,主要是调用了ILoadBalancer这个接口的chooseServer方法,而没有使用到LoadBalancerClient中的choose方法,这样问题又一次发生了转移,我们现在来看一下这个ILoadBalancer接口:
public interface ILoadBalancer { void addServers(Listvar1); Server chooseServer(Object var1); void markServerDown(Server var1); /** @deprecated */ @Deprecated List getServerList(boolean var1); List getReachableServers(); List getAllServers(); }
这个接口很明显就是顶一个一个客户端负载均衡器所需要的一系列抽象方法,列举几个:addServers方法就是想负载均衡器中维护的实力列表增加服务实例,choose方法就是通过某种策略(后面会讲)从实例列表中挑出一个具体的服务实例。
这个接口中有一个Server类,这个类就是一个传统的服务端节点,存储了一些服务端节点的元信息,比如host,port等。接下来的问题就是这个接口的具体实现类都是哪些了,我们通过查阅源码得知,在Spring Cloud的默认实现中,是使用了ZoneAwareLoadBalancer这个类来实现负载均衡。
现在我们先回到刚才的execute方法中,我们得到了服务实例对象之后,会把它包装成ribbonServer对象,然后再回调apply函数,向一个具体的服务实例发起请求,从而实现一开始以服务名为host的URI请求到host:port这种形式的转换,这个转换的过程就是ReconstructURI方法的具体实现,在这里不在赘述。
本篇内容到此结束,下一篇将专门讲解各种负载均衡器的实现以及各种负载均衡策略,最后还会讲述一下ribbon的配置
注:本篇内容参考《Spring Cloud微服务实战》 翟永超著