首頁 > 軟體

SpringCloud超詳細講解負載均衡元件Ribbon原始碼

2022-07-16 18:01:10

前言

上一篇文章中我們通過自己開發了一個負載均衡元件,實現了隨機演演算法的負載均衡功能,如果要實現其他演演算法,還需要修改程式碼增加相應的功能。這一篇文章,我們將介紹一個更簡單的負載均衡實現,使用**@LoadBalanced**註解實現負載均衡的功能。

專案實戰

建立專案

同樣的,我們的專案現在依然有一個registry註冊中心,一個provider服務提供者,接下來,我們再次修改一下consumer服務消費者的程式碼:

@EnableEurekaClient
@SpringBootApplication
@RestController
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }
    @Autowired
    DiscoveryClient discoveryClient;
    @Autowired
    RestTemplate restTemplate;
    @GetMapping("/hello2")
    public String hello2(String name) {
        String returnInfo = restTemplate.getForObject(  "http://provider/hello?name={1}", String.class, name);
        return returnInfo;
    }
}

在這個版本,同樣的,還是建立RestTemplate Bean物件,不同的是上面僅僅增加了@LoadBalanced註解。

啟動專案驗證

依然正確返回了結果!

太神奇了吧,比我們自己開發的負載均衡元件簡單太多了吧,僅僅在restTemplate() 方法上面增加了一個@LoadBalanced註解,怎麼就實現的呢?廢話不說,為了一探究竟,扒一扒原始碼吧!

原始碼分析

首先,點選@LoadBalanced註解進去,沒有什麼特別之處,那麼我們在想想,Spring在建立Bean範例的時候,註解在什麼地方起了作用?什麼?不知道?翻一下這篇文章吧:

肝了兩週,一張圖解鎖Spring核心原始碼

通過回顧Spring啟動以及Bean的生命週期建立過程,我們就會發現加上@LoadBalancer註解後,專案啟動時就會載入LoadBalancerAutoConfiguration這個設定類(通過spring-cloud-commons包下面的的spring.factories)。通過檢視該設定類原始碼,發現其有個靜態內部類LoadBalancerInterceptorConfig,其內部又建立了一個負載均衡攔截器:LoadBalancerInterceptor,該攔截器包含有一個loadBalancerClient引數:

@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
        }
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
        }
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
    }

我們繼續點選LoadBalancerInterceptor類進入,發現intercept方法,該方法中呼叫了LoadBalancerClient的execute方法,

    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);
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }

LoadBalancerClient是一個介面,點選進去我們發現其實現類是RibbonLoadBalancerClient,檢視其繼承關係:

通過介面中的方法名稱,我們可以猜想,choose方法就是選擇其中服務列表中其中一個服務,reconstructURI方法就是重新構造請求的URI。

選擇服務

choose方法是在RibbonLoadBalancerClient實現類中實現的

    public ServiceInstance choose(String serviceId, Object hint) {
        Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
        return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
    }

在這個方法中,先呼叫 getServer 方法獲取服務,這個方法最終會呼叫 ILoadBalancer 介面的 chooseServer 方法,而 ILoadBalancer 介面的實現類預設是ZoneAwareLoadBalancer。

ZoneAwareLoadBalancer 繼承自 DynamicServerListLoadBalancer ,而在 DynamicServerListLoadBalancer 的構造方法中,呼叫了 this.restOfInit(clientConfig);在restOfInit這個方法中,通過 this.updateListOfServers()來獲取服務列表;

而在chooseServer ()方法中,就會根據負載均衡演演算法,選擇其中一個服務並返回:

 BaseLoadBalancer zoneLoadBalancer = this.getLoadBalancer(zone);
 server = zoneLoadBalancer.chooseServer(key);

地址替換

選擇其中一個服務資訊後,怎麼將介面從 http://provider/hello 變為 http://localhost:8003/hello 呢?還記得上面我們說的reconstructURI方法嗎?通過設定類LoadBalancerAutoConfiguration載入後,會注入LoadBalancerInterceptor攔截器,該攔截器會攔截我們的請求,並對請求地址進行處理,重構方法的具體實現在 LoadBalancerContext 類的 reconstructURIWithServer 方法中

public URI reconstructURIWithServer(Server server, URI original) {
        String host = server.getHost();
        int port = server.getPort();
        String scheme = server.getScheme();
        if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
            return original;
        } else {
            if (scheme == null) {
                scheme = original.getScheme();
            }
            if (scheme == null) {
                scheme = (String)this.deriveSchemeAndPortFromPartialUri(original).first();
            }
            try {
                StringBuilder sb = new StringBuilder();
                sb.append(scheme).append("://");
                if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
                    sb.append(original.getRawUserInfo()).append("@");
                }
                sb.append(host);
                if (port >= 0) {
                    sb.append(":").append(port);
                }
                sb.append(original.getRawPath());
                if (!Strings.isNullOrEmpty(original.getRawQuery())) {
                    sb.append("?").append(original.getRawQuery());
                }
                if (!Strings.isNullOrEmpty(original.getRawFragment())) {
                    sb.append("#").append(original.getRawFragment());
                }
                URI newURI = new URI(sb.toString());
                return newURI;
            } catch (URISyntaxException var8) {
                throw new RuntimeException(var8);
            }
        }
    }

可以看到該方法中,將原始的請求地址original,替換成了選取的服務的IP和埠。並最終呼叫該服務的介面方法。

看到這裡,再想想我們上一章的內容,是不是有異曲同工之妙?

總結

通過新增@LoadBalanced註解,就及其簡單的實現了負載均衡的功能,與其說是Ribbon的強大,不如說是Spring的強大,Spring在整個上下文建立過程中,在不同的時機開放了一個又一個的介面,這就為各種元件的繼承提供了遍歷,同時也進一步促進了Spring生態的快速發展。

到此這篇關於SpringCloud超詳細講解負載均衡元件的文章就介紹到這了,更多相關SpringCloud負載均衡元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com