03.07 Spring cloud Ribbon 客戶端負載均衡詳解(一)

前言

Spring Cloud Ribbon 是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現。通過Spring Cloud的封裝,將面向服務的REST模板請求自動轉換成客戶端負載均衡的服務調用。Spring Cloud Ribbon 雖然只是一個工具類框架,它不像註冊中心、配置中心、API網關那樣需要獨立部署,但是幾乎存在於每個Spring Cloud構建的微服務和基礎設施中。微服務之間的調用、API網關請求轉發實際上都是通過Ribbon來實現的,Feign也是。因此理解Ribbon對於使用Spring Cloud至關重要。


Spring cloud Ribbon 客戶端負載均衡詳解(一)

從上圖中可以看到LoadBalanced註解在spring cloud common包中,因此也很好理解Ribbon貫穿整個Spring cloud體系中。

客戶端負載均衡

負載均衡對系統的高可用、網絡壓力的緩解和處理能力擴容的重要手段。

負載均分為硬件負載均衡(F5)、軟件負載均衡(Nginx、Apache)。

負載均衡算法有:線性輪詢、權重輪詢、流量負載等。

通過Spring Cloud Ribbon的封裝我們載微服務架構中使用客戶端負載均衡調用只要兩步:

1、服務提供者只需要啟動多個服務實例並註冊到一個註冊中心或是多個相關聯的服務註冊中心

2、服務消費者直接通過調用被@LoadBalance 註解修飾過的RestTemplate來實現服務的接口調用。


Spring cloud Ribbon 客戶端負載均衡詳解(一)

負載均衡

RestTemplate詳解

調用代碼示例

1、配置@LoadBalanced @Bean@LoadBalancedRestTemplate restTemplate(){ return new RestTemplate(); }

<code>package com.anzy.cloud.cloud01;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration;
import org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.client.RestTemplate;

/**
* ServerEurekaApplication
*
* @author anzy
* @date 2018-05-01
*/
@SpringBootApplication(exclude = {
ElasticsearchAutoConfiguration.class,
RabbitAutoConfiguration.class,
RedisAutoConfiguration.class,
MongoAutoConfiguration.class,
MongoDataAutoConfiguration.class})
@EnableTransactionManagement // 事務
//@EnableEurekaClient // @EnableEurekaClient只適用於Eureka作為註冊中心,@EnableDiscoveryClient 可以是其他註冊中心。
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.anzy.cloud"})
@ComponentScan(basePackages = {"com.anzy.cloud"})
public class DemoCloud01Application {
public static void main(String[] args) {
SpringApplication.run(DemoCloud01Application.class, args);
}


@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}/<code>

2、注入RestTemplate 調用getForEntity()方法

<code>package com.anzy.cloud.cloud01.consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
* @author anzy
* @version 1.0
* @date 2020/1/18 20:51
**/
@RestController
@RequestMapping(value = "/consumer")
public class ConsumerController {

@Autowired
RestTemplate restTemplate;

@GetMapping("/hello")
public String hello(){
// 調用SERVICE-ADMIN 服務下的 /admin/test/mobile/v1/test 接口
return restTemplate.getForEntity("http://SERVICE-ADMIN/admin/test/mobile/v1/test",String.class).getBody().toString();
}

}
/<code>

RestTemplate請求方式主要分為:post、get、put、delete。如下圖:


Spring cloud Ribbon 客戶端負載均衡詳解(一)

get請求


Spring cloud Ribbon 客戶端負載均衡詳解(一)

post請求


Spring cloud Ribbon 客戶端負載均衡詳解(一)

PUT請求方法


Spring cloud Ribbon 客戶端負載均衡詳解(一)

delete方法

源碼分析:

首先看一下LoadBalanced註解源碼

