【一起學源碼-微服務】Ribbon 源碼四:進一步探究Ribbon的IRule和IPing


前言

前情回顧

上一講深入的講解了Ribbon的初始化過程及Ribbon與Eureka的整合代碼,與Eureka整合的類就是DiscoveryEnableNIWSServerList,同時在DynamicServerListLoadBalancer中會調用PollingServerListUpdater 進行定時更新Eureka注冊表信息到BaseLoadBalancer中,默認30s調度一次。

本講目錄

我們知道Ribbon主要是由3個組件組成的:

  1. ILoadBalancer
  2. IRule
  3. IPing

其中ILoadBalancer前面我們已經分析過了,接下來我們一起看看IRuleIPing中的具體實現。

目錄如下:

  1. 負載均衡默認Server選擇邏輯
  2. Ribbon實際執行http請求邏輯
  3. Ribbon中ping機制原理
  4. Ribbon中其他IRule負載算法初探

說明

原創不易,如若轉載 請標明來源!

博客地址:一枝花算不算浪漫
微信公眾號:壹枝花算不算浪漫

源碼分析

負載均衡默認Server選擇邏輯

還記得我們上一講說過,在Ribbon初始化過程中,默認的IRuleZoneAvoidanceRule,這里我們可以通過debug看看,從RibbonLoadBalancerClient.getServer() 一路往下跟,這里直接看debug結果:

image.png

然后我們繼續跟ZoneAvoidanceRule.choose() 方法:

public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
   
    /**
     * Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.
     * 
     */
    public abstract AbstractServerPredicate getPredicate();
        
    /**
     * Get a server by calling {@link AbstractServerPredicate#chooseRandomlyAfterFiltering(java.util.List, Object)}.
     * The performance for this method is O(n) where n is number of servers to be filtered.
     */
    @Override
    public Server choose(Object key) {
        ILoadBalancer lb = getLoadBalancer();
        Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
        if (server.isPresent()) {
            return server.get();
        } else {
            return null;
        }       
    }
}

這里是調用的ZoneAvoidanceRule的父類中的choose()方法,首先是拿到對應的ILoadBalancer,然后拿到對應的serverList數據,接着調用chooseRoundRobinAfterFiltering()方法,繼續往后跟:

public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {

    public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
        List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
        if (eligible.size() == 0) {
            return Optional.absent();
        }
        return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
    }

    private int incrementAndGetModulo(int modulo) {
        for (;;) {
            int current = nextIndex.get();
            int next = (current + 1) % modulo;
            if (nextIndex.compareAndSet(current, next) && current < modulo)
                return current;
        }
    }
}

到了這里可以看到incrementAndGetModulo()方法就是處理serverList輪詢的算法,這個和RoudRobinRule中采用的是一樣的算法,這個算法大家可以品一品,我這里也會畫個圖來說明下:

06_Ribbon輪詢算法圖解.jpg

看了圖=中的例子估計大家都會明白了,這個算法就是依次輪詢。這個算法寫的很精簡。

Ribbon實際執行http請求邏輯

我們上面知道,我們按照輪詢的方式從serverList取到一個server后,那么怎么把之前原有的類似於:http://ServerA/sayHello/wangmeng中的ServerA給替換成請求的ip數據呢?

接着我們繼續看RibbonLoadBalancerClient.execute()方法,這個里面request.apply()會做一個serverName的替換邏輯。

最后可以一步步跟到RibbonLoadBalancerClient.reconstructURI(),這個方法是把請求自帶的getURI方法給替換了,我們最后查看context.reconstructURIWithServer() 方法,debug結果如圖,這個里面會一步步把對應的請求url給拼接起來:

image.png

Ribbon中ping機制原理

我們知道 Ribbon還有一個重要的組件就是ping機制,通過上一講Ribbon的初始化我們知道,默認的IPing實現類為:NIWSDiscoveryPing,我們可以查看其中的isAlive()方法:

