Ribbon的AvailabilityFilteringRule的坑(Spring Cloud Finchley.SR2)

如題,本文基於Spring Cloud Finchley.SR2

我們項目配置了AvailabilityFilteringRule作爲所有Ribbon調用的負載均衡規則,它有那些坑呢(理解歧義和注意點)?

首先來看源碼,核心是choose方法:

public Server choose(Object key) {
    int count = 0;
    //通過輪詢選擇一個server
    Server server = roundRobinRule.choose(key);
    //嘗試10次如果都不滿足要求,就放棄,採用父類的choose
    //這裏爲啥嘗試10次?
    //1. 輪詢結果相互影響,可能導致某個請求每次調用輪詢返回的都是同一個有問題的server
    //2. 集羣很大時,遍歷整個集羣判斷效率低,我們假設集羣中健康的實例要比不健康的多,如果10次找不到,就用父類的choose,這也是一種快速失敗機制
    while (count++ <= 10) {
        if (predicate.apply(new PredicateKey(server))) {
            return server;
        }
        server = roundRobinRule.choose(key);
    }
    return super.choose(key);
}

輪詢是怎麼輪詢呢,爲啥會相互影響?

來看下RoundRobinRule的源碼

//多線程輪詢算法
private int incrementAndGetModulo(int modulo) {
    for (;;) {
        //當前值
        int current = nextServerCyclicCounter.get();
        //新值,通過對於modulo(就是實例個數)取餘
        int next = (current + 1) % modulo;
        //只有設置成功才返回
        if (nextServerCyclicCounter.compareAndSet(current, next))
            return next;
    }
}

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

    Server server = null;
    int count = 0;
    //這裏也是10次,不遍歷整個集羣,防止一個請求執行過長時間在選server上,快速失敗
    while (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)) {
            log.warn("No up servers available from load balancer: " + lb);
            return null;
        }

        int nextServerIndex = incrementAndGetModulo(serverCount);
        server = allServers.get(nextServerIndex);

        if (server == null) {
            /* Transient. */
            Thread.yield();
            continue;
        }
        //判斷server狀態
        if (server.isAlive() && (server.isReadyToServe())) {
            return (server);
        }

        // Next.
        server = null;
    }

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

AvailabilityFilteringRule如何判斷Server滿足條件?

看下判斷類AvailabilityPredicate的源碼:

這裏涉及兩個配置:

  1. niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped,默認爲true,即是否過濾掉斷路的Server(什麼是斷路我們之後會說)
  2. niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit,默認爲Integer的最大值,每個Server實例最大的活躍連接數(其實就是本機發往這個Server未處理完的請求個數)
public boolean apply(@Nullable PredicateKey input) {
    LoadBalancerStats stats = getLBStats();
    if (stats == null) {
        return true;
    }
    //判斷是否滿足條件
    return !shouldSkipServer(stats.getSingleServerStat(input.getServer()));
}


private boolean shouldSkipServer(ServerStats stats) {        
    //niws.loadbalancer.availabilityFilteringRule.filterCircuitTripped是否爲true
    if ((CIRCUIT_BREAKER_FILTERING.get() &&
    //該Server是否爲斷路狀態
    stats.isCircuitBreakerTripped()) 
    //本機發往這個Server未處理完的請求個數是否大於Server實例最大的活躍連接數
            || stats.getActiveRequestsCount() >= activeConnectionsLimit.get()) {
        return true;
    }
    return false;
}

Server是否爲斷路狀態是如何判斷的呢?

ServerStats源碼,這裏詳細源碼我們不貼了,說一下機制:

斷路是通過時間判斷實現的。每次失敗記錄上次失敗時間。如果失敗了觸發判斷是否斷路的最小失敗次數以上的次數,則判斷:

  1. 計算斷路持續時間: (2^失敗次數)* 斷路時間因子,如果大於最大斷路時間,則取最大斷路時間
  2. 判斷當前時間是否大於上次失敗時間+短路持續時間,如果小於,則是斷路狀態

這裏又涉及三個配置(這裏需要將default替換成你調用的微服務名稱):

  1. niws.loadbalancer.default.connectionFailureCountThreshold,默認爲3, 觸發判斷是否斷路的最小失敗次數,也就是,默認如果失敗三次,就會判斷是否要斷路了。
  2. niws.loadbalancer.default.circuitTripTimeoutFactorSeconds, 默認爲10, 斷路時間因子,
  3. niws.loadbalancer.default.circuitTripMaxTimeoutSeconds,默認爲30,最大斷路時間

ServerStats如何更新呢?

首先是清空,根據我的另一系列文章對於Eureka源碼和配置的分析,每次在ribbon從eureka本地定時重新拉取server列表時,就會清空。這個配置是:

#eureka客戶端ribbon刷新時間
#默認30s
ribbon.ServerListRefreshInterval=1000

這裏我們配置是1秒,也就是1秒內如果斷路三次,就會觸發斷路判斷

然後是怎麼增加斷路次數?這裏我們看調用這個方法的源碼,有效調用裏面都有一個判斷:

if (lbContext.getRetryHandler().isCircuitTrippingException(throwable)) {
	//調用增加斷路次數
}

這個isCircuitTrippingException,對於默認的DefaultLoadBalancerRetryHandler就是判斷是否爲SocketException.class, SocketTimeoutException.class這兩個異常。如果是,就會記錄到斷路次數

SocketException.class, SocketTimeoutException.class兩個異常的坑與Ribbon連接超時時間

參考我另一篇文章,Ribbon對於SocketTimeOutException重試的坑以及重試代碼解析,這裏不要把Ribbon的連接超時設置太短,一般如下設置即可:

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