<code>/**
* Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
* 註解用於標記RestTemplate 來配置使用LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}
/<code>

從LoadBalanced註解 中的註釋可以看到 來配置使用LoadBalancerClient,然後搜索LoadBalancerClient,查看其源碼:

<code>//
// 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;

public interface LoadBalancerClient extends ServiceInstanceChooser {
// 使用從負載均衡中挑選的實例來執行請求任務
T execute(String serviceId, LoadBalancerRequest request) throws IOException;
// 使用從負載均衡中挑選的實例來執行請求任務
T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException;
\t\t// 為系統構建一個合適的host:port 形式的URI
URI reconstructURI(ServiceInstance instance, URI original);
}
/<code>

LoadBalancerClient父類

<code>public interface ServiceInstanceChooser {

/**
* Choose a ServiceInstance from the LoadBalancer for the specified service
* 從負載均衡中選擇一個服務實例
* @param serviceId the service id to look up the LoadBalancer
* @return a ServiceInstance that matches the serviceId
*/
ServiceInstance choose(String serviceId);
}
/<code>
Spring cloud Ribbon 客戶端負載均衡詳解(一)

整理了Ribbon類之間的關係如圖:

Spring cloud Ribbon 客戶端負載均衡詳解(一)

Ribbon UML圖

首先來看一下LoadBalancerAutoConfiguration.java類,從名字上可以看到是一個配置類

<code>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).
* Ribbon自動配置
* @author Spencer Gibb
* @author Dave Syer
* @author Will Tran
* @author Gang Li
*/
@Configuration
// RestTemplate必須存在於當前工程的環境
@ConditionalOnClass(RestTemplate.class)
// Spring 的Bean 工程必須有LoadBalanceClient的實現Bean
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

\t@LoadBalanced
\t@Autowired(required = false)
\tprivate List<resttemplate> restTemplates = Collections.emptyList();

\t@Bean
\tpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
\t\t\tfinal ObjectProvider<list>> restTemplateCustomizers) {
\t\treturn () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
\t}

\t@Autowired(required = false)
\tprivate List<loadbalancerrequesttransformer> transformers = Collections.emptyList();

\t@Bean
\t@ConditionalOnMissingBean
\tpublic LoadBalancerRequestFactory loadBalancerRequestFactory(
\t\t\tLoadBalancerClient loadBalancerClient) {
\t\treturn new LoadBalancerRequestFactory(loadBalancerClient, transformers);
\t}

\t@Configuration
\t@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
\tstatic class LoadBalancerInterceptorConfig {
// 創建一個LoadBalancerIntercept的Bean,用於實現對客戶端發起請求時進行攔截以實現客戶端負載均衡。
\t\t@Bean
\t\tpublic LoadBalancerInterceptor ribbonInterceptor(
\t\t\t\tLoadBalancerClient loadBalancerClient,
\t\t\t\tLoadBalancerRequestFactory requestFactory) {
\t\t\treturn new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
\t\t}
\t\t//2、創建一個RestTemplateCustomizer的Bean,用於給RestTemplate增加LoadBalancerIntercept攔截器。
\t\t@Bean
\t\t@ConditionalOnMissingBean
\t\tpublic RestTemplateCustomizer restTemplateCustomizer(
\t\t\t\tfinal LoadBalancerInterceptor loadBalancerInterceptor) {
\t\t\treturn restTemplate -> {
List<clienthttprequestinterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
\t\t}
\t}


\t@Configuration
\t@ConditionalOnClass(RetryTemplate.class)
\tpublic static class RetryAutoConfiguration {

\t\t@Bean
\t\t@ConditionalOnMissingBean
\t\tpublic LoadBalancedRetryFactory loadBalancedRetryFactory() {
\t\t\treturn new LoadBalancedRetryFactory() {};
\t\t}
\t}

\t@Configuration
\t@ConditionalOnClass(RetryTemplate.class)
\tpublic static class RetryInterceptorAutoConfiguration {
\t\t@Bean
\t\t@ConditionalOnMissingBean
\t\tpublic RetryLoadBalancerInterceptor ribbonInterceptor(
\t\t\t\tLoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
\t\t\t\tLoadBalancerRequestFactory requestFactory,
\t\t\t\tLoadBalancedRetryFactory loadBalancedRetryFactory) {
\t\t\treturn new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
\t\t\t\t\trequestFactory, loadBalancedRetryFactory);
\t\t}

\t\t@Bean
\t\t@ConditionalOnMissingBean
\t\tpublic RestTemplateCustomizer restTemplateCustomizer(
\t\t\t\tfinal RetryLoadBalancerInterceptor loadBalancerInterceptor) {
\t\t\treturn restTemplate -> {
List<clienthttprequestinterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
\t\t}
\t}
}
/<clienthttprequestinterceptor>/<clienthttprequestinterceptor>/<loadbalancerrequesttransformer>/<list>/<resttemplate>/<code>

在LoadBalancerAutoConfiguration類頭上的註解可以看出,Ribbon的實現需要滿足兩個條件:

1、 @ConditionalOnClass(RestTemplate.class) // RestTemplate必須存在於當前工程的環境

2、 @ConditionalOnBean(LoadBalancerClient.class) // Spring 的Bean 工程必須有LoadBalanceClient的實現Bean

在自動化類LoadBalancerAutoConfiguration中主要做了三件事:

1、創建一個LoadBalancerIntercept的Bean,用於實現對客戶端發起請求時進行攔截以實現客戶端負載均衡。

2、創建一個RestTemplateCustomizer的Bean,用於給RestTemplate增加LoadBalancerIntercept攔截器。

3、維護一個被LoadBalance註解修飾的RestTemplate對象列表,並在這裡進行初始化,通過調用RestTemplateCustomizer的實例來個需要客戶端負載均衡的RestTemplate增加LoadBalancerIntercept攔截器。


從上面可以看出LoadBalancerIntercept比較重要,接下來看一下:

<code>package org.springframework.cloud.client.loadbalancer;

import java.io.IOException;
import java.net.URI;

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;

/**
* 當一個被LoadBalanced註解修飾的RestTemplate對象向外發送HTTP請求攔截
* @author Spencer Gibb
* @author Dave Syer
* @author Ryan Baxter
* @author William Tran
*/
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

\tprivate LoadBalancerClient loadBalancer;
\tprivate LoadBalancerRequestFactory requestFactory;


\tpublic LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
\t\tthis.loadBalancer = loadBalancer;
\t\tthis.requestFactory = requestFactory;
\t}

\tpublic LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
\t\t// for backwards compatibility
\t\tthis(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
\t}

// 攔截核心方法
\t@Override
\tpublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
\t\t\tfinal ClientHttpRequestExecution execution) throws IOException {
// 獲取URI
\t\tfinal URI originalUri = request.getURI();
// 從URI獲取HOST
\t\tString serviceName = originalUri.getHost();
\t\tAssert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 根據服務名選擇實例發起實際請求
\t\treturn this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
\t}
}
/<code>
<code>public class AsyncLoadBalancerInterceptor implements AsyncClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;

public AsyncLoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}

public ListenableFuture<clienthttpresponse> intercept(final HttpRequest request, final byte[] body, final AsyncClientHttpRequestExecution execution) throws IOException {
URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
return (ListenableFuture)this.loadBalancer.execute(serviceName, new LoadBalancerRequest<listenablefuture>>() {
public ListenableFuture<clienthttpresponse> apply(final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
return execution.executeAsync(serviceRequest, body);
}
});
}
}/<clienthttpresponse>/<listenablefuture>/<clienthttpresponse>/<code>

接下來看一下execute()方法具體如何執行的:

<code>@Override
\tpublic T execute(String serviceId, LoadBalancerRequest request) throws IOException {
//根據serviceId 拿到LoadBalancer然後再哪去Server
\t\tILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 跟蹤源碼發現用的 loadBalancer.chooseServer("default"); 獲取server,下面對ILoadBalancer 接口做詳解
\t\tServer server = getServer(loadBalancer);
\t\tif (server == null) {
\t\t\tthrow new IllegalStateException("No instances available for " + serviceId);
\t\t}
// 獲取到server後包裝成RibbonServer, RibbonServer包含server對象以及serverId\t
\t\tRibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
\t\t\t\tserviceId), serverIntrospector(serviceId).getMetadata(server));

\t\treturn execute(serviceId, ribbonServer, request);
\t}

\t@Override
\tpublic T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request) throws IOException {
\t\tServer server = null;
\t\tif(serviceInstance instanceof RibbonServer) {
\t\t\tserver = ((RibbonServer)serviceInstance).getServer();
\t\t}
\t\tif (server == null) {
\t\t\tthrow new IllegalStateException("No instances available for " + serviceId);
\t\t}

\t\tRibbonLoadBalancerContext context = this.clientFactory
\t\t\t\t.getLoadBalancerContext(serviceId);
\t\tRibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

\t\ttry {
// 回調到AsyncLoadBalancerInterceptor請求中的apply()方法
\t\t\tT returnVal = request.apply(serviceInstance);
\t\t\tstatsRecorder.recordStats(returnVal);
\t\t\treturn returnVal;
\t\t}
\t\t// catch IOException and rethrow so RestTemplate behaves correctly
\t\tcatch (IOException ex) {
\t\t\tstatsRecorder.recordStats(ex);
\t\t\tthrow ex;

\t\t}
\t\tcatch (Exception ex) {
\t\t\tstatsRecorder.recordStats(ex);
\t\t\tReflectionUtils.rethrowRuntimeException(ex);
\t\t}
\t\treturn null;
\t}
/<code>

上面發現getServer是通過ILoadBalancer 接口中定義的chooseServer()方法獲取的。

Spring cloud Ribbon 客戶端負載均衡詳解(一)

ILoadBalancer 接口

1、addServers:向負載均衡服務器中維護的實例列表增加服務實例。

2、chooseServer:通過某種策略,從負載均衡服務器中挑選一個具體的服務實例。

3、markServerDown:用來通知和標識負載均衡服務器中某個具體實例已經停止服務,不然負載均衡服務器在下一次獲取服務實例清單前會認為服務實例均是正常的。

4、getReachableServers 獲取單籤正常服務實例列表。

5、getAllServers:獲取所有已知的服務實例列表,包括正常服務和停止服務的實例。

注:接口中的Server對象存儲了服務端點元數據,如:host、port。

整理ILoadBalancer 接口之間的關係如圖:

Spring cloud Ribbon 客戶端負載均衡詳解(一)

ILoadBalancer

查看RibbonClientConfiguration配置類可以看到默認採用ZoneAwareLoadBalancer來實現負載均衡

<code>    @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));
}
/<server>/<server>/<code>


之前提到過RibbonServer對象,下面看一下RibbonServer對象,他實際上實現了ServiceInstance


Spring cloud Ribbon 客戶端負載均衡詳解(一)

ServiceInstance接口

<code>public static class RibbonServer implements ServiceInstance {
\t\tprivate final String serviceId;
\t\tprivate final Server server;
\t\tprivate final boolean secure;
\t\tprivate Map<string> metadata;

\t\tpublic RibbonServer(String serviceId, Server server) {
\t\t\tthis(serviceId, server, false, Collections.<string> emptyMap());
\t\t}

\t\tpublic RibbonServer(String serviceId, Server server, boolean secure,
\t\t\t\tMap<string> metadata) {
\t\t\tthis.serviceId = serviceId;
\t\t\tthis.server = server;
\t\t\tthis.secure = secure;
\t\t\tthis.metadata = metadata;
\t\t}
// 省略get,toString方法
\t\t...
\t}/<string>/<string>/<string>/<code>


從Ribbon對象代碼中可以看到,它除了包含Server對象之外,還存儲了服務名,是否使用HTTPS標識以及一個Map類型的元數據集合。

再看一下apply接收到了ServiceInstance具體實例下面如何做

<code> public ListenableFuture<clienthttpresponse> apply(final ServiceInstance instance) throws Exception {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, AsyncLoadBalancerInterceptor.this.loadBalancer);
return execution.executeAsync(serviceRequest, body);
}/<clienthttpresponse>/<code>

從apply的實現上,可以看到它具體執行時候,還傳入了ServiceRequestWrapper並重寫了getURI(),重寫後的getURI()方法來重新構建一個URI來進行訪問。

<code>public class ServiceRequestWrapper extends HttpRequestWrapper { 

\tprivate final ServiceInstance instance;
\tprivate final LoadBalancerClient loadBalancer;

\tpublic ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,
\t\t\t\t\t\t\t\t LoadBalancerClient loadBalancer) {
\t\tsuper(request);
\t\tthis.instance = instance;
\t\tthis.loadBalancer = loadBalancer;
\t}

\t@Override
\tpublic URI getURI() {
\t\tURI uri = this.loadBalancer.reconstructURI(
\t\t\t\tthis.instance, getRequest().getURI());
\t\treturn uri;
\t}
}
/<code>

跟蹤executeAsync,可以看到裡面調用到getURI()fangfa

<code>Override
\t\tpublic ListenableFuture<clienthttpresponse> executeAsync(HttpRequest request, byte[] body)
\t\t\t\tthrows IOException {

\t\t\tif (this.iterator.hasNext()) {
\t\t\t\tAsyncClientHttpRequestInterceptor interceptor = this.iterator.next();
\t\t\t\treturn interceptor.intercept(request, body, this);
\t\t\t}
\t\t\telse {
\t\t\t\tURI uri = request.getURI();
\t\t\t\tHttpMethod method = request.getMethod();
\t\t\t\tHttpHeaders headers = request.getHeaders();

\t\t\t\tAssert.state(method != null, "No standard HTTP method");
\t\t\t\tAsyncClientHttpRequest delegate = requestFactory.createAsyncRequest(uri, method);
\t\t\t\tdelegate.getHeaders().putAll(headers);
\t\t\t\tif (body.length > 0) {
\t\t\t\t\tStreamUtils.copy(body, delegate.getBody());
\t\t\t\t}

\t\t\t\treturn delegate.executeAsync();
\t\t\t}
\t\t}
\t}
/<clienthttpresponse>/<code>


再看一下getURI()方法下的reconstructURI()方法:

<code>\t@Override
\tpublic URI reconstructURI(ServiceInstance instance, URI original) {
\t\tAssert.notNull(instance, "instance can not be null");
\t\tString serviceId = instance.getServiceId();
// 構建上下文
\t\tRibbonLoadBalancerContext context = this.clientFactory
\t\t\t\t.getLoadBalancerContext(serviceId);

\t\tURI uri;
\t\tServer server;
\t\tif (instance instanceof RibbonServer) {
\t\t\tRibbonServer ribbonServer = (RibbonServer) instance;
\t\t\tserver = ribbonServer.getServer();
\t\t\turi = updateToSecureConnectionIfNeeded(original, ribbonServer);
\t\t} else {
\t\t\tserver = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
\t\t\tIClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
\t\t\tServerIntrospector serverIntrospector = serverIntrospector(serviceId);
\t\t\turi = updateToSecureConnectionIfNeeded(original, clientConfig,
\t\t\t\t\tserverIntrospector, server);
\t\t}
\t\treturn context.reconstructURIWithServer(server, uri);
\t}/<code>

在這裡可以看到SpringClientFactory和RibbonLoadBalancerContext,下面簡單介紹一下,幫助大家理解:

1、SpringClientFactory類是一個用來創建客戶端負載均衡器的工廠類,該工廠類會為每一個不同名的Ribbon客戶端端生成Spring上下文。

2、RibbonLoadBalancerContext類是LoadBalancerContext的子類,該類用於存儲一些被負載均衡器使用的上下文內容和API操作(reconstructURIWithServer 就是其中之一)。

<code> 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);
}
}/<code>

從reconstructURIWithServer的實現上,我們可以看到它同reconstructURI類似,只是reconstructURI第一個保存具體服務實例的參數使用了Spring Cloud定義的ServiceInstance,而reconstructURIWithServer中使用了Netflix中定義的Server,所以在RibbonLoadBalancerClient實現reconstructURI的時候,做了一次轉換,使用ServiceInstance的host和port信息構建了一個對象給reconstructURIWithServer使用。從reconstructURIWithServer實現邏輯可以看到,它從Server對象中獲取host和port信息然後根據以服務名為host的URI對象original中獲取其他請求信息,將兩者內容進行拼接整合最終要訪問的服務實例的具體地址。


總結:通過LoadBalancerInterceptor攔截器對RestTemplate的請求進行攔截,利用Spring Cloud的負載均衡器LoadBalancerClient將以邏輯服務名為host的URI轉換成具體的服務實例地址過程。同時通過分析LoadBalancerClient的Ribbon實現RibbonLoadBalancerClient ,可以知道在使用Ribbon實現負載均衡器的時候,實際使用的還是Ribbon中定義的ILoadBalancer接口實現的,自動化配置會採用ZoneAwareLoadBalancer的實現客戶端負載均衡。


資料參考:《Spring Cloud 微服務實戰》


分享到:


相關文章: