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


前提概要:

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

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

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

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是負載均衡的。
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不再帶有攔截器
RestTemplate不帶攔截器
而且,我們可以看到,最后接口走的是SimpleBufferingClientHttpRequest,而不是RibbonLoadBalancerClient
SimpleBufferingClientHttpRequest

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


免責聲明!

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



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