spring cloud ribbon 是一个基于 HTTP 和 TCP 的客户端负载均衡工具,它基于Netflix Ribbon 实现。经过Spring Cloud 的封装,能够轻松的将面向服务的REST模块请求自动转换为客户端负载均衡的服务调用。Spring Cloud Ribbon 虽然只是一个工具类框架,不像服务注册中心、配置中心、API网关那样须要独立部署,但它几乎存在于每个Spring Cloud 构建的微服务和基础设施中。由于微服务间的调用,API网关的请求转发等内容,实际上都是经过 Ribbon 来实现的,包括后续要介绍的 Feign,它也是基于Ribbon实现的工具。java
负载均衡在系统架构中是一个很是重要,而且不得不去实施的内容。由于负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。一般说的负载均衡都指的是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要是经过在服务器节点之间安装专门用于负载均衡的设备,好比F5等;而软件负载均衡则是经过在服务器上安装一些具备负载均衡功能或模块的软件来完成请求分发工做,好比Nginx等。不论是硬件仍是软件负载均衡,只要是服务器端负载均衡都能以相似下图的架构方式构建起来:web
硬件负载均衡的设备或是软件负载均衡的软件模块都会维护一个下挂可用的服务端清单,经过心跳监测来剔除故障的服务端节点以保证清单中都是能够正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按某种算法(好比线性轮询、按权重负载、按流量负载等)从维护的可用服务端清单中取出一台服务端的地址,而后进行转发。算法
而客户端的负载均衡和服务端的负载均衡最大的不一样点在于上面所提到的服务清单所存储的位置。在客户端负载均衡中,全部客户端节点都维护着本身要访问的服务端清单,而这些服务端的清单来自于服务注册中心,好比上一章中介绍的Eureka服务端。同服务端负载均衡的架构相似,在客户端负载均衡中也须要心跳去维护服务端清单的健康性,只是这个步骤须要与服务注册中心配合完成。在Spring Cloud 实现的服务治理框架中,默认会建立针对各个服务治理框架的 Ribbon 自动化整合配置,好比 Eureka 中的 org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration,Consul 中的 org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在实际使用的时候,能够经过查看这两个类的实现,以找到它们的配置详情来帮助咱们更好的使用它。spring
经过 Spring Cloud Ribbon 的封装,咱们在微服务架构中使用客户端负载均衡调用很是简单,只须要以下两步:数组
上一章中,咱们经过引入Ribbon实现了服务消费者的客户端负载均衡功能。其中,咱们使用了一个很是有用的对象 RestTemplate 。该对象会使用 Ribbon 的自动化配置,同时经过配置 @LoadBalanced 还可以开启客户端负载均衡。以前演示了经过 RestTemplate 实现了最简单的服务访问,下面详细介绍 RestTemplate 针对几种不一样请求类型和参数类型的服务调用实现。服务器
在 RestTemplate 中,对GET请求能够经过以下两个方法进行调用。网络
第一种:getForEntity 函数。该方法返回的是 ResponseEntity,该对象是Spring 对 HTTP 请求响应的封装,其中主要存储了 HTTP 的几个重要元素,好比 HTTP 请求状态码的枚举对象 HttpStatus(404,500这些错误码)、在它的父类 HttpEntity 中还存储着 HTTP 请求的头信息对象 HttpHeaders 以及泛型类型的请求体对象。好比下面的例子,就是访问HELLO-SERVER服务的/index请求,同时最后一个参数didi 会替换 url 中的 {1} 占位符,而返回的 ResponseEntity 对象中的 body 内容类型会根据第二个参数转换为 String 类型。架构
RestTemplate restTemplate = new RestTemplate (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/index?name={1}",String.class,"didi"); String body = responseEntity.getBody();
若但愿返回一个自定义类型,好比返回User类型,能够把第二个参数换成User.class,以下:并发
RestTemplate restTemplate = new RestTemplate (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/index?name={1}",User.class,"didi"); User body = responseEntity.getBody();
上面的例子是比较经常使用的方法,getForEntity 函数实际上提供了一下三种不一样的重载实现。app
第二种:getForObject 函数。该方法能够理解为对 getForEntity 的进一步封装,它经过 HttpMessageConverterExtractor 对 HTTP 的请求响应体 body 内容进行对象转换,实现请求直接返回包装好的对象内容。好比:
RestTemplate restTemplate = new RestTemplate (); String body = restTemplate .getForObject(url, String.class);
当body 是一个User对象时,能够直接这样实现:
RestTemplate restTemplate = new RestTemplate (); User body = restTemplate .getForObject(url, User.class);
该方法也提供了三种不一样的重载实现(参数和上面的方法相似):
在RestTemplate中,对POST请求时能够经过以下三个方法进行调用实现。
第一种:postForEntity 函数。使用方法和 getForEntity 函数相似
postForEntity 函数也实现了三种不一样的重载方法。
这几个函数的参数用法大部分都与 getForEntity 函数一致。
第二种:postForObject 函数。该函数也和 getForObject 函数相似,它的做用是简化 postForEntity 函数的后续处理。
postForObject 函数也实现了三种不一样的重载方法(使用方法和上述相似):
第三种:postForLocation 函数
postForLocation 也实现了三种不一样的重载方法(使用方法和上述相似):
这两个请求的函数都直接以 put 、delete 做为方法名,使用方法和上述相似。
从上述消费者示例中能够发现, @LoadBalanced 注解实现了客户端的负载均衡。经过搜索LoadBalancerClient 能够发现,这是 Spring Cloud 中定义的一个接口:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.io.IOException; import java.net.URI; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest; import org.springframework.cloud.client.loadbalancer.ServiceInstanceChooser; public interface LoadBalancerClient extends ServiceInstanceChooser { <T> T execute(String var1, LoadBalancerRequest<T> var2) throws IOException; <T> T execute(String var1, ServiceInstance var2, LoadBalancerRequest<T> var3) throws IOException; URI reconstructURI(ServiceInstance var1, URI var2); }
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import org.springframework.cloud.client.ServiceInstance; public interface ServiceInstanceChooser { ServiceInstance choose(String var1); }
从该接口中,咱们能够经过定义的抽象方法来了解客户端负载均衡器中应具有的几种能力:
顺着 LoadBalancerClient 接口的所属包 org.springframework.cloud.client.loadbalancer,咱们对其内容进行整理,能够得出以下图所示的关系:
从类名可初步判断 loadBalancerAutoConfiguration 为实现客户端负载均衡器的自动化配置类。经过查看源码,能够获得验证:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; 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.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestTransformer; import org.springframework.cloud.client.loadbalancer.LoadBalancerRetryProperties; import org.springframework.cloud.client.loadbalancer.RestTemplateCustomizer; import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryPolicyFactory.NeverRetryFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.RestTemplate; @Configuration @ConditionalOnClass({RestTemplate.class}) @ConditionalOnBean({LoadBalancerClient.class}) @EnableConfigurationProperties({LoadBalancerRetryProperties.class}) public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired( required = false ) private List<RestTemplate> restTemplates = Collections.emptyList(); @Autowired( required = false ) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); public LoadBalancerAutoConfiguration() { } @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer(final List<RestTemplateCustomizer> 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); } } } }; } @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } @Configuration @ConditionalOnClass({RetryTemplate.class}) public static class RetryInterceptorAutoConfiguration { public RetryInterceptorAutoConfiguration() { } @Bean @ConditionalOnMissingBean public RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties, LoadBalancedRetryPolicyFactory lbRetryPolicyFactory, LoadBalancerRequestFactory requestFactory) { return new RetryLoadBalancerInterceptor(loadBalancerClient, properties, lbRetryPolicyFactory, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final RetryLoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { public void customize(RestTemplate restTemplate) { ArrayList list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } @Configuration @ConditionalOnClass({RetryTemplate.class}) public static class RetryAutoConfiguration { public RetryAutoConfiguration() { } @Bean public RetryTemplate retryTemplate() { RetryTemplate template = new RetryTemplate(); template.setThrowLastExceptionOnExhausted(true); return template; } @Bean @ConditionalOnMissingBean public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() { return new NeverRetryFactory(); } } @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) { ArrayList list = new ArrayList(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
从该类的类注解可知,Ribbon 实现的负载均衡自动化配置须要知足下面两个条件。
在该类中,主要作了下面三件事:
接着看下 LoadBalancerInterceptor 拦截器是如何讲一个普通的 RestTemplate 变成客户端负载均衡的:
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.cloud.client.loadbalancer; import java.io.IOException; import java.net.URI; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestFactory; import org.springframework.http.HttpRequest; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.util.Assert; 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) { this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); } public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { URI originalUri = request.getURI(); String serviceName = originalUri.getHost(); Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri); return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } }
经过源码以及以前的自动化配置类,咱们能够看到在拦截器中注入了 LoadBalancerClient 的实现。当一个被 @LoadBalanced 注解修饰的 RestTemplate 对象向外发起 HTTP 请求时,会被 LoadBalancerInterceptor 类的 intercept 函数所拦截。因为咱们在使用 RestTemplate 时采用了服务名做为host,因此直接从 HttpRequest 的 URI 对象中经过 getHost 函数就能够拿到服务名,而后调用 execute 函数去根据服务名来选择实例并发起实际的请求。
到此,LoadBalancerClient 还只是一个抽象的负载均衡器接口,因此咱们还须要找到它的具体实现类来进一步分析。经过查看 Ribbon 的源码,能够在 org.springframework.cloud.netflix.ribbon 包下找到对应的实现类 RibbonLoadBalancerClient 。
public <T> T execute(String serviceId, LoadBalancerRequest<T> 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); } } public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> 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 { Object ex = request.apply(serviceInstance); statsRecorder.recordStats(ex); return ex; } catch (IOException var8) { statsRecorder.recordStats(var8); throw var8; } catch (Exception var9) { statsRecorder.recordStats(var9); ReflectionUtils.rethrowRuntimeException(var9); return null; } } }
能够看到,在execute 函数的实现中,第一步作的就是经过 getServer 根据传入的服务名 serviceId 去得到具体的服务实例:
protected Server getServer(ILoadBalancer loadBalancer) { return loadBalancer == null?null:loadBalancer.chooseServer("default"); }
经过该函数的实现,能够看到这里获取具体服务实例的时候并无使用 LoadBalancerClient 接口中的 choose 函数,而是使用了 Netflix Ribbon 自身的 ILoadBalancer 接口中定义的 chooseServer 函数。
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.netflix.loadbalancer; import com.netflix.loadbalancer.Server; import java.util.List; public interface ILoadBalancer { void addServers(List<Server> var1); Server chooseServer(Object var1); void markServerDown(Server var1); /** @deprecated */ @Deprecated List<Server> getServerList(boolean var1); List<Server> getReachableServers(); List<Server> getAllServers(); }
能够看到,在该接口中定义了一个客户端负载均衡器须要的一系列抽象操做。
在该接口定义中涉及的Server 对象定义是一个传统的服务端节点,在该类中存储了服务端节点的一些元数据信息,包括 host、port 以及一些部署信息等。
对于该接口的实现,能够整理出下图所示的结构。能够看到 BaseLoadBalancer 类实现了基础的 负载均衡,而 DynamicServerListLoadBalancer 和 ZoneAwareLoadBalancer 在负载均衡的策略上作了一些功能的扩展。
那么在整合Ribbon的时候 Spring Cloud 默认采用了哪一个具体实现呢,能够经过 RibbonClientConfiguration 配置类看出在整合时默认采用了 ZoneAwareLoadBalancer 来实现负载均衡器。
@Bean @ConditionalOnMissingBean public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server> serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, ServerListUpdater serverListUpdater) { return (ILoadBalancer)(this.propertiesFactory.isSet(ILoadBalancer.class, this.name)?(ILoadBalancer)this.propertiesFactory.get(ILoadBalancer.class, config, this.name):new ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, serverListUpdater)); }
下面回到 RibbonLoadBalancerClient 的 execute 函数逻辑,在经过 ZoneAwareLoadBalancer 的 chooseServer 函数获取负载均衡策略分配到的服务实例对象 Server 后,将其内容包装成 RibbonServer 对象,而后使用该对象再回调 LoadBalancerInterceptor 请求拦截器中 LoadBalancerRequest 的 apply(final ServiceInstance instance)函数,向一个实际的具体服务实例发起请求,从而实现一开始以服务名为host 的 URI 请求到host:port形式的实际访问地址的转换。
…………