1. 程式人生 > >Spring Cloud原始碼分析Ribbon

Spring Cloud原始碼分析Ribbon

在之前介紹使用Ribbon進行服務消費的時候,我們用到了RestTemplate,但是熟悉Spring的同學們是否產生過這樣的疑問:RestTemplate不是Spring自己就有的嗎?跟Ribbon的客戶端負載均衡又有什麼關係呢?下面在本文,我們來看RestTemplateRibbon是如何聯絡起來並實現客戶端負載均衡的。

首先,回顧一下之前的消費者示例:我們是如何實現客戶端負載均衡的?仔細觀察一下程式碼之前的程式碼,我們可以發現在消費者的例子中,可能就是這個註解@LoadBalanced是之前沒有接觸過的,並且從命名上來看也與負載均衡相關。我們不妨以此為線索來看看原始碼實現的機制。

@LoadBalanced註解原始碼的註釋中,我們可以知道該註解用來給RestTemplate標記,以使用負載均衡的客戶端(LoadBalancerClient)來配置它。

通過搜尋LoadBalancerClient,我們可以發現這是Spring Cloud中定義的一個介面:

123456789
public interface LoadBalancerClient {    ServiceInstance choose(String serviceId);    <T> T execute(String serviceId, LoadBalancerRequest<T> request)
throws IOException
; URI reconstructURI(ServiceInstance instance, URI original);}

從該介面中,我們可以通過定義的抽象方法來了解到客戶端負載均衡器中應具備的幾種能力:

  • ServiceInstance choose(String serviceId):根據傳入的服務名serviceId,從負載均衡器中挑選一個對應服務的例項。
  • T execute(String serviceId, LoadBalancerRequest request) throws IOException
    :使用從負載均衡器中挑選出的服務例項來執行請求內容。
  • URI reconstructURI(ServiceInstance instance, URI original):為系統構建一個合適的“host:port”形式的URI。在分散式系統中,我們使用邏輯上的服務名稱作為host來構建URI(替代服務例項的“host:port”形式)進行請求,比如:http://myservice/path/to/service。在該操作的定義中,前者ServiceInstance物件是帶有host和port的具體服務例項,而後者URI物件則是使用邏輯服務名定義為host的URI,而返回的URI內容則是通過ServiceInstance的服務例項詳情拼接出的具體“host:post”形式的請求地址。

順著LoadBalancerClient介面的所屬包org.springframework.cloud.client.loadbalancer,我們對其內容進行整理,可以得出如下圖的關係:

從類的命名上我們初步判斷LoadBalancerAutoConfiguration為實現客戶端負載均衡器的自動化配置類。通過檢視原始碼,我們可以驗證這一點假設:

從類的命名上我們初步判斷LoadBalancerAutoConfiguration為實現客戶端負載均衡器的自動化配置類。通過檢視原始碼,我們可以驗證這一點假設:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
@Configuration@ConditionalOnClass(RestTemplate.class)@ConditionalOnBean(LoadBalancerClient.class)public class LoadBalancerAutoConfiguration {    @LoadBalanced    @Autowired(required = false)    private List<RestTemplate> restTemplates = Collections.emptyList();    @Bean    public SmartInitializingSingleton loadBalancedRestTemplateInitializer(            final List<RestTemplateCustomizer> customizers) {        return new SmartInitializingSingleton() {            @Override            public void afterSingletonsInstantiated() {                for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {                    for (RestTemplateCustomizer customizer : customizers) {                        customizer.customize(restTemplate);                    }                }            }        };    }    @Bean    @ConditionalOnMissingBean    public RestTemplateCustomizer restTemplateCustomizer(            final LoadBalancerInterceptor loadBalancerInterceptor) {        return new RestTemplateCustomizer() {            @Override            public void customize(RestTemplate restTemplate) {                List<ClientHttpRequestInterceptor> list = new ArrayList<>(                        restTemplate.getInterceptors());                list.add(loadBalancerInterceptor);                restTemplate.setInterceptors(list);            }        };    }    @Bean    public LoadBalancerInterceptor ribbonInterceptor(            LoadBalancerClient loadBalancerClient) {        return new LoadBalancerInterceptor(loadBalancerClient);    }}

LoadBalancerAutoConfiguration類頭上的註解可以知道Ribbon實現的負載均衡自動化配置需要滿足下面兩個條件:

  • @ConditionalOnClass(RestTemplate.class)RestTemplate類必須存在於當前工程的環境中。
  • @ConditionalOnBean(LoadBalancerClient.class):在Spring的Bean工程中有必須有LoadBalancerClient的實現Bean。

在該自動化配置類中,主要做了下面三件事:

  • 建立了一個LoadBalancerInterceptor的Bean,用於實現對客戶端發起請求時進行攔截,以實現客戶端負載均衡。
  • 建立了一個RestTemplateCustomizer的Bean,用於給RestTemplate增加LoadBalancerInterceptor攔截器。
  • 維護了一個被@LoadBalanced註解修飾的RestTemplate物件列表,並在這裡進行初始化,通過呼叫RestTemplateCustomizer的例項來給需要客戶端負載均衡的RestTemplate增加LoadBalancerInterceptor攔截器。

接下來,我們看看LoadBalancerInterceptor攔截器是如何將一個普通的RestTemplate變成客戶端負載均衡的:

123456789101112131415161718192021222324252627282930313233343536373839404142