DUBBO服務治理

ailover與failfast的代碼基本同樣,主要區別就是出現異常以後直接忽略,而後返回空的RpcResult。其他的幾種集羣策略在平時用的比較少,我就很少介紹了,其實現也都比較簡單。
Dubbo的容錯不只體如今provider的cluster,對於註冊中心也有提供cluster內容(AvailableCluster),只不過該內容比較簡單,只是隨機選取了一個可用的註冊中心。算法

負載均衡bash

負載均衡的概念:從多個目標服務器中選擇出其中一個服務器供客戶端調用,這個選擇的具體過程就叫作負載均衡(純粹是本身給小白用戶的解釋)。
通常的服務性框架都會有負載均衡的內容,Dubbo也是基於本身的URL機制作了一層負載均衡,咱們看到上面的集羣內容時候就看到集羣內部其實就依賴了負載均衡策略來從多個Invoker中選取其中的一個,只不過一次負載所選擇到的Invoker並不必定能知足條件,好比在Failover策略下,失敗以後Loadbalance從新選擇的Invoker仍是失敗過的那個,那就要從新計算了。
Dubbo的負載均衡策略主要有如下幾種:服務器

  • Random LoadBalance 隨機選取服務提供者 最簡單的無狀態的負載均衡算法
  • RoundRobin LoadBalance 以輪訓的方式調用服務提供者 缺點是有狀態,必須在併發之下記住上一次到誰了
  • LeastActive LoadBalance 調用最少活躍調調用的服務提供者,這裏有一點須要注意,這裏的服務調用統計維度是在方法級別的,也就是說是方法級別的LoadBalance。
  • ConsistentHash LoadBalance 一致性Hash(用的很少,很少講解)

首先看下LoadBalance的接口就直到它是作什麼的:併發

//從invokers列表中根據url和invocation信息選出一個合適的Invoker供consumer端調用
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
複製代碼

在全部的LoadBalance中都提到了一個概念:weight。正常狀況下咱們不管配置在provider仍是service中,對應的全部服務端的providerUrl的weight都是同樣的,這種狀況其實weight配置不配置意義不大。可是一旦動態針對某個服務調整過weight值,這個影響就出來了。例如:override://10.20.153.10/com.foo.BarService?category=configurators&dynamic=false&weight=200 配置以後,就將10.20.153.10服務器上的com.foo.BarServic的權值改成了200,那麼它在以後的LoadBalance中被選取的可能性就更大了(默認的權值爲100)。app

Random LoadBalance負載均衡

private final Random random = new Random();
 
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // 總個數
        int totalWeight = 0; // 總權重
        boolean sameWeight = true; // 權重是否都同樣
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            totalWeight += weight; // 累計總權重
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                sameWeight = false; // 計算全部權重是否同樣
            }
        }
        if (totalWeight > 0 && ! sameWeight) {
            // 若是權重不相同且權重大於0則按總權重數隨機
            int offset = random.nextInt(totalWeight);
            // 隨機數落到哪一個片斷上,就取哪一個片斷對應的Invoker對象(擊鼓傳花式日後切割)
            for (int i = 0; i < length; i++) {
                offset -= getWeight(invokers.get(i), invocation);
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // 若是權重相同或權重爲0則均等隨機
        return invokers.get(random.nextInt(length));
    }
複製代碼

RoundRobin LoadBalance
框架

//輪訓是針對方法級別的,並非全部服務調用
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
        int length = invokers.size(); // 總個數
        int maxWeight = 0; // 最大權重
        int minWeight = Integer.MAX_VALUE; // 最小權重
        // invoker->weight,IntegerWrapper就是一個簡單的Integer包裝類
        final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
        int weightSum = 0;
        for (int i = 0; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            maxWeight = Math.max(maxWeight, weight); // 累計最大權重
            minWeight = Math.min(minWeight, weight); // 累計最小權重
            if (weight > 0) {
                invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
                weightSum += weight;
            }
        }
        AtomicPositiveInteger sequence = sequences.get(key);
        if (sequence == null) {
            sequences.putIfAbsent(key, new AtomicPositiveInteger());
            sequence = sequences.get(key);
        }
        //currentSequence表明某個方法是第多少次被調用的,例如第1W次
        int currentSequence = sequence.getAndIncrement();
        if (maxWeight > 0 && minWeight < maxWeight) { // 權重不同
            // 能夠把weightSum理解成一個權重範圍內的總集,mod就帶表在這個總集中具體執行到的位置
            int mod = currentSequence % weightSum;
            //weightSum < maxWeight*length
            for (int i = 0; i < maxWeight; i++) {
                for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                    final Invoker<T> k = each.getKey();
                    final IntegerWrapper v = each.getValue();
                    //這裏的邏輯比較抽象,本質上就是誰的權重越大,輪詢到誰的次數就越多
                    if (mod == 0 && v.getValue() > 0) {
                        return k;
                    }
                    if (v.getValue() > 0) {
                        v.decrement();
                        mod--;
                    }
                }
            }
        }
        // 取模輪循
        return invokers.get(currentSequence % length);
    }
複製代碼

簡單題下RoundRobin的弊端:若是某個服務有3臺服務器,權重依次是10,1000,100,可能配置的人的本意是在輪訓的時候走到三臺機器的比例是:1:100:10,可是實際上確實是1000個請求壓倒了第二臺機器上。。。dom

LeastActive LoadBalanceide

private final Random random = new Random();
 
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // 總個數
        int leastActive = -1; // 最小的活躍數
        int leastCount = 0; // 相同最小活躍數的個數
        int[] leastIndexs = new int[length]; // 相同最小活躍數的下標
        int totalWeight = 0; // 總權重
        int firstWeight = 0; // 第一個權重,用於於計算是否相同
        boolean sameWeight = true; // 是否全部權重相同
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // 活躍數
            int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 權重
            if (leastActive == -1 || active < leastActive) { // 若是是初始狀況下或者某臺機器的active數量小於如今保存的leastActive數量,就會從新開始
                leastActive = active; // 記錄最小活躍數
                leastCount = 1; // 從新統計相同最小活躍數的個數
                leastIndexs[0] = i; // 從新記錄最小活躍數下標
                totalWeight = weight; // 從新累計總權重
                firstWeight = weight; // 記錄第一個權重
                sameWeight = true; // 還原權重相同標識
            } else if (active == leastActive) { // 累計相同最小的活躍數
                leastIndexs[leastCount ++] = i; // 累計相同最小活躍數下標
                totalWeight += weight; // 累計總權重
                // 判斷全部權重是否同樣
                if (sameWeight && i > 0 
                        && weight != firstWeight) {
                    sameWeight = false;
                }
            }
        }
        // 若是隻有一個最小值的話就直接調用
        if (leastCount == 1) {
            // 若是隻有一個最小則直接返回
            return invokers.get(leastIndexs[0]);
        }
        if (! sameWeight && totalWeight > 0) {
            // 若是權重不相同且權重大於0則按總權重數隨機
            int offsetWeight = random.nextInt(totalWeight);
            // 並肯定隨機值落在哪一個片段上
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexs[i];
                offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
                if (offsetWeight <= 0)
                    return invokers.get(leastIndex);
            }
        }
        // 若是權重相同或權重爲0則均等隨機
        return invokers.get(leastIndexs[random.nextInt(leastCount)]);
複製代碼

一致性哈希對應的LoadBalance本次不講解。
我我的對於Dubbo 提供的LoadBalance的見解是:基本上知足平常使用,可是應該更加豐富。由於咱們平常使用的是leastactive,可是由於該負載均衡策略是基於方法級別的,因此沒法控制其餘的方法對於應用的影響,這裏若是將統計的維度上升到機器緯度,其實能夠作到相似於整個集羣的leastactive,這樣的話就不容易出現部分幾臺機器負載特別高,而其他的大部分機器都有不少資源結餘。固然也能夠提供接口緯度的負載均衡,這個徹底能夠根據具體的業務實現定值,由於SPI機制的緣故,自定義負載均衡策略實現起來仍是比較方便的。函數

路由

說到路由仍是貼一張官方的圖會比較好理解一些


從上圖中能看出來router的做用實際上是在LB以前的,也就是說LB的入參其實就是router的結果。
由於router可能理解起來並不直觀,所以仍是大體介紹一下router的含義。
在平常的服務這裏過程當中,好比我想給某個接口開個特權,專門設置一些提供者只供其調用;讀寫分離,讀操做配置專門的機器,寫操做配置專門的機器;某個消費者有問題,想及時下掉等等。都是能夠經過router來實現的,明白了router的具體含義以後咱們來一塊兒看一下router的實現:
首先爲了明確路由的基本規則,把官方的案例拿過來一塊兒看一下:

服務調用信息,如:method, argument 等 (暫不支持參數路由) URL自己的字段,如:protocol, host, port 等 以及URL上的全部參數,如:application, organization 等
條件支持:
等號"="表示"匹配",如:host = 10.20.153.10 不等號"!="表示"不匹配",如:host != 10.20.153.10
值支持:
以逗號","分隔多個值,如:host != 10.20.153.10,10.20.153.11 以星號"

"結尾,表示通配,如:host != 10.20.

以美圓符"$"開頭,表示引用消費者參數,如:host = $host

下面以ConditionRouter爲例來看一下路由規則的具體實現

//構造函數初始化的時候難點在於when和then的初始化:
    public ConditionRouter(URL url) {
        this.url = url;
        //路由規則的優先級,用於排序,優先級越大越靠前執行,可不填,缺省爲0。
        this.priority = url.getParameter(Constants.PRIORITY_KEY, 0);
        //當路由結果爲空時,是否強制執行,若是不強制執行,路由結果爲空的路由規則將自動失效,可不填,缺省爲flase
        this.force = url.getParameter(Constants.FORCE_KEY, false);
        try {
            String rule = url.getParameterAndDecoded(Constants.RULE_KEY);
            if (rule == null || rule.trim().length() == 0) {
                throw new IllegalArgumentException("Illegal route rule!");
            }
            rule = rule.replace("consumer.", "").replace("provider.", "");
            int i = rule.indexOf("=>");
            // =>前的部分
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            // =>後的部分
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            // NOTE: When條件是容許爲空的,外部業務來保證相似的約束條件
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    
   
    /**
     * 將對應的rule信息解析爲對應的MatchPair
     * host=10.20.153.10解析出來就是一個host:MatchPair,Matcher的matches內容爲10.20.153.10
     * host!=10.20.153.10解析出來就是一個host:MatchPair,Matcher的mismatches內容爲10.20.153.10
     * 能夠理解爲MatcherPair就是區分matches和mismatches的具體聚合類,拿到這個Matcher就拿到表達式初步解析後的數據
     * @param rule
     * @return
     * @throws ParseException
     */
    private static Map<String, MatchPair> parseRule(String rule)
            throws ParseException {
        Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
        if(StringUtils.isBlank(rule)) {
            return condition;
        }        
        // 匹配或不匹配Key-Value對
        MatchPair pair = null;
        // 多個Value值
        Set<String> values = null;
        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
        //例如:host=10.20.153.10 第一次匹配的group1='',group2='host',第二次匹配的group1='=',group2='10.20.153.10'
        while (matcher.find()) { // 逐個匹配
            String separator = matcher.group(1);
            String content = matcher.group(2);
            // 表達式開始
            if (separator == null || separator.length() == 0) {
                pair = new MatchPair();
                //'host':new MatchPair()
                condition.put(content, pair);
            }
            // &符號尚未遇到過
            else if ("&".equals(separator)) {
                if (condition.get(content) == null) {
                    pair = new MatchPair();
                    condition.put(content, pair);
                } else {
                    condition.put(content, pair);
                }
            }
            // 匹配=號部分
            else if ("=".equals(separator)) {
                if (pair == null)
                    throw new RuntimeException();
                values = pair.matches;
                values.add(content);
            }
            // 匹配!=號部分
            else if ("!=".equals(separator)) {
                if (pair == null)
                    throw new RuntimeException();
                values = pair.mismatches;
                values.add(content);
            }
            // ,號直接跟着前面的=或者!=走
            else if (",".equals(separator)) {
                if (values == null || values.size() == 0)
                    throw new RuntimeException();
                values.add(content);
            } else {
                throw new RuntimeException();
            }
        }
        return condition;
    }
    
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
        if (invokers == null || invokers.size() == 0) {
            return invokers;
        }
        try {
            //若是沒有匹配到的前置條件後直接返回,意思就是當前做用的consumer信息不須要通過路由操做
            // 若是路由的配置值有$開頭的話就將其替換爲URL中對應的key的value
            if (! matchWhen(url)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            //黑名單
            if (thenCondition == null) {
                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
                return result;
            }
            for (Invoker<T> invoker : invokers) {
                //逐個匹配後置條件
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            if (result.size() > 0) {
                return result;
                //感受強制執行的話返回一個空的List並無卵用呀
            } else if (force) {
                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(Constants.RULE_KEY));
                return result;
            }
        } catch (Throwable t) {
            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
        }
        return invokers;
    }
複製代碼

想了解更多詳細資料:1903832579

相關文章
相關標籤/搜索