Spring Cloud Ribbon負載均衡策略詳解

Spring Cloud Ribbon負載均衡策略詳解。

Spring Cloud Ribbon是一個基於HTTP和TCP的客戶端負載均衡工具,它基於Netflix Ribbon實現,Spring Cloud集成了Netflix Ribbon,只是對Netflix Ribbon進行了一次封裝。

這裏使用的ribbon的版本是:ribbon-loadbalancer-2.2.2.jar。

 

 

一,IRule接口

 

IRule接口定義了選擇負載均衡策略的基本操作。通過調用choose()方法,就可以選擇具體的負載均衡策略。

 

// 選擇目標服務節點

Server choose(Object var1);

// 設置負載均衡策略

void setLoadBalancer(ILoadBalancer var1);

// 獲取負載均衡策略

ILoadBalancer getLoadBalancer();

 

二,ILoadBalancer接口

 

ILoadBalancer接口定義了ribbon負載均衡的常用操作,有以下幾個方法:

void addServers(List<Server> var1);

Server chooseServer(Object var1);

void markServerDown(Server var1);

List<Server> getReachableServers();

List<Server> getAllServers();

 

 

三,AbstractLoadBalancerRule抽象類

 

AbstractLoadBalancerRule實現了IRule接口和IClientConfigAware接口,主要對IRule接口的2個方法進行了簡單封裝。

private ILoadBalancer ib;

// 設置負載均衡策略

public void setLoadBalancer(ILoadBalancer ib){

    this.ib = ib;

}

// 獲取負載均衡策略

public ILoadBalancer getLoadBalancer(){

    return this.ib;

}

 

AbstractLoadBalancerRule是每個負載均衡策略需要直接繼承的類,Ribbon提供的幾個負載均衡策略,都繼承了這個抽象類。同理,我們如果需要自定義負載均衡策略,也要繼承這個抽象類。

 

四,AbstractLoadBalancerRule的實現類

 

AbstractLoadBalancerRule的實現類就是ribbon的具體負載均衡策略,首先來看默認的輪詢策略。

 

1,輪詢策略(RoundRobinRule)

 

輪詢策略理解起來比較簡單,就是拿到所有的server集合,然後根據id進行遍歷。這裏的id是ip+端口,Server實體類中定義的id屬性如下:

this.id = host + ":" + port

這裏還有一點需要注意,輪詢策略有一個上限,當輪詢了10個服務端節點還沒有找到可用服務的話,輪詢結束。

 

2,隨機策略(RandomRule)

 

隨機策略:使用jdk自帶的隨機數生成工具,生成一個隨機數,然後去可用服務列表中拉取服務節點Server。如果當前節點不可用,則進入下一輪隨機策略,直到選到可用服務節點爲止。

 

這裏在while循環中,使用了Thread#interrupted()方法和Thread#yield()方法,使用的很巧妙,可以參考一下。

 

 

while(server == null) {

    if (Thread.interrupted()) {

        return null;

    }

……

    // 如果隨機打到的節點爲null,則再次循環隨機

    if (server == null) {

        Thread.yield();

    } else {

        if (server.isAlive()) {

            return server;

        }

        // 如果節點不是存活狀態,則再次循環隨機

        server = null;

        Thread.yield();

    }

}

 

 

 

 

 

 

3,可用過濾策略(AvailabilityFilteringRule)

 

策略描述:過濾掉連接失敗的服務節點,並且過濾掉高併發的服務節點,然後從健康的服務節點中,使用輪詢策略選出一個節點返回。

AvailabilityFilteringRule#choose()方法實現如下:

 

 

// 記錄輪詢次數,最多輪詢10次

int count = 0;

// 輪詢10次

for(Server server = roundRobinRule.choose(key); count++ <= 10; server = roundRobinRule.choose(key)) {

    if (this.predicate.apply(new PredicateKey(server))) {

        return server;

    }

}

// 如果輪詢10次還沒有找到可用server,則執行父類中的篩選邏輯

return super.choose(key);

 

 

 

 

 

 

 

 

4,響應時間權重策略(WeightedResponseTimeRule)

 

策略描述:根據響應時間,分配一個權重weight,響應時間越長,weight越小,被選中的可能性越低。

如何計算權重呢?代碼邏輯位於WeightedResponseTimeRule$$ServerWeight#maintainWeights(),判斷邏輯還是挺複雜的,暫時沒時間看,先留着,以後有機會再研究。

注意,服務剛啓動時,由於統計信息不足,先使用輪詢策略。等到信息足夠了,切換到WeightedResponseTimeRule策略。

 

5,輪詢失敗重試策略(RetryRule)

 

輪詢失敗重試策略(RetryRule)是這樣工作的,首先使用輪詢策略進行負載均衡,如果輪詢失敗,則再使用輪詢策略進行一次重試,相當於重試下一個節點,看下一個節點是否可用,如果再失敗,則直接返回失敗。

這裏還有一個點要注意,重試的時間間隔,默認是500毫秒,我們可以自定義這個重試時間間隔。

this.maxRetryMillis = 500L;

 

另外,失敗重試策略的源碼中,RetryRule#choose()方法很有參考價值,如果我們需要自己實現調用接口的失敗重試功能的話,可以參考這個方法。這個方法中給我們展示瞭如何使用Thread#interrupted()和Thread#yield()。

 

6,併發量最小可用策略(BestAvailableRule)

 

策略描述:選擇一個併發量最小的server返回。如何判斷併發量最小呢?ServerStats有個屬性activeRequestCount,這個屬性記錄的就是server的併發量。輪詢所有的server,選擇其中activeRequestCount最小的那個server,就是併發量最小的服務節點。

 

接下來我們看源碼:

 

if (this.loadBalancerStats == null) {

    return super.choose(key);

} else {

    // 獲取所有的server

    List<Server> serverList = this.getLoadBalancer().getAllServers();

    int minimalConcurrentConnections = 2147483647;

    long currentTime = System.currentTimeMillis();

    Server chosen = null;

    Iterator var7 = serverList.iterator();

    while(var7.hasNext()) {

        Server server = (Server)var7.next();

        ServerStats serverStats = this.loadBalancerStats.getSingleServerStats(server);

        // 判斷斷路器是否跳閘,如果沒有跳閘,繼續往下走。

        if (!serverStats.isCircuitBreakerTripped(currentTime)) {

            int concurrentConnections = serverStats.getActiveRequestCount(currentTime);

            if (concurrentConnections < minimalConcurrentConnections) {

                minimalConcurrentConnections = concurrentConnections;

                chosen = server;

            }

        }

    }

    // 如果沒有找到併發量最小的服務節點,則使用父類的策略

    if (chosen == null) {

        return super.choose(key);

    } else {

      return chosen;

    }

}

 

併發量最小可用策略(BestAvailableRule)的優點是:可以充分考慮每臺服務節點的負載,把請求打到負載壓力最小的服務節點上。但是缺點是:因爲需要輪詢所有的服務節點,如果集羣數量太大,那麼就會比較耗時。當然一般來說,幾十臺,幾百臺的集羣數量是不用考慮這個問題的。因此對於大部分的項目而言,是一個不錯的選擇。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7,ZoneAvoidanceRule

策略描述:複合判斷server所在區域的性能和server的可用性,來選擇server返回。

 

四,BaseLoadBalancer

 

BaseLoadBalancer是一個負載均衡器,是ribbon框架提供的負載均衡器。Spring Cloud對ribbon封裝以後,直接調用ribbon的負載均衡器來實現微服務客戶端的負載均衡。

 

這裏需要注意,ribbon框架本身提供了幾個負載均衡器,BaseLoadBalancer只是其中之一。

Spring Cloud是如何封裝ribbon框架的呢?Spring Cloud提供了2個接口:ServiceInstanceChooser和LoadBalancerClient,這2個接口就是客戶端負載均衡的定義。具體實現類是RibbonLoadBalancerClient。RibbonLoadBalancerClient#choose()方法根據微服務實例的serviceId,然後使用配置的負載均衡策略,打到對於的微服務實例節點上。

 

OK,到這裏,我們簡單梳理一下Spring Cloud集成ribbon後,負載均衡的執行邏輯。

 

1,Spring Cloud RibbonLoadBalancerClient#choose()調用ribbon框架的BaseLoadBalancer。

 

2,BaseLoadBalancer#chooseServer()選擇具體的負載均衡策略(RoundRibonRule),然後執行。

但是,RibbonLoadBalancerClient#choose()是在哪裏調用的呢?這裏用到了攔截器,@RibbonClient註解自動化配置類LoadBalancerAutoConfiguration.class中有兩個註解:

@ConditionalOnClass({RestTemplate.class})

@ConditionalOnClass({LoadBalancerClient.class})

也就是說,在RestTemplate.class和LoadBalancerClient.class存在的情況下,LoadBalancerInterceptor.class會攔截RestTemplate.class上的@LoadBalanced註解,然後將請求中的微服務實例名serviceId轉化爲具體的ip+端口,然後去請求目標服務節點。

 

OK,有點亂,我們再來梳理一下調用關係:

1,@LoadBalanced註解

2,org.springframework.web.client.RestTemplate

3,LoadBalancerAutoConfiguration.class

4,LoadBalancerInterceptor.class攔截org.springframework.web.client.RestTemplate的請求,注入客戶端負載均衡功能,發送請求到目標服務節點。

 

這就是Spring Cloud 集成的ribbon客戶端負載均衡。

 

五,如何自定義負載均衡策略

 

Ribbon不僅實現了幾種負載均衡策略,也爲開發者提供了自定義負載均衡策略的支持。

 

自定義負載均衡策略有3個關鍵點:

 

1,繼承抽象類AbstractLoadBalancerRule

 

2,自定義的負載均衡策略類,不能放在@ComponentScan所掃描的當前包和子包下。

 

3,在主啓動類上添加@RibbonClient註解,或者在配置文件中指定哪個微服務使用自定義負載均衡策略。

 

@RibbonClient註解的使用方法如下:

@RibbonClient(name="微服務名稱", configuration="自定義配置類.class")

 

配置文件中配置項如下:

springboot.微服務名稱.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

或者

springboot.微服務名稱.NFLoadBalancerRuleClassName=com.xxx.xxx.xxx.自定義負載均衡策略實現類

 

這裏需要注意,Spring Cloud微服務啓動類上有個註解@SpringBootApplication,這個註解的源碼上標註了@ScanComponent註解,也就是說,我們的微服務啓動類其實間接引入了@ComponentScan註解。

@ComponentScan註解的作用就是掃描當前包及其子包下的標有指定註解的bean,然後把它們注入到IOC容器。

 

@Component會掃描哪些註解呢?有4個註解,分別是:

@Component

@Service

@Controller

@Repository

另外,所有間接引入了上面4個註解的註解,最終也會被@ComponentScan掃描,比如我們常用的@Configuration,也會被@ComponentScan掃描。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章