public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
            
        BaseLoadBalancer lb = null; 
        

        public NIWSDiscoveryPing() {
        }
        
        public BaseLoadBalancer getLb() {
            return lb;
        }

        /**
         * Non IPing interface method - only set this if you care about the "newServers Feature"
         * @param lb
         */
        public void setLb(BaseLoadBalancer lb) {
            this.lb = lb;
        }

        public boolean isAlive(Server server) {
            boolean isAlive = true;
            if (server!=null && server instanceof DiscoveryEnabledServer){
                DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;                
                InstanceInfo instanceInfo = dServer.getInstanceInfo();
                if (instanceInfo!=null){                    
                    InstanceStatus status = instanceInfo.getStatus();
                    if (status!=null){
                        isAlive = status.equals(InstanceStatus.UP);
                    }
                }
            }
            return isAlive;
        }

        @Override
        public void initWithNiwsConfig(
                IClientConfig clientConfig) {
        }
        
}

這里就是獲取到DiscoveryEnabledServer對應的注冊信息是否為UP狀態。那么 既然有個ping的方法,肯定會有方法進行調度的。

我們可以查看isAlive()調用即可以找到調度的地方。
BaseLoadBalancer構造函數中會調用setupPingTask()方法,進行調度:

protected int pingIntervalSeconds = 10;

void setupPingTask() {
    if (canSkipPing()) {
        return;
    }
    if (lbTimer != null) {
        lbTimer.cancel();
    }
    lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
            true);
    lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
    forceQuickPing();
}

這里pingIntervalSecondsBaseLoadBalancer中定義的是10s,但是在initWithConfig()方法中,通過傳入的時間覆蓋了原本的10s,這里實際的默認時間是30s。如下代碼:

image.png

我們也可以通過debug來看看:

image.png

可能大家好奇為什么要單獨截圖來說這個事,其實是因為網上好多博客講解都是錯的,都寫的是ping默認調度時間為10s,想必他們都是沒有真正debug過吧。

image.png

還是那句話,源碼出真知。

Ribbon中其他IRule負載算法初探

  1. RoundRobinRule:系統內置的默認負載均衡規范,直接round robin輪詢,從一堆server list中,不斷的輪詢選擇出來一個server,每個server平攤到的這個請求,基本上是平均的

    這個算法,說白了是輪詢,就是一台接着一台去請求,是按照順序來的

  2. AvailabilityFilteringRule:這個rule就是會考察服務器的可用性

    如果3次連接失敗,就會等待30秒后再次訪問;如果不斷失敗,那么等待時間會不斷變長
    如果某個服務器的並發請求太高了,那么會繞過去,不再訪問

    這里先用round robin算法,輪詢依次選擇一台server,如果判斷這個server是存活的可用的,如果這台server是不可以訪問的,那么就用round robin算法再次選擇下一台server,依次循環往復,10次。

  3. WeightedResponseTimeRule:帶着權重的,每個服務器可以有權重,權重越高優先訪問,如果某個服務器響應時間比較長,那么權重就會降低,減少訪問

  4. ZoneAvoidanceRule:根據機房和服務器來進行負載均衡,說白了,就是機房的意思,看了源碼就是知道了,這個就是所謂的spring cloud ribbon環境中的默認的Rule

  5. BestAvailableRule:忽略那些請求失敗的服務器,然后盡量找並發比較低的服務器來請求

總結

到了這里 Ribbon相關的就結束了,對於Ribbon注冊表拉取及更新邏輯這里也梳理下,這里如果Ribbon保存的注冊表信息有宕機的情況,這里最多4分鍾才能感知到,所以spring cloud還有一個服務熔斷的機制,這個后面也會講到。

07_Ribbon默認IRule可能存在的問題.jpg

申明

本文章首發自本人博客:https://www.cnblogs.com/wang-meng 和公眾號:壹枝花算不算浪漫,如若轉載請標明來源!

感興趣的小伙伴可關注個人公眾號:壹枝花算不算浪漫

22.jpg


免責聲明!

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



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