Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过 Spring Cloud的封装,可以让我们轻松的将面向服务的REST模板请求自动转换成客户端负载均衡的服务调用。Spring Cloud Ribbon虽然是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务的调用,API网关的请求转发等内容实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,也是基于Ribbon实现的工具。
客户端负载均衡
负载均衡在系统架构中是一个非常重要并且不得不实施的内容。因为负载均衡是对系统的高可用、网络压力的缓解和处理能力扩容的重要手段之一。我们通常所说的负载均衡都值得是服务端负载均衡,其中分为硬件负载均衡和软件负载均衡。硬件负载均衡主要通过在服务器节点之间安装专门用于负载均衡的设备,比如F5等。而软件负载均衡则是通过在服务器上安装具有负载均衡功能的软件来实现请求分发工作,比如Nginx。
服务端负载均衡(包括硬件和软件)都会维护一个下挂可用的服务端清单,通过心跳监测来剔除故障的服务端节点以保障清单中都是可以正常访问的服务端节点。当客户端发送请求到负载均衡设备的时候,该设备按照某种算法(比如轮询、按权重、按流量负载等)从维护的服务端清单中取出一台服务端的地址,然后进行转发。
客户端负载均衡和服务端负载均衡最大的不同点就是服务清单所存储的位置,在客户端负载均衡中,所以客户端节点都维护自己要访问的服务端清单,而这些服务端的清单来自注册中心,客户端负载均衡也要通过心跳去维护服务清单的健康性,只是这个步骤需要服务注册中心配合完成。在Spring Cloud实现的服务治理框架中,默认会创建针对各个服务治理框架的Ribbon自动化整合配置,比如Eureka中的org.springframework.cloud.netflix.ribbon.eureka.RibbonEurekaAutoConfiguration和Consul中的org.springframework.cloud.consul.discovery.RibbonConsulAutoConfiguration。在实际使用它们的时候,我们可以通过查看这两个类的实现,已找到它们的配置详情来帮助我们使用它。
通过Spring Cloud Ribbon的封装,我们在微服务架构中使用客户端负载均衡调用非常简单:
- 服务提供者只需要启动多个服务实例并注册到一个或多个相关联的服务注册中心。
- 服务消费者直接通过调用被@LoadBalanced注解修饰过得RestTemplate来实现面向服务的接口调用。
这样我们就可以将服务提供者的高可用已经服务消费者的负载均衡调用一起实现了。
现在我们通过分析源码来了解一下Ribbon是如何通过RestTemplate实现客户端负载均衡的。
我们回顾一下系列四中开启客户端负载均衡使用到了@LoadBalanced注解,我们从这里开始入手。
package org.springframework.cloud.client.loadbalancer;
/**
* 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 {
}
看这一句Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient,就知道该注解是给RestTemplate做标记,以使用负载均衡的客户端(LoadBalancerClient)来配置它。通过搜索LoadBalancerClient发现这是Spring Cloud定义的接口。它定义了以下方法:
<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的方法
ServiceInstance choose(String serviceId);
通过这四个抽象方法我们大概了解了一个客户端负载均衡器拥有下面几个功能
- ServiceInstance choose(String serviceId):根据传入的服务名serviceId,从负载均衡器中挑选一个对应服务的实例。
- T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException:使用从负载均衡器中挑选出的实例服务来执行请求内容。
- URI reconstructURI(ServiceInstance instance, URI original):为系统构建一个合适的host:port形式的URI。在分布式系统中,我们使用逻辑上的服务名称作为host来构建URI(替代服务实例的host:post形式)进行请求,比如http://myservice/path/tpo/service。在该操作的定义中,前者ServiceInstance对象是带有host和port的具体服务实例,而后者URI对象则是使用逻辑服务名定义为host的URI,而返回的URI内容则是通过ServiceInstance的服务实例详情拼接出的具体host:port形式的请求地址。
接下来看这个类org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,它是为实现客户端负载均衡器的自动化配置类。
package org.springframework.cloud.client.loadbalancer;
/**
* 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标记的RestTemplate实例
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
//为restTemplate添加定制
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);
}
//不存在RetryTemplate时生效
@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 -> {
//为restTemplate添加拦截器
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
//以下是针对classpath存在RetryTemplate.class的情况的配置,先忽略
@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);
};
}
}
}
从LoadBalancerAutoConfiguration类上的注解可以知道,Ribbon实现的负载均衡自动化配置需要满足下面两个条件
- @ConditionalOnClass(RestTemplate.class):RestTemplate类必须存在于当前工程的环境中。
- @ConditionalOnBean(LoadBalancerClient.class):在Spring的bean工程中必须有LoadBalancerClient的实现bean。
先忽略存在RetryTemplate的情况,在该自动化配置类中,主要做了三件事:
- 创建了一个LoadBanlancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
- 创建了一个RestTemplateCustomizer 的bean,用于给RestTemplate增加LoadBanlancerInterceptor拦截器。
- 维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBanlancerInterceptor拦截器。
接下来我们看看LoadBanlancerInterceptor拦截器是如何将一个普通的RestTemplate变成客户端负载均衡的。
package org.springframework.cloud.client.loadbalancer;
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
* @author William Tran
*/
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();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
//主要的拦截处理,在这里通过LoadBalancerClient执行具体的请求发送
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
}
}
我们可以看到在拦截器中注入了LoadBalancerClient 的实现。当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起Http请求时,会被LoadBalancerInterceptor类的intercept方法拦截。由于我们在使用RestTemplate时采用了服务名作为host,所以直接从HttpRequest的URI对象中通过getHost()就可以拿到服务名,然后调用execute方法根据服务名来选择实例并发送实际的请求。接下来我们看一下LoadBalancerClient的具体实现类RibbonLoadBalancerClient。
package org.springframework.cloud.netflix.ribbon;
/**
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
* @author Tim Ysewyn
*/
public class RibbonLoadBalancerClient implements LoadBalancerClient {
private SpringClientFactory clientFactory;
public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
this.clientFactory = clientFactory;
}
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
URI uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
return context.reconstructURIWithServer(server, uri);
}
//通过LoadBalancer找到service_id对应的ServiceInstance
@Override
public ServiceInstance choose(String serviceId) {
Server server = getServer(serviceId);
if (server == null) {
return null;
}
return new RibbonServer(serviceId, server, isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
}
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
Server server = getServer(loadBalancer);
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);
}
@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;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
private boolean isSecure(Server server, String serviceId) {
IClientConfig config = this.clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
return RibbonUtils.isSecure(config, serverIntrospector, server);
}
protected Server getServer(String serviceId) {
return getServer(getLoadBalancer(serviceId));
}
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
//实际负载均衡的地方
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
protected ILoadBalancer getLoadBalancer(String serviceId) {
return this.clientFactory.getLoadBalancer(serviceId);
}
public static class RibbonServer implements ServiceInstance {
private final String serviceId;
private final Server server;
private final boolean secure;
private Map<String, String> metadata;
public RibbonServer(String serviceId, Server server) {
this(serviceId, server, false, Collections.<String, String> emptyMap());
}
public RibbonServer(String serviceId, Server server, boolean secure,
Map<String, String> metadata) {
this.serviceId = serviceId;
this.server = server;
this.secure = secure;
this.metadata = metadata;
}
@Override
public String getServiceId() {
return this.serviceId;
}
@Override
public String getHost() {
return this.server.getHost();
}
@Override
public int getPort() {
return this.server.getPort();
}
@Override
public boolean isSecure() {
return this.secure;
}
@Override
public URI getUri() {
return DefaultServiceInstance.getUri(this);
}
@Override
public Map<String, String> getMetadata() {
return this.metadata;
}
public Server getServer() {
return this.server;
}
@Override
public String getScheme() {
return this.server.getScheme();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("RibbonServer{");
sb.append("serviceId='").append(serviceId).append('\'');
sb.append(", server=").append(server);
sb.append(", secure=").append(secure);
sb.append(", metadata=").append(metadata);
sb.append('}');
return sb.toString();
}
}
}
可以看到在execute方法中第一步就是通过getServer根据传入的服务名serviceId去获的具体的服务实例。
protected Server getServer(ILoadBalancer loadBalancer) {
if (loadBalancer == null) {
return null;
}
return loadBalancer.chooseServer("default"); // TODO: better handling of key
}
这里是用了Netflix Ribbon自身的ILoadBalancer 接口定义的chooseServer方法。来看一下这个接口
package com.netflix.loadbalancer;
import java.util.List;
public interface ILoadBalancer {
public void addServers(List<Server> newServers);
public Server chooseServer(Object key);
public void markServerDown(Server server);
public List<Server> getReachableServers();
public List<Server> getAllServers();
}
可以看到,在该接口定义了一个客户端负载均衡器需要的一系列抽象操作(未列举过期方法)
- addServers:向负载均衡器中维护的实例列表增加服务实例。
- chooseServer:通过某种策略,从负载均衡器中挑选一个具体的服务实例。
- markServerDown:用来通知和标识负载均衡器中某个具体实例已经停止服务,不然负载均衡器在下一次获取服务清单前都会认为服务实例均是正常服务的。
- getReachableServers:获取当前正常服务的实例列表。
- getAllServers:获取所有已知的服务实例列表,包括正常服务和停止服务的实例。
在该接口定义的Server对象存储了服务端节点的一些元数据信息,包括host、以及一些部署信息等。
通过如下所示的结构,发现BaseLoadBalancer类实现了基础的负载均衡,DynamicServerListLoadBalancer和ZoneAwareLoadBalancer在负载均衡的策略上做了一些功能的扩展。而Spring Cloud默认采用的是ZoneAwareLoadBalancer来实现负载均衡器。
我们接着回到RibbonLoadBalancerClient的execute方法,在通过ZoneAwareLoadBalancer的chooseServer方法获取了负责均衡策略分配到的服务实例对象Server后,将其内容包装成RibbonServer对象(该对象除了存储了服务实例的信息外还增加了服务名serviceId、是否需要使用HTTPS等其他信息),然后使用该对象再回调LoadBalancerInterceptor请求拦截器中LoadBalancerRequest的apply(final ServiceInstance instance)方法,向一个实际的具体服务实例发起请求。从而实现一开始以服务名为host的URI请求到host:port形式的实际访问地址的转换。
@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;
}
private ServerIntrospector serverIntrospector(String serviceId) {
ServerIntrospector serverIntrospector = this.clientFactory.getInstance(serviceId,
ServerIntrospector.class);
if (serverIntrospector == null) {
serverIntrospector = new DefaultServerIntrospector();
}
return serverIntrospector;
}
在apply方法中传入的ServiceInstance接口对象是对服务实例的抽象定义。在该接口中暴露了服务治理系统中每个服务实例需要提供的一些基本信息,比如serviceId、host、port等。
public interface ServiceInstance {
/**
* @return the service id as registered.
*/
String getServiceId();
/**
* @return the hostname of the registered ServiceInstance
*/
String getHost();
/**
* @return the port of the registered ServiceInstance
*/
int getPort();
/**
* @return if the port of the registered ServiceInstance is https or not
*/
boolean isSecure();
/**
* @return the service uri address
*/
URI getUri();
/**
* @return the key value pair metadata associated with the service instance
*/
Map<String, String> getMetadata();
/**
* @return the scheme of the instance
*/
default String getScheme() {
return null;
}
}
而上面提到的RibbonServer就是ServiceInstance接口的实现,它除了包含Server对象外,额外存储了服务名、是否使用HTTPS标识以及一个Map元数据集合(RibbonServer的源代码可以在上面的RibbonLoadBalancerClient代码中看到)。
接下来看一下apply方法的具体实现
package org.springframework.cloud.client.loadbalancer;
public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
@Override
public ListenableFuture<ClientHttpResponse> intercept(final HttpRequest request, final byte[] body,
final AsyncClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return this.loadBalancer.execute(serviceName,
new LoadBalancerRequest<ListenableFuture<ClientHttpResponse>>() {
@Override
public ListenableFuture<ClientHttpResponse> apply(final ServiceInstance instance)
throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request,
instance, loadBalancer);
return execution.executeAsync(serviceRequest, body);
}
});
}
}
可以看到它执行的时候还传入了ServiceRequestWrapper对象,该对象继承了HttpRequestWrapper并重写了getURI方法,重写后的getURI通过调用LoadBalancerClient接口的reconstructURI方法来重新构建一个URI来进行访问。
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(
this.instance, getRequest().getURI());
return uri;
}
在LoadBalancerInterceptor拦截器中,ClientHttpRequestExecution实例具体执行execution.execute(serviceRequest, body)时会调用InterceptingClientHttpRequest下的InterceptingRequestExecution类的execute方法,具体实现如下:
@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
可以看到,在创建请求时 requestFactory.createRequest(request.getURI(), method),这里的request.getURI()会调用刚刚介绍的ServiceRequestWrapper对象中重写的getURI方法。此时他就会使用RibbonLoadBalancerClient中实现的reconstructURI来组织具体请求的服务实例地址。
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String serviceId = instance.getServiceId();
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
Server server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
ServerIntrospector serverIntrospector = serverIntrospector(serviceId);
URI uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig,
serverIntrospector, server);
return context.reconstructURIWithServer(server, uri);
}
从reconstructURI方法可以看到他通过ServiceInstance实例对象的serviceId,从SpringClientFactory类的clientFactory对象中获取对应serviceId的负载均衡器的上下文RibbonLoadBalancerContext对象,然后根据ServiceInstance中的信息来构建具体服务实例信息的Server对象,并使用RibbonLoadBalancerContext对象的reconstructURIWithServer方法来构建服务实例的URI。
SpringClientFactory:是一个用来创建客户端负载均衡器的工厂类,该工厂会为每一个不同名的Ribbon客户端生成不同的Spring上下文。
RibbonLoadBalancerContext:是LoadBalancerContext的子类,该类用于存储一些被负载均衡器使用的上下文内容和API操作(reconstructURIWithServer就是其中之一)。
从reconstructURIWithServer实现中可以看到,它同reconstructURI的定义类似。只是reconstructURI的第一个保持具体服务实例的参数使用了Spring Cloud定义的ServerInstance,而reconstructURIWithServer使用了Netflix中定义的Server,所以RibbonLoadBalancerClient实现reconstructURI的时候做了一次转换,使用ServerInstance的host和port信息构建了一个Server对象来给reconstructURIWithServer使用。从reconstructURIWithServer的实现逻辑看,它从Server对象获取host和port,根据以服务名为host的URI对象original中获取其他请求信息,将两者内容进行拼接整合,形成最终要访问的服务实例的具体地址。
public URI reconstructURIWithServer(Server server, URI original) {
String host = server.getHost();
int port = server.getPort();
String scheme = server.getScheme();
if (host.equals(original.getHost())
&& port == original.getPort()
&& scheme == original.getScheme()) {
return original;
}
if (scheme == null) {
scheme = original.getScheme();
}
if (scheme == null) {
scheme = deriveSchemeAndPortFromPartialUri(original).first();
}
try {
StringBuilder sb = new StringBuilder();
sb.append(scheme).append("://");
if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
sb.append(original.getRawUserInfo()).append("@");
}
sb.append(host);
if (port >= 0) {
sb.append(":").append(port);
}
sb.append(original.getRawPath());
if (!Strings.isNullOrEmpty(original.getRawQuery())) {
sb.append("?").append(original.getRawQuery());
}
if (!Strings.isNullOrEmpty(original.getRawFragment())) {
sb.append("#").append(original.getRawFragment());
}
URI newURI = new URI(sb.toString());
return newURI;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
到这里,我们大致了解了Spring Cloud Ribbon实现客户端负载均衡的基本流程。了解了他如何通过LoadBalancerInterceptor拦截器对RestTemplate的请求进行拦截,并利用Spring Cloud的负载均衡器LoadBalancerClient将以逻辑服务名为host的URI转换成具体的服务实例地址的过程,同时可以通过分析LoadBalancerClient的Ribbon实现RibbonLoadBalancerClient,可以知道在使用Ribbon实现负载均衡器的时候,实际上使用的还是Ribbon中定义的ILoadBalancer接口的实现,自动化配置会采用ZoneAwareLoadBalancer的实例来实现客户端负载均衡。
下一篇会详细介绍Spring Cloud RIbbon的负载均衡器和负载均衡策略。
参考书籍:《Spring Cloud微服务实战》——翟永超