天天看點

Spring Cloud Netflix 負載均衡元件—Ribbon 進階應用

上一部分,我們使用Ribbon通過Sping 提供的RestTemplate了解了Ribbon負載均衡的使用,那麼是如何通過RestTemplate實作用戶端的負載均衡的呢?本篇我們将詳細分析Ribbon是如何使用RestTemplate進行負載均衡的,并且會介紹Ribbon的一些自定義的配置。

在Spring-Web中,我們使用RestTemplate調用遠端服務時,不涉及負載均衡,URI隻需要是IP+端口即可,而在Ribbon中則是服務名,是以Ribbon需要根據服務名擷取要調用的服務的IP +端口位址。這裡用到了Spring-Web提供的RestTemplate的攔截器ClientHttpRequestInterceptor,在請求之前對請求進行攔截,可以改變要請求的位址。如下類圖為ClientHttpRequestInterceptor的具體實作

Spring Cloud Netflix 負載均衡元件—Ribbon 進階應用

在上面的類中其中LoadBalanceInterceptor和RetryLoadBalanceInterceptor為Ribbon為我們提供的兩個攔截器,一個用于負載均衡,一個用于請求重試,我們可以先看源碼:LoadBalanceInterceptor。

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
//Ribbon用戶端
    private LoadBalancerClient loadBalancer;
//負載均衡請求工廠類
    private LoadBalancerRequestFactory requestFactory;
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }
    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
//擷取請求的URL
        URI originalUri = request.getURI();
//擷取請求的服務名稱
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
//使用Ribbon執行調用
        return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
    }
}
           

 通過上面的代碼我們看出請求最終交給LoadBalanceClient執行個體去執行,LoadBalanceClient是Ribbon提供的負載均衡用戶端,很多重要的邏輯都在這裡處理。如下為LoadBalanceClient的類圖:

Spring Cloud Netflix 負載均衡元件—Ribbon 進階應用

我們以RibbonLoadBalancerClient為例講解,在LoadBalanceInterceptor中調用了LoadBalanceClient的execute方法,如下為RibbonLoadBalancerClient的實作:

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
//擷取一個負載均衡器
    ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//根據負載均衡器擷取一個服務
    Server server = this.getServer(loadBalancer);
    if (server == null) {
        throw new IllegalStateException("No instances available for " + serviceId);
    } else {
        RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
        return this.execute(serviceId, ribbonServer, request);
    }
}
           

 在上面的代碼中我們根據serviceId擷取一個負載均衡器,然後根據負載均衡器擷取一個Server,後續的請求會請求到該Server,Server執行個體中包含主機和端口字段。我們所要關注的就是通過負載均衡器擷取Server的過程。如下為ILoadBalancer的實作:

Spring Cloud Netflix 負載均衡元件—Ribbon 進階應用

 如上所示為ILoadBalancer的所有實作類,其中NoOpLoadBalancer為不需要負載均衡,這裡不做講解。然後我們看ILoadBalancer的其他實作。如上所示為ILoadBalancer的所有實作類,其中NoOpLoadBalancer為不需要負載均衡,這裡不做講解。然後我們看ILoadBalancer的其他實作。AbstractLoadBalancer是ILoadBalancer接口的抽象實作。在該抽象類中定義了一個關于服務執行個體的分組枚舉類 ServerGroup, 它包含以下三種不同類型。ALL: 所有服務執行個體;STATUS_UP: 正常服務的執行個體;STATUS_NOT_UP: 停止服務的執行個體。我們選擇其中的BaseLoadBalancer檢視源碼,它是其他負均衡元件的基礎實作。源碼如下所示:

public Server chooseServer(Object key) {
        if (this.counter == null) {
            this.counter = this.createCounter();
        }、
        this.counter.increment();
        if (this.rule == null) {
            return null;
        } else {
            try {
                //通負載均衡規則,傳回一個位址
                return this.rule.choose(key);
            } catch (Exception var3) {
                logger.warn("LoadBalancer [{}]:  Error choosing server for key {}", new Object[]{this.name, key, var3});
                return null;
            }
        }
    }
           

上面的代碼也是選擇一個位址,但是負載均衡元件不是自己去選擇而是交給一個接口為IRule的負載均衡規則去選擇一個位址,它預設的負載均衡規則為RoundRobinRule模式。如下為Ribbon提供的負載均衡規則實作:

Spring Cloud Netflix 負載均衡元件—Ribbon 進階應用

上面的類圖是IRule負載均衡政策的實作,下面将會上面常用的實作進行簡單的講解。下面的介紹僅僅簡單的介紹了一下實作原理,詳細實作可以自行閱讀源碼。

Random Rule :該政策實作了從服務執行個體清單中随機選擇一個服務執行個體的功能。從具體的實作上看,它會使用傳入的負載均衡器來獲得可用執行個體清單upList和所有執行個體清單allList, 并通過rand.nextint(serverCoun七)函數來擷取 一個 随機數,并将該随機數作為 upList 的索引值來傳回具體執行個體。

RoundRobinRule:該政策實作了按照線性輪詢的方式依次選擇每個服務執行個體的功能。 它的具體實作如下,RandomRule 非常類似。 除了循環條件不同外, 就是從可用清單中擷取所謂的邏輯不同。 從循環條件中, 它增加了一個 count計數變量, 該變量會在每次循環之後累加, 也就是說, 如果一直選擇不到 server 超過 10 次, 那麼就會結束嘗試, 并列印一個警告資訊 No available alive servers after 10 tries from load balancer: ...。而線性輪詢的實作則是通過Atomicinteger nextServerCyclicCounter對象實作,每次進行執行個體選擇時通過調用 incrementAndGetModulo 函數實作遞增。

RetryRule:該政策實作了一個具備重試機制的執行個體選擇功能。它在其内部還定義了一個IRule 對象,預設使用了 RoundRobinRule 執行個體。 而在 choose 方法中則實作了對内部定義的政策進行反複嘗試的政策, 若期間能夠選擇到具體的服務執行個體就傳回,若選擇不到就根據設定的嘗試結束時間為阙值(maxRetryMillis參數定義的值+ choose 方法開始執行的時間戳)當超過該闌值後就傳回 null。

Weighted ResponseTimeRul:該政策是對 RoundRobinRule 的擴充, 增加了根據執行個體的運作情況來計算權重,并根據權重來挑選執行個體, 以達到更優的配置設定效果,具體實作這裡不在解釋,可以檢視源碼自行了解。

BestAvailableRule:該政策繼承自ClientConfigEnabledRoundRobinRule, 在實作中它注入了負載均衡器的統計對象LoadBalancerStats ,同時在具體的choose算法中利用LoadBalancerStats 儲存的執行個體統計資訊來選擇滿足要求的執行個體。它通過周遊負載均衡器中維護的所有服務執行個體, 會過濾掉故障的執行個體,并找出并發請求數最小的一個, 是以該政策的特性是可選出最空閑的執行個體。

PredicateBasedRule:這是一個抽象政策它也繼承了 ClientConfigEnabledRoundRobinRule, 它是一個基千 Predicate 實作的政策 Predicate 是 Google Guava Collection 工具對集合進行過濾的條件接口。先通過子類中實作的 Predicate 邏輯來過濾一部分服務執行個體, 然後再以線性輪詢的方式從過濾後的執行個體清單中選出一個。具體實作可以檢視源碼

AvailabilityFilteringRul:該政策繼承自上面介紹的抽象政策 PredicateBasedRule, 是以它也繼承了“先過濾清單,再輪詢選擇”的基本處理邏輯,其中過濾條件使用了 AvaliabilityPredicate。

ZoneAvoidanceRule 它也是PredicateBasedRule的具體實作類。它使用了 CompositePredicate 來進行服務執行個體清單的過濾。這是一個組合過濾條件,在其構造函數中,它以ZoneAvoidancePredicate 為主過濾條件,AvailabilityPredicate為次過濾條件初始化了組合過濾條件的執行個體。

Ribbon配置

前面對Ribbon的負載均衡政策做了簡單的講解,介紹了幾種負載均衡政策,而作為Ribbon的配置,自然也是對負載均衡政策的配置,Ribbon提供了兩種方式對Ribbon進行配置,一種是以代碼的形式,一種是以配置檔案的形式,在介紹之前我們先看一下要配制的有哪些内容:IRule: Ribbon 的負載均衡政策;IPing: Ribbon的執行個體檢查政策;ILoadBalancer: 負載均衡器;IClientConfig:Ribbon 的 用戶端配置;ServerList<Server>: 服務執行個體清單的維護機制; ServerListFilter<Server>: 服務執行個體清單過濾機制。

一般我們需要配置的是IRule、IPing、和ILoadBalancer三個執行個體,我們隻需要使用@Configuration注解類将執行個體注入即可。代碼如下所示:

@Configuration
public class IRuleConfig {
    @Bean
    public IRule iRule() {
        return new RandomRule();
    }
}
           

如上代碼所示,我們配置了一個全局的負載均衡政策,所有調用都通過該政策負載,如果我們想要為某個服務做專門的政策則可以使用@RibbonClient注解,代碼如下所示:

@Configuration
@RibbonClient(name = "ribbon-provider",configuration = IRuleConfig.class)
public class RibbonProviderIRuleConfig {
}
           

除了使用代碼進行配置,在Camden版本之後還支援以配置檔案的形式進行配置,可以直接通 過<clientName>.ribbon.<key>=<value>的形式進行配置。比如我們要實作與上面例子一樣的配置(将ribbon-provider 服務用戶端的IPing 接口實作替換為 PingUrl), 隻需在application.properties配置中增加下面的内容即可:

ribbin-provider.ribbon.NFLoadBalancerPingClassName=com.netf讓x.loadbalancer.PingUrl
           

除了配置IPing之外,還可以配置IRule,ILoadBalancer,ServerList,ServerListFilter等,他們的key分别為NFLoadBalancerRuleClassName,NFLoadBalancerClassName;NIWSServerListClassName,NIWSServerListFilterClassName.

本篇介紹了Ribbon的核心:負載均衡,包括負載均衡政策IRule以及負載均衡的配置。并沒有将負載均衡政策源碼貼到部落格,有興趣的可以自行閱讀閱讀源碼。

繼續閱讀