為何一個@LoadBalanced注解就能讓RestTemplate擁有負載均衡的能力?


每篇一句

你應該思考:為什么往往完成比完美更重要?

前言

Spring Cloud微服務應用體系中,遠程調用都應負載均衡。我們在使用RestTemplate作為遠程調用客戶端的時候,開啟負載均衡極其簡單:一個@LoadBalanced注解就搞定了
相信大家大都使用過RibbonClient端的負載均衡,也許你有和我一樣的感受:Ribbon雖強大但不是特別的好用。我研究了一番,其實根源還是我們對它內部的原理不夠了解,導致對一些現象無法給出合理解釋,同時也影響了我們對它的定制和擴展。本文就針對此做出梳理,希望大家通過本文也能夠對Ribbon有一個較為清晰的理解(本文只解釋它@LoadBalanced這一小塊內容)。

開啟客戶端負載均衡只需要一個注解即可,形如這樣:

@LoadBalanced // 標注此注解后,RestTemplate就具有了客戶端負載均衡能力 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }

Spring是Java界最優秀、最傑出的重復發明輪子作品一點都不為過。本文就代領你一探究竟,為何開啟RestTemplate的負載均衡如此簡單。

說明:本文建立在你已經熟練使用RestTemplate,並且了解RestTemplate它相關組件的原理的基礎上分析。若對這部分還比較模糊,強行推薦你參看我前面這篇文章:RestTemplate的使用和原理你都爛熟於胸了嗎?【享學Spring MVC】

RibbonAutoConfiguration

這是Spring Boot/Cloud啟動Ribbon的入口自動配置類,需要先有個大概的了解:

@Configuration // 類路徑存在com.netflix.client.IClient、RestTemplate等時生效 @Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class) // // 允許在單個類中使用多個@RibbonClient @RibbonClients // 若有Eureka,那就在Eureka配置好后再配置它~~~(如果是別的注冊中心呢,ribbon還能玩嗎?) @AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration") @AutoConfigureBefore({ LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class }) // 加載配置:ribbon.eager-load --> true的話,那么項目啟動的時候就會把Client初始化好,避免第一次懲罰 @EnableConfigurationProperties({ RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class }) public class RibbonAutoConfiguration { @Autowired private RibbonEagerLoadProperties ribbonEagerLoadProperties; // Ribbon的配置文件們~~~~~~~(復雜且重要) @Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>(); // 特征,FeaturesEndpoint這個端點(`/actuator/features`)會使用它org.springframework.cloud.client.actuator.HasFeatures @Bean public HasFeatures ribbonFeature() { return HasFeatures.namedFeature("Ribbon", Ribbon.class); } // 它是最為重要的,是一個org.springframework.cloud.context.named.NamedContextFactory 此工廠用於創建命名的Spring容器 // 這里傳入配置文件,每個不同命名空間就會創建一個新的容器(和Feign特別像) 設置當前容器為父容器 @Bean public SpringClientFactory springClientFactory() { SpringClientFactory factory = new SpringClientFactory(); factory.setConfigurations(this.configurations); return factory; } // 這個Bean是關鍵,若你沒定義,就用系統默認提供的Client了~~~ // 內部使用和持有了SpringClientFactory。。。 @Bean @ConditionalOnMissingBean(LoadBalancerClient.class) public LoadBalancerClient loadBalancerClient() { return new RibbonLoadBalancerClient(springClientFactory()); } ... }

