前提概要:
前天,有個前端大佬問了我兩個問題:為啥不引入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
如何進行負載均衡的也很感興趣,有空再大家一起研究研究😄。