Robbin負載均衡


Robbin是在Spring Cloud中的一個組件,是由Netfix發布的負載均衡器,有助於控制HTTP和TCP客戶端的行為。它給我們提供了默認的輪詢、隨機等負載均衡算法。同時也可以由我們定義自己的算法。

由於Robbin已經被集成在Eureka里面,因此我們這個樣例的代碼都是在《微服務Eureka使用詳解》的基礎上進行。

參考博客:https://blog.csdn.net/u013089490/article/details/83786844https://blog.csdn.net/dwhdome/article/details/86477961 

 

負載均衡樣例

(1)我們首先啟動好在《微服務Eureka使用詳解》中編寫的三個服務:服務注冊中心,user服務,roles服務。訪問Eureka的管理頁面可以看到如下內容:

(2)下面我們先來修改User服務(只修改controller):

    @GetMapping("users/{id}")
    public String getUser(@PathVariable("id") String id) {
        String str = "7001User.id" + id;
        System.out.println(str);
        return str;
    }

啟動服務,它的端口是7001。

然后再復制一個User項目,將Controller內容調整為如下:

    @GetMapping("users/{id}")
    public String getUser(@PathVariable("id") String id) {
        String str = "7002User.id" + id;
        System.out.println(str);
        return str;
    }

以及將配置文件中的端口修改為7002

server.port=7002

啟動該應用。

這時我們再查看Eureka服務頁面:

可以清楚的看到USER服務的可用區域(Availability Zones)已經從(1)變成了(2)。狀態(status)已經變成了兩個服務地址7001和7002。

(3)Roles服務的負載均衡在《微服務Eureka使用詳解》中已經配置過了,我們這里查看一下即可。

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

(4)訪問Roles服務路徑~/roles/{id},可用連續多次訪問,這里假如我連續訪問5次:

/roles/1
/roles/2
/roles/3
/roles/4
/roles/5

可用看到返回的結果:

7001User.id1
7002User.id2
7001User.id3
7002User.id4
7001User.id5

兩個User服務,7001端口和7002端口是默認處於一個輪詢的狀態。假設這一次訪問7001端口,下一次就訪問7002端口,以此類推。

 

修改負載均衡策略

負責負載均衡策略的頂級接口:

com.netflix.loadbalancer.IRule

所有的負責均衡算法均實現了這個接口,它的實現類如下:

默認情況下,使用的是

com.netflix.loadbalancer.RoundRobinRule:以輪詢的方式進行負載均衡。

常用的還有

com.netflix.loadbalancer.RandomRule:隨機策略
com.netflix.loadbalancer.RetryRule:重試策略。
com.netflix.loadbalancer.WeightedResponseTimeRule:權重策略。會計算每個服務的權重,越高的被調用的可能性越大。
com.netflix.loadbalancer.BestAvailableRule:最佳策略。遍歷所有的服務實例,過濾掉故障實例,並返回請求數最小的實例返回。
com.netflix.loadbalancer.AvailabilityFilteringRule:可用過濾策略。過濾掉故障和請求數超過閾值的服務實例,再從剩下的實力中輪詢調用。

 如果我們要實現自己的策略,可以繼承IRule接口,下面我們來以RoundRobinRule為例查看一下如何實現負載均衡策略。

(1)IRule接口

public interface IRule {

    // 返回經過負載均衡后最終調用的服務
    Server choose(Object var1);

    void setLoadBalancer(ILoadBalancer var1);

    ILoadBalancer getLoadBalancer();
}

(2)RoundRobinRule類

我們先看最重要的choose(Object)方法

    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

里面調用了我們另一個choose(ILoadBalancer, Object)方法

    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            log.warn("no load balancer");
            return null;
        } else {
            Server server = null;
            int count = 0;

            while(true) {
                if (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) {
                        int nextServerIndex = this.incrementAndGetModulo(serverCount);
                        server = (Server)allServers.get(nextServerIndex);
                        if (server == null) {
                            Thread.yield();
                        } else {
                            if (server.isAlive() && server.isReadyToServe()) {
                                return server;
                            }

                            server = null;
                        }
                        continue;
                    }

                    log.warn("No up servers available from load balancer: " + lb);
                    return null;
                }

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

                return server;
            }
        }
    }

在上面的方法中,主要內容是在while(true)內獲取下一個server,獲取的方法是incrementAndGetModulo(int)。然后根據方法返回的服務下標,從服務集合中找到對應的server,如果server存在且存活,會直接使用這個server。如果server不存在或不存在,則會再循環獲取下一個。直到循環10次,或着沒有從服務注冊中心找到可用的服務,會返回null。

 核心的incrementAndGetModulo(int)方法

    private int incrementAndGetModulo(int modulo) {
        int current;
        int next;
        do {
            current = this.nextServerCyclicCounter.get(); //nextServerCyclicCounter是AtomicInteger對象,默認值0,可保證線程安全性 
            next = (current + 1) % modulo; //每次往后移一位,取集合中的下一個server。這里要注意的是從1開始,即數組中的第二個server會被第一個調用。
        } while(!this.nextServerCyclicCounter.compareAndSet(current, next)); //操作完成后用CAS操作將next賦值給nextServerCyclicCounter return next;
    }

(3)可用順便再看一下RetryRule類。

    public Server choose(ILoadBalancer lb, Object key) {
        long requestTime = System.currentTimeMillis();
        long deadline = requestTime + this.maxRetryMillis;
        Server answer = null;
        answer = this.subRule.choose(key); //內部仍然是使用了輪詢策略。 if ((answer == null || !answer.isAlive()) && System.currentTimeMillis() < deadline) {
            InterruptTask task = new InterruptTask(deadline - System.currentTimeMillis());

            while(!Thread.interrupted()) {
                answer = this.subRule.choose(key);
                if (answer != null && answer.isAlive() || System.currentTimeMillis() >= deadline) {
                    break;
                }

                Thread.yield();
            }

            task.cancel();
        }

        return answer != null && answer.isAlive() ? answer : null;
    }

這個類采用的是重試策略,可以看到里面其實仍是采用了輪詢策略,只不過如果輪詢的server無法訪問,或者不存活,會在指定的時間(500)內一直獲取下一個server,直到找到一個存活的server。

注意:上面所說的故障服務,是由Eureka注冊中心來判斷。即使服務已經掛掉,但是Eureka的實例未過期,仍會被判斷為正常。但是實際的返回可能是null等。

 

如果我們要實現自己的負載均衡策略,也可以通過繼承IRule接口,在配置文件中進行配置。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM