分佈式springcould服務調用Ribbon的負載均衡

[TOC]java

以前咱們介紹了管理分佈式組件註冊的服務;eureka、consul、zookeeper、nacos他們均可以實現咱們服務的註冊於獲取。
可是實際咱們仍是須要咱們本身調用最終的客戶端獲取數據的。

前提概要

  • 上面的服務發現框架均可以使用。consul由於須要保證網絡通訊正常。而eureka是咱們本身註冊java服務。因此這裏就選擇經過eureka來做爲咱們的服務治理框架。
  • 這裏咱們就藉助咱們以前eureka服務搭建整合文章。咱們直接用以前那個分支啓動eureka服務,一個order服務、兩個payment服務。

  • 而後仍是訪問咱們localhost/order/payment/123這個接看看響應是不是負載均衡的。

  • 這些都是咱們eureka章節的內容。這個時候問題來了。爲何restTemplate會實現負載均衡。這裏咱們查閱資料就會發如今服務治理框架中會注入ribbon框架。在ribbon註冊的時候回將ribbon提供的攔截器注入到restTemplate中。restTemplate執行以前會先走攔截器從而實現負載均衡。
  • 因此重點仍是在ribbon。由於是他實現了負載均衡

Ribbon做用

  • ribbon是springcloud項目組件。全名spring-cloud-ribbon。他的主要功能是負載均衡和服務調用。ribbon在服務調用是有超時,重試的設置。內部提供默認負載均衡機制。也提供接口方便咱們自定義負載均衡策略。
  • ribbon的服務調用藉助月RestTemplate,RestTemplate的負載均衡依賴於ribbon。二者是相輔相成的一個產品。

Ribbon原理

  • 上面也說了適合RestTemplate結合使用的。還有springcloud提供的Feign結合使用的。Ribbon首先內部在構建一個http包下的ClientHttpRequestInterceptor攔截器。
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
        LoadBalancerClient loadBalancerClient,
        LoadBalancerRequestFactory requestFactory) {
    return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}

  • 而後會獲取全部被LoadBalanced標準的RestTemplate。遍歷全部RestTemplate誒個注入咱們LoadBalancerInterceptor攔截器。在這個攔截器內部會實現服務列表獲取。而後負載均衡。

Ribbon源碼分析

  • 原理很簡單。就是在RestTemplate調用以前依據Ribbon的能力獲取真正須要調用的地址而後交由RestTemplate調用。

Ribbon自動配置


  • 基於springboot的spi機制咱們可以發如今springcloud-common中會加載LoadBalancerAutoConfiguration配置類。經過名稱咱們大概也能瞭解到這個類是配置負載均衡的自動配置類。
  • 下面咱們來看看這個類都爲咱們準備了哪些工做。

  • 首先是注入兩個成員變量。restTeplates、transformers兩個。
  • restTemplates就是獲取全部被@LoadBalanced標註的restTemplate。準備爲他們注入攔截器
  • LoadBalancerRequestTransformer類註釋Allows applications to transform the load-balanced {@link HttpRequest} given the chosen
  • LoadBalancerRequestTransformer類註釋意思就是容許給指定的請求切換負載均衡策略。這裏能力有限有時間在深挖一下。

LoadBalanced

  • 這裏咱們須要先介紹下Loadbanced這個註解。爲何加入這個註解咱們就能獲取到指定的RestTemplate集合呢。

  • 點開源碼發現也沒啥東西,就是一個註解表示。可是這個註解不通常。咱們注意到他內部有個元註解@Qualifier。這個註解是org.springframework.beans.factory.annotation包下。瞭解Spring的讀者應該知道這是spring注入類的一種方式。關於spring注入方式解析咱們單獨開篇分析下。這裏咱們只用記住@Qualifier會注入相同屬性的bean.
  • 什麼叫相同屬性的bean。好比咱們上面可能會多個地方注入RestTemplate。添加@Loadbanlanced註解至關於以下註解

  • 而後@Qualifier結合@Autowired註解就會注入全部RestTemplate在spring容器中的bean。且@Qualifier中的value=""的。也就是上面兩個註解spring就會蒐羅到負載均衡標記的RestTemplate

LoadBalancerInterceptor

  • 索羅到對象以後下面理所應到應該開始準備攔截器了


  • 上面生成RestTemplateCustomizer對象springcloud是經過lamda表達式生成的。實際上就是實現RestTemplateCustomizer這個接口。內部會將RestTemplate對象調用set方法將LoadBancerInterptor攔截器注入到對象內。在RestTemplate執行的時候回先通過過濾器的洗禮。這裏留個坑吧。關於RestTemplate調用咱們稍後再說。

回到LoadBalancerAutoConfiguration

  • 咱們繼續回到LoadBalancerAutoConfiguration . 上面咱們知道兩個注入的屬性的做用了。在後面咱們看到了SmartinitializingSingletonLoadBalancerRequestFactory。關於LoadbalancerRequestFactory這實際就是個工廠。在構造LoadBalancerIntercrptor攔截器的時候須要用到。
  • 重點在SmartInitializingSingleton這個類。裏面傳了一個參數經過ObjectProvider封裝的。ObjectProvider的做用能夠簡單理解爲@Autowired。而內部的RestTemplateCustomer就是咱們上文提到的做用是爲了封裝RestTemplate
  • customizer.customize就是調用添加攔截器了。
  • 到此RestTemplate機會被裝在有Ribbon實現的攔截器了。當咱們在經過RestTemplate調用接口的時候就會有負載均衡的功能了。

RetryTemplate

  • 自動配置以後咱們發現還有兩個配置類。仔細看看和上面的配置是同樣的。可是多了retry字眼。這個類主要依賴於RetryTemplate。是用來重試的。

  • 一樣是爲RestTemplate注入interceptor。只不過都是Retry模式的。

  • RetryLoadBalancerInterceptor攔截內部實現裏實際上就是RetryTemplate和RestTemplate的區別。

  • 兩個攔截器的區別其實就是前者多了一個重試機制。具體重試的策略是經過LoadBalancedRetryPolicy設置的。
  • 在Ribbon中RetryTemplate是須要外部提供的。由於咱們系統中沒有加入因此這裏爆紅。這裏也就沒有生成重試的效果。有興趣的讀者能夠試試。

RestTemplate結合Ribbon調用原理

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptorspring

  • 咱們直接觀察LoadBalancerInterceptor這個攔截器不難發現。他就是實現了ClientHttpRequestIntrceptor。這裏咱們記住這個接口。在RestTemplate調用的時候確定會設計到ClientHttpRequestInterceptor這個類。

RestTemplate源碼跟蹤

  • 咱們仍是拿咱們的order訂單舉例。還記的咱們的訂單訪問接口嗎

http://localhost/order/getpayment/123springboot

getForObject

@Override
@Nullable
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}
  • RestTemplate方法內部首先經過請求頭驗證並組裝response。最終會執行execute方法。

execute

  • execute內部是doExecute.上面是doExecute方法。大概邏輯也很簡單。
    ①、構建request對象
    ②、發送請求
    ③、處理響應
    ④、返回響應數據
  • 咱們的重點在構建request對象上。很明顯是在createRequest裏進行攔截器設置的。最終在request.execute執行中進行攔截器鏈路執行的。

  • RestTemplate的類結構就是集成HttpAccessor。內部會維護ClientHttpRequestFactory來構建request對象。

  • 最終會在InterceptingClientHttpRequestFactory這個類中的createRequest方法上。
    -內部就是屬性的賦值了。

  • 上面request.execute最終就會落在InterceptingClientHttpRequestFactory.execute 這個能夠跟蹤下這個類的接口就知道方法入口了。

  • 在上面咱們可以看到會先判斷是否有攔截器,有的話會直接交由攔截器執行。從代碼中咱們也可以看出InterceptingClientHttpRequest對象會在攔截器部分阻塞。因此這裏咱們不難看出上面Ribbon實現的LoadBalancerInterceptor。這個攔截器內部確定須要實現接口的調度。

  • 咱們在LoadBalancerInteptor攔截器中看到前面會Ribbon找尋地址。而後交由InterceptorHttpAccess中內置的InterceptorClientHttpRequestFactory工廠來處理請求。沒錯這個類就是咱們上面請求的東西。東西有回去。這個createRequest方法咱們上面已經看過了,若是內部存在攔截器就會交由攔截器實現。若是沒有就會進行轉發
  • 這裏InterceptorClientHttpRequestFactory有點責任鏈模式的感受。

Ribbon負載均衡源碼追蹤

  • 上面分別介紹了Ribbon自動配置、RestTemplate結合Ribbon發送請求兩部分源碼追蹤。如今咱們再把矛頭指向最終RIbbon的看家本領負載均衡的源碼吧。

  • 上面咱們源碼追蹤可以知道在調用以前是經過LoadBalancerClient.execute方法實現負載均衡的。LoadBalancerClient在那個模塊註冊到spring容器裏我暫時沒有找到。但願瞭解的讀者能夠指出。多謝!!!!!
public interface LoadBalancerClient extends ServiceInstanceChooser {
    <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
    <T> T execute(String serviceId, ServiceInstance serviceInstance,
            LoadBalancerRequest<T> request) throws IOException;
    URI reconstructURI(ServiceInstance instance, URI original);

}

  • 最終LoadBalancerClient內部是經過ILoadBalancer來實現負載均衡的。說實話筆者這裏爲了省事並無落實ILoadBalancerLoadBalancerClient之間是如何綁定的。
  • 咱們能夠看BaseLoadBalancerILoadBalancer的實現。裏面重要的是chooseServer這個方法。
public Server chooseServer(Object key) {
    if (counter == null) {
        counter = createCounter();
    }
    counter.increment();
    if (rule == null) {
        return null;
    } else {
        try {
            return rule.choose(key);
        } catch (Exception e) {
            logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", name, key, e);
            return null;
        }
    }
}
  • 咱們能夠看到裏面有個Counter對象用於管理數量。最後會有Irule對象去實現負載策略。RoundRobbinRule中就是經過AtomicInteger原子類操做請求次數在於容器數量取模決定獲取哪一個容器進行調用的。
public Server choose(ILoadBalancer lb, Object key) {
    if (lb == null) {
        log.warn("no load balancer");
        return null;
    }

    Server server = null;
    int count = 0;
    while (server == null && count++ < 10) {
        List<Server> reachableServers = lb.getReachableServers();
        List<Server> allServers = lb.getAllServers();
        int upCount = reachableServers.size();
        int serverCount = allServers.size();

        if ((upCount == 0) || (serverCount == 0)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }

        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }

    if (count >= 10) {
        log.warn("No available alive servers after 10 tries from load balancer: "
                + lb);
    }
    return server;
}

總結

-Ribbon爲咱們提供了不少中內置的負載均衡策略。經常使用的就是輪詢。若是上述的不知足咱們也能夠經過實現Irule來自定義策略規則。網絡

  • 只須要在啓動類上經過@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)來指定咱們的服務下負載策略。

相關文章
相關標籤/搜索