1. 程式人生 > >【Spring Cloud 原始碼解讀】之 【這也太神奇了,RestTemplate加上一個@LoadBalanced註解就能實現負載均衡!】

【Spring Cloud 原始碼解讀】之 【這也太神奇了,RestTemplate加上一個@LoadBalanced註解就能實現負載均衡!】

前提概要:

前天,有個前端大佬問了我兩個問題:為啥不引入Ribbon依賴就能使用Ribbon?為啥RestTemplate加上@LoadBalanced註解就能負載均衡了?我也表示很疑惑,而我自己其實也真的沒去了解過,所以趁著工作不太忙,趕緊去研究一波。

第一個問題比較簡單,一般都是其他依賴引入了Ribbon,我這裡是Nacos,而他那邊也是註冊中心Eureka

第二個問題由於有一點深度,所以需要好好的研究一番。

1、準備:啟動兩個服務提供者例項,然後啟動一個服務消費者例項

2、開始搞起來

1、準備兩個RestTemplate:

一個啟動負載均衡,一個不啟動負載均衡

@Configuration
public class MyConfiguration {

    // 啟動負載均衡
    @LoadBalanced
    @Bean
    RestTemplate loadBalanced() {
        return new RestTemplate();
    }

    // 不啟動負載均衡
    @Bean
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

2、負載均衡探索:

@Autowired
private RestTemplate loadBalanced;

@GetMapping("restTemplate-hello")
public String sayHello(){
    return loadBalanced.getForObject("http://cloud-nacos-discovery-server/hello",String.class);
}

注意:使用負載均衡的RestTemplate去請求時url一定得寫服務名,因為Ribbon會根據服務名[serviceId]去獲取所有例項,然後進行負載均衡。所以記得不能寫IP:Port

,不然會報錯。

java.lang.IllegalStateException: No instances available for 10.172.29.666

(1)、為何帶上@LoadBalanced就能負載均衡?

之所以標記了@LoadBalancedRestTemplate會帶有負載均衡的功能,是因為RestTemplate裡面加入LoadBalancerInterceptor攔截器。我們也可以看到,我們上面的程式碼使用的loadBalanced確實有LoadBalancerInterceptor攔截器。

(2)、攔截器是如何進行負載均衡的?

RestTemplate的每次請求都會被此攔截,然後利用Ribbon

實現負載均衡邏輯。

public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final 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);
      //這裡是使用負載均衡,而這裡的loadBalancer就是Spring Cloud提供的LoadBalancerClient介面的實現類。
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }

我們也看到,最後是通過(ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution))去負載均衡的,而從上圖我們也可以看到,我們RestTemplateloadBanalcerRibbonLoadBalancerClient,所以說,最後是通過Ribbon是負載均衡的。

(3)、那究竟是誰幫RestTemplate加上這個攔截器的?而且是什麼時候加的?

① LoadBalancerAutoConfiguration配置類

關於@LoadBalanced自動生效的配置,我們需要來到這個自動配置類:LoadBalancerAutoConfiguration

我們可以看到這個配置類上有倆個註解:@ConditionalOnClass({RestTemplate.class})@ConditionalOnBean({LoadBalancerClient.class}),意思是說:只要有RestTemplate類存在,並且Spring容器中存在LoadBalancerClient型別的Bean,這個配置類才會生效。首先,Springweb模組已經提供了RestTemplate類,而Ribbon也提供了實現LoadBalancerClient介面的實現類,所以說上面所有的條件都符合了,該配置類會生效。

@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
② 一個關鍵的成員變數

我們可以看到LoadBalancerAutoConfiguration中有一個成員變數:

//拿到Spring容器內所有的標註有@LoadBalanced註解的RestTemplate們。 注意:是帶有@LoadBalanced註解的
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
③ RestTemplateCustomizer來加攔截器

我們會先找攔截器相關的程式碼,因為此時我們都知道負載均衡主要靠的是攔截器,所以,上程式碼:

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
    return (restTemplate) -> {
        // 我們可以看到,如果我們沒有自己實現`RestTemplateCustomizer`,就會執行下面的邏輯,而最突兀的就是,它給每一個`RestTemplate`添加了`LoadBalancerInterceptor`,也就是實現負載均衡的重點所在。
        List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
        list.add(loadBalancerInterceptor);
        restTemplate.setInterceptors(list);
    };
}
④ 何時利用RestTemplateCustomizer來給RestTemplate加攔截器

還有另外一段很重要的程式碼,需要來解讀一下:

首先我們得先了解SmartInitializingSingleton是幹嘛的,它的afterSingletonsInstantiated()方法會在所有的單例Bean初始化完成之後,再去一個一個的去處理。

public interface SmartInitializingSingleton {
    void afterSingletonsInstantiated();
}

那麼我們就知道了,接下來要解讀的程式碼就是為了處理一個個帶有@LoadBalancedRestTemplate們,利用RestTemplateCustomizerRestTemplate們加上攔截器LoadBalancerInterceptor

@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
    return () -> {
        restTemplateCustomizers.ifAvailable((customizers) -> {
            // 遍歷上面提及的成員變數,帶@LoadBalanced的RestTemplate們
            Iterator var2 = this.restTemplates.iterator();

            while(var2.hasNext()) {
                RestTemplate restTemplate = (RestTemplate)var2.next();
                Iterator var4 = customizers.iterator();

                while(var4.hasNext()) {
                    // 利用上面的RestTemplateCustomizer給RestTemplate們加攔截器
                    RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                    customizer.customize(restTemplate);
                }
            }

        });
    };
}

所以最後,我們可以給第三個問題一個答案:在帶有@LoadBalanced註解的RestTemplate們完成Bean初始化之後,利用RestTemplateCustomizerRestTemplate們加上攔截器LoadBalancerInterceptor,來實現負載均衡。

3、非負載均衡探索

@Autowrite
private RestTemplate restTemplate;

@GetMapping("restTemplate-hello")
public String sayHello(){
    return myRestTemplate.getForObject("http://10.172.29.666:8887/hello",String.class);
}

首先可以看到,RestTemplate不再帶有攔截器

而且,我們可以看到,最後介面走的是SimpleBufferingClientHttpRequest,而不是RibbonLoadBalancerClient

到此,關於為什麼添加了@LoadBalanced就能進行負載均衡的分析已經結束。而如果大家對Ribbon如何進行負載均衡的也很感興趣,有空再大家一起研究研究