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

前言

前情回顧

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

本講目錄

咱們知道Ribbon主要是由3個組件組成的:算法

  1. ILoadBalancer
  2. IRule
  3. IPing

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

目錄以下:服務器

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

說明

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

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

源碼分析

負載均衡默認Server選擇邏輯

還記得咱們上一講說過,在Ribbon初始化過程當中,默認的IRuleZoneAvoidanceRule,這裏咱們能夠經過debug看看,從RibbonLoadBalancerClient.getServer() 一路往下跟,這裏直接看debug結果:app

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()方法,繼續日後跟:dom

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中採用的是同樣的算法,這個算法你們能夠品一品,我這裏也會畫個圖來講明下:ide

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

相關文章
相關標籤/搜索