前天,有個前端大佬問了我兩個問題:爲啥不引入Ribbon
依賴就能使用Ribbon
?爲啥RestTemplate
加上@LoadBalanced
註解就能負載均衡了?我也表示很疑惑,而我本身其實也真的沒去了解過,因此趁着工做不太忙,趕忙去研究一波。前端
第一個問題比較簡單,通常都是其餘依賴引入了Ribbon
,我這裏是Nacos
,而他那邊也是註冊中心Eureka
。
java
第二個問題因爲有一點深度,因此須要好好的研究一番。web
一個啓動負載均衡,一個不啓動負載均衡app
@Configuration public class MyConfiguration { // 啓動負載均衡 @LoadBalanced @Bean RestTemplate loadBalanced() { return new RestTemplate(); } // 不啓動負載均衡 @Bean RestTemplate restTemplate() { return new RestTemplate(); } }
@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
之因此標記了@LoadBalanced
的RestTemplate
會帶有負載均衡的功能,是由於RestTemplate
裏面加入LoadBalancerInterceptor
攔截器。咱們也能夠看到,咱們上面的代碼使用的loadBalanced
確實有LoadBalancerInterceptor
攔截器。
ide
RestTemplate
的每次請求都會被此攔截,而後利用Ribbon
實現負載均衡邏輯。ui
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
是負載均衡的。
this
關於@LoadBalanced
自動生效的配置,咱們須要來到這個自動配置類:LoadBalancerAutoConfiguration
。url
咱們能夠看到這個配置類上有倆個註解:@ConditionalOnClass({RestTemplate.class})
,@ConditionalOnBean({LoadBalancerClient.class})
,意思是說:只要有RestTemplate
類存在,而且Spring
容器中存在LoadBalancerClient
類型的Bean,這個配置類纔會生效。首先,Spring
的web
模塊已經提供了RestTemplate
類,而Ribbon
也提供了實現LoadBalancerClient
接口的實現類,因此說上面全部的條件都符合了,該配置類會生效。3d
@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();
咱們會先找攔截器相關的代碼,由於此時咱們都知道負載均衡主要靠的是攔截器,因此,上代碼:
@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); }; }
還有另一段很重要的代碼,須要來解讀一下:
首先咱們得先了解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
,來實現負載均衡。
@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
如何進行負載均衡的也很感興趣,有空再你們一塊兒研究研究😄。