這個配置類最重要的是完成了Ribbon相關組件的自動配置,有了LoadBalancerClient才能做負載均衡(這里使用的是它的唯一實現類RibbonLoadBalancerClient


@LoadBalanced

注解本身及其簡單(一個屬性都木有):

// 所在包是org.springframework.cloud.client.loadbalancer // 能標注在字段、方法參數、方法上 // JavaDoc上說得很清楚:它只能標注在RestTemplate上才有效 @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @Qualifier public @interface LoadBalanced { }

它最大的特點:頭上標注有@Qualifier注解,這是它生效的最重要因素之一,本文后半啦我花了大篇幅介紹它的生效時機。
關於@LoadBalanced自動生效的配置,我們需要來到這個自動配置類:LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration

// Auto-configuration for Ribbon (client-side load balancing). // 它的負載均衡技術依賴於的是Ribbon組件~ // 它所在的包是:org.springframework.cloud.client.loadbalancer @Configuration @ConditionalOnClass(RestTemplate.class) //可見它只對RestTemplate生效 @ConditionalOnBean(LoadBalancerClient.class) // Spring容器內必須存在這個接口的Bean才會生效(參見:RibbonAutoConfiguration) @EnableConfigurationProperties(LoadBalancerRetryProperties.class) // retry的配置文件 public class LoadBalancerAutoConfiguration { // 拿到容器內所有的標注有@LoadBalanced注解的Bean們 // 注意:必須標注有@LoadBalanced注解的才行 @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); // LoadBalancerRequestTransformer接口:允許使用者把request + ServiceInstance --> 改造一下 // Spring內部默認是沒有提供任何實現類的(匿名的都木有) @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); // 配置一個匿名的SmartInitializingSingleton 此接口我們應該是熟悉的 // 它的afterSingletonsInstantiated()方法會在所有的單例Bean初始化完成之后,再調用一個一個的處理BeanName~ // 本處:使用配置好的所有的RestTemplateCustomizer定制器們,對所有的`RestTemplate`定制處理 // RestTemplateCustomizer下面有個lambda的實現。若調用者有需要可以書寫然后扔進容器里既生效 // 這種定制器:若你項目中有多個RestTempalte,需要統一處理的話。寫一個定制器是個不錯的選擇 // (比如統一要放置一個請求攔截器:輸出日志之類的) @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } // 這個工廠用於createRequest()創建出一個LoadBalancerRequest // 這個請求里面是包含LoadBalancerClient以及HttpRequest request的 @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } // =========到目前為止還和負載均衡沒啥關系========== // =========接下來的配置才和負載均衡有關(當然上面是基礎項)========== // 若有Retry的包,就是另外一份配置,和這差不多~~ @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig {、 // 這個Bean的名稱叫`loadBalancerClient`,我個人覺得叫`loadBalancerInterceptor`更合適吧(雖然ribbon是唯一實現) // 這里直接使用的是requestFactory和Client構建一個攔截器對象 // LoadBalancerInterceptor可是`ClientHttpRequestInterceptor`,它會介入到http.client里面去 // LoadBalancerInterceptor也是實現負載均衡的入口,下面詳解 // Tips:這里可沒有@ConditionalOnMissingBean哦~~~~ @Bean public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } // 向容器內放入一個RestTemplateCustomizer 定制器 // 這個定制器的作用上面已經說了:在RestTemplate初始化完成后,應用此定制化器在**所有的實例上** // 這個匿名實現的邏輯超級簡單:向所有的RestTemplate都塞入一個loadBalancerInterceptor 讓其具備有負載均衡的能力 // Tips:此處有注解@ConditionalOnMissingBean。也就是說如果調用者自己定義過RestTemplateCustomizer類型的Bean,此處是不會執行的 // 請務必注意這點:容易讓你的負載均衡不生效哦~~~~ @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } ... }

這段配置代碼稍微有點長,我把流程總結為如下幾步:

  1. LoadBalancerAutoConfiguration要想生效類路徑必須有RestTemplate,以及Spring容器內必須有LoadBalancerClient的實現Bean
    1. LoadBalancerClient的唯一實現類是:org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient
  2. LoadBalancerInterceptor是個ClientHttpRequestInterceptor客戶端請求攔截器。它的作用是在客戶端發起請求之前攔截,進而實現客戶端的負載均衡
  3. restTemplateCustomizer()返回的匿名定制器RestTemplateCustomizer它用來給所有的RestTemplate加上負載均衡攔截器(需要注意它的@ConditionalOnMissingBean注解~)

不難發現,負載均衡實現的核心就是一個攔截器,就是這個攔截器讓一個普通的RestTemplate逆襲成為了一個具有負載均衡功能的請求器

LoadBalancerInterceptor

該類唯一被使用的地方就是LoadBalancerAutoConfiguration里配置上去~

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { // 這個命名都不叫Client了,而叫loadBalancer~~~ private LoadBalancerClient loadBalancer; // 用於構建出一個Request private LoadBalancerRequestFactory requestFactory; ... // 省略構造函數(給這兩個屬性賦值) @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); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } }

此攔截器攔截請求后把它的serviceName委托給了LoadBalancerClient去執行,根據ServiceName可能對應N多個實際的Server,因此就可以從眾多的Server中運用均衡算法,挑選出一個最為合適的Server做最終的請求(它持有真正的請求執行器ClientHttpRequestExecution)。


LoadBalancerClient

請求被攔截后,最終都是委托給了LoadBalancerClient處理。

// 由使用負載平衡器選擇要向其發送請求的服務器的類實現 public interface ServiceInstanceChooser { // 從負載平衡器中為指定的服務選擇Service服務實例。 // 也就是根據調用者傳入的serviceId,負載均衡的選擇出一個具體的實例出來 ServiceInstance choose(String serviceId); } // 它自己定義了三個方法 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; // 重新構造url:把url中原來寫的服務名 換掉 換成實際的 URI reconstructURI(ServiceInstance instance, URI original); }

它只有一個實現類RibbonLoadBalancerClientServiceInstanceChooser是有多個實現類的~)。

RibbonLoadBalancerClient

首先我們應當關注它的choose()方法:

public class RibbonLoadBalancerClient implements LoadBalancerClient { @Override public ServiceInstance choose(String serviceId) { return choose(serviceId, null); } // hint:你可以理解成分組。若指定了,只會在這個偏好的分組里面去均衡選擇 // 得到一個Server后,使用RibbonServer把server適配起來~~~ // 這樣一個實例就選好了~~~真正請求會落在這個實例上~ public ServiceInstance choose(String serviceId, Object hint) { Server server = getServer(getLoadBalancer(serviceId), hint); if (server == null) { return null; } return new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); } // 根據ServiceId去找到一個屬於它的負載均衡器 protected ILoadBalancer getLoadBalancer(String serviceId) { return this.clientFactory.getLoadBalancer(serviceId); } }

choose方法:傳入serviceId,然后通過SpringClientFactory獲取負載均衡器com.netflix.loadbalancer.ILoadBalancer,最終委托給它的chooseServer()方法選取到一個com.netflix.loadbalancer.Server實例,也就是說真正完成Server選取的是ILoadBalancer

ILoadBalancer以及它相關的類是一個較為龐大的體系,本文不做更多的展開,而是只聚焦在我們的流程上

LoadBalancerInterceptor執行的時候是直接委托執行的loadBalancer.execute()這個方法:

RibbonLoadBalancerClient:

    // hint此處傳值為null:一視同仁 // 說明:LoadBalancerRequest是通過LoadBalancerRequestFactory.createRequest(request, body, execution)創建出來的 // 它實現LoadBalancerRequest接口是用的一個匿名內部類,泛型類型是ClientHttpResponse // 因為最終執行的顯然還是執行器:ClientHttpRequestExecution.execute() @Override public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { return execute(serviceId, request, null); } // public方法(非接口方法) public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException { // 同上:拿到負載均衡器,然后拿到一個serverInstance實例 ILoadBalancer loadBalancer = getLoadBalancer(serviceId); Server server = getServer(loadBalancer, hint); if (server == null) { // 若沒找到就直接拋出異常。這里使用的是IllegalStateException這個異常 throw new IllegalStateException("No instances available for " + serviceId); } // 把Server適配為RibbonServer isSecure:客戶端是否安全 // serverIntrospector內省 參考配置文件:ServerIntrospectorProperties RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); //調用本類的重載接口方法~~~~~ return execute(serviceId, ribbonServer, request); } // 接口方法:它的參數是ServiceInstance --> 已經確定了唯一的Server實例~~~ @Override public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { // 拿到Server)(說白了,RibbonServer是execute時的唯一實現) Server server = null; if (serviceInstance instanceof RibbonServer) { server = ((RibbonServer) serviceInstance).getServer(); } if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } // 說明:執行的上下文是和serviceId綁定的 RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId); ... // 真正的向server發送請求,得到返回值 // 因為有攔截器,所以這里肯定說執行的是InterceptingRequestExecution.execute()方法 // so會調用ServiceRequestWrapper.getURI(),從而就會調用reconstructURI()方法 T returnVal = request.apply(serviceInstance); return returnVal; ... // 異常處理 }

returnVal是一個ClientHttpResponse,最后交給handleResponse()方法來處理異常情況(若存在的話),若無異常就交給提取器提值:responseExtractor.extractData(response),這樣整個請求就算全部完成了。

使用細節

針對@LoadBalanced下的RestTemplate的使用,我總結如下細節供以參考:

  1. 傳入的String類型的url必須是絕對路徑(http://...),否則拋出異常:java.lang.IllegalArgumentException: URI is not absolute
  2. serviceId不區分大小寫(http://user/...效果同http://USER/...
  3. serviceId后請不要跟port端口號了~~~

最后,需要特別指出的是:標注有@LoadBalancedRestTemplate只能書寫serviceId而不能再寫IP地址/域名去發送請求了。若你的項目中兩種case都有需要,請定義多個RestTemplate分別應對不同的使用場景~

本地測試

了解了它的執行流程后,若需要本地測試(不依賴於注冊中心),可以這么來做:

// 因為自動配置頭上有@ConditionalOnMissingBean注解,所以自定義一個覆蓋它的行為即可 // 此處復寫它的getServer()方法,返回一個固定的(訪問百度首頁)即可,方便測試 @Bean public LoadBalancerClient loadBalancerClient(SpringClientFactory factory) { return new RibbonLoadBalancerClient(factory) { @Override protected Server getServer(ILoadBalancer loadBalancer, Object hint) { return new Server("www.baidu.com", 80); } }; }

這么一來,下面這個訪問結果就是百度首頁的html內容嘍。

@Test public void contextLoads() { String obj = restTemplate.getForObject("http://my-serviceId", String.class); System.out.println(obj); }

此處my-serviceId肯定是不存在的,但得益於我上面自定義配置的LoadBalancerClient

什么,寫死return一個Server實例不優雅?確實,總不能每次上線前還把這部分代碼給注釋掉吧,若有多個實例呢?還得自己寫負載均衡算法嗎?很顯然Spring Cloud早早就為我們考慮到了這一點:脫離Eureka使用配置listOfServers進行客戶端負載均衡調度(<clientName>.<nameSpace>.listOfServers=<comma delimited hostname:port strings>

對於上例我只需要在主配置文件里這么配置一下:

# ribbon.eureka.enabled=false # 若沒用euraka,此配置可省略。否則不可以 my-serviceId.ribbon.listOfServers=www.baidu.com # 若有多個實例請用逗號分隔

效果完全同上。

Tips:這種配置法不需要是完整的絕對路徑,http://是可以省略的(new Server()方式亦可)

自己添加一個記錄請求日志的攔截器可行嗎?

顯然是可行的,我給出示例如下:

@LoadBalanced @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); List<ClientHttpRequestInterceptor> list = new ArrayList<>(); list.add((request, body, execution) -> { System.out.println("當前請求的URL是:" + request.getURI().toString()); return execution.execute(request, body); }); restTemplate.setInterceptors(list); return restTemplate; }

這樣每次客戶端的請求都會打印這句話:當前請求的URI是:http://my-serviceId,一般情況(缺省情況)自定義的攔截器都會在負載均衡攔截器前面執行(因為它要執行最終的請求)。若你有必要定義多個攔截器且要控制順序,可通過Ordered系列接口來實現~


最后的最后,我拋出一個非常非常重要的問題:

    @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();

@Autowired + @LoadBalanced能把你配置的RestTemplate自動注入進來拿來定制呢???核心原理是什么?

> 提示:本原理內容屬於Spring Framwork核心技術,建議深入思考而不囫圇吞棗。有疑問的可以給我留言,我也將會在下篇文章給出詳細解答(建議先思考)

推薦閱讀

RestTemplate的使用和原理你都爛熟於胸了嗎?【享學Spring MVC】
@Qualifier高級應用---按類別批量依賴注入【享學Spring】

 

------------------------------------------------------

 

Ribbon是如何通過一個@LoadBalanced注解就實現負載均衡的

一.介紹下測試用到的服務
在這里插入圖片描述
從Eureka注冊中心中可以可以看出有EUREKA-CLIENT和RIBBON-CLIENT的服務,其中EUREKA-CLIENT有兩個節點作為服務提供者,而RIBBON-CLIENT則是服務消費者,通過RestTemplate來消費EUREKA-CLIENT的服務。

下面代碼就是簡單實現Ribbon負載均衡的配置類:

@Configuration public class RibbonConfig { @Bean @LoadBalanced RestTemplate getRestTemlate() { return new RestTemplate(); } } 

這樣簡單的通過一個@LoadBalanced注解在RestTemplate上 ,在RestTemplate 遠程調用的時候,就會出現負載均衡的效果。

二.一步一步理清Ribbon負載均衡的邏輯

  1. 首先全局搜索@LoadBalanced這個注解,發現在LoadBalancerAutoConfiguration類有用到該注解:
@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(); } 

分析以上代碼:

  • 通過@Configuration表明這是一個配置類
  • 通過@ConditionalOnClass(RestTemplate.class)可以知道RestTemplate類要在類路徑上存在才會實例化LoadBalancerAutoConfiguration
  • 通過@ConditionalOnBean(LoadBalancerClient.class)可以知道LoadBalancerClient類要存在才會實例化LoadBalancerAutoConfiguration
  • @EnableConfigurationProperties(LoadBalancerRetryProperties.class)是用來使用@ConfigurationProperties注解的類LoadBalancerRetryProperties生效,貼上部分LoadBalancerRetryProperties類的代碼,會更清晰:
@ConfigurationProperties("spring.cloud.loadbalancer.retry") public class LoadBalancerRetryProperties { private boolean enabled = true; /** * Returns true if the load balancer should retry failed requests. * @return True if the load balancer should retry failed requests; false otherwise. */ public boolean isEnabled() { return this.enabled; } 
  1. 所以重啟下RIBBON-CLIENT服務,Debug繼續看LoadBalancerAutoConfiguration 類的代碼,發現在啟動時會先進入LoadBalancerAutoConfiguration 的loadBalancerRequestFactory方法,實例化出LoadBalancerRequestFactory
    @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers); } 

接下去斷點進入LoadBalancerAutoConfiguration 類中的靜態內部類LoadBalancerInterceptorConfig的ribbonInterceptor方法,可以看出這是為了實例化出LoadBalancerInterceptor 攔截器

    @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } 

繼續跟斷點,進入了loadBalancedRestTemplateInitializerDeprecated方法,可以看出這個方法里主要的邏輯代碼是customizer.customize(restTemplate)

    @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated( final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) { return () -> restTemplateCustomizers.ifAvailable(customizers -> { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } }); } 

繼續Debug,斷點進入LoadBalancerAutoConfiguration類中的靜態內部類LoadBalancerInterceptorConfig:

@Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return restTemplate -> { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); }; } } 

通過 list.add(loadBalancerInterceptor)和restTemplate.setInterceptors(list)兩段代碼可以看出,這是要給restTemplate加上loadBalancerInterceptor攔截器。

那么接下來看看loadBalancerInterceptor攔截器里做了什么,通過頁面發起一個http請求,斷點進入到LoadBalancerInterceptor類的intercept方法,

@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); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } 

截圖看下信息:
在這里插入圖片描述
可以看到該方法取得了request里的url和servicName,然后將這些參數交給loadBalancer.execute去執行方法。而loadBalancer是LoadBalancerClient類的實例。
看下LoadBalancerClient的類圖,可以看到LoadBalancerClient繼承了ServiceInstanceChooser,LoadBalancerClient的實現類是RibbonLoadBalancerClient
在這里插入圖片描述
邏輯繼續,斷點進入了RibbonLoadBalancerClient的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); } 

跟着斷點一步一步看方法:

  • ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
    在這里插入圖片描述
    經過這個方法,得到loadBalancer,從截圖里可以看到,loadBalancer里有個allServerList集合,里面有兩個對象,端口號分別是8763和8762,這就是我們提供的服務節點。

  • Server server = getServer(loadBalancer, hint)
    在這里插入圖片描述
    從圖里可以看出,通過這個getServer方法,會返回給我們一個當前可調用的服務節點,而至於怎么返回服務節點,會再寫一篇分析,寫完后會更新鏈接到該篇。

  • 生成RibbonServer 作為參數傳入execute方法

  • 運行execute方法

接着跟進execute方法
在這里插入圖片描述
可以看該方法里的關鍵執行方法是:
T returnVal = request.apply(serviceInstance);
接着看apply方法,發現它是LoadBalancerRequest接口的方法,該接口卻沒有具體的實現類:

public interface LoadBalancerRequest<T> { T apply(ServiceInstance instance) throws Exception; } 

思路回溯,是request對象調用的apply方法,而request其實是execute方法傳進來的參數,追溯到源頭,發現是LoadBalancerInterceptor類的intercept方法里this.requestFactory.createRequest(request, body, execution)生成了LoadBalancerRequest,然后作為參數傳入,之后再調用了apply方法

@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); return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } 

跟進createRequest方法里:

在這里插入圖片描述
可以從圖中看到,經過一些操作后,生成的serviceRequest對象里的serviceId是eureka-client,也就是我們的服務節點名,而server是localhost:8763,這是具體的服務節點ip,之后作為參數調用org.springframework.http.client包下的InterceptingClientHttpRequest類中的execute方法

斷點進入該方法:
在這里插入圖片描述
可以看出通過requestFactory.createRequest(request.getURI(), method)方法生成了ClientHttpRequest類的實例delegate,它的url就是我們最后真正要請求的,最后正常調用delegate.execute()方法取得返回ClientHttpResponse就好了。

而這里產生了一個疑問,url是怎么產生的?重新發起請求斷點試下
發現關鍵在LoadBalancerRequestFactory類中的createRequest方法中的這句:

HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,his.loadBalancer); 

跟進ServiceRequestWrapper類中,發現它繼承了HttpRequestWrapper 類,同時重寫了getURI方法

public class ServiceRequestWrapper extends HttpRequestWrapper { private final ServiceInstance instance; private final LoadBalancerClient loadBalancer; public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance, LoadBalancerClient loadBalancer) { super(request); this.instance = instance; this.loadBalancer = loadBalancer; } @Override public URI getURI() { URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI()); return uri; } } 

斷點打在getURI方法里:
在這里插入圖片描述
可以看到該方法返回了我們最后需要的url。

最后,關於Ribbon是如何通過一個@LoadBalanced注解就實現負載均衡的分析就到這了,還是有很多疏漏的地方,但是大致的邏輯就是這樣的了,還有一些更深層的比如如何根據策略選出當前提供服務的節點等,留待后續補充,來日方長~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM