【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就能負載均衡?
之所以標記了@LoadBalanced
的RestTemplate
會帶有負載均衡的功能,是因為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))
去負載均衡的,而從上圖我們也可以看到,我們RestTemplate
的loadBanalcer
是RibbonLoadBalancerClient
,所以說,最後是通過Ribbon
是負載均衡的。
(3)、那究竟是誰幫RestTemplate加上這個攔截器的?而且是什麼時候加的?
① LoadBalancerAutoConfiguration配置類
關於@LoadBalanced
自動生效的配置,我們需要來到這個自動配置類:LoadBalancerAutoConfiguration
。
我們可以看到這個配置類上有倆個註解:@ConditionalOnClass({RestTemplate.class})
,@ConditionalOnBean({LoadBalancerClient.class})
,意思是說:只要有RestTemplate
類存在,並且Spring
容器中存在LoadBalancerClient
型別的Bean,這個配置類才會生效。首先,Spring
的web
模組已經提供了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();
}
那麼我們就知道了,接下來要解讀的程式碼就是為了處理一個個帶有@LoadBalanced
的RestTemplate
們,利用RestTemplateCustomizer
給RestTemplate
們加上攔截器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初始化之後,利用RestTemplateCustomizer
給RestTemplate
們加上攔截器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
如何進行負載均衡的也很感興趣,有空再大家一起研究研究