1. 程式人生 > >Dubbo 路由機制的實現

Dubbo 路由機制的實現

Dubbo 路由機制是在服務間的呼叫時,通過將服務提供者按照設定的路由規則來決定呼叫哪一個具體的服務。

路由服務結構

Dubbo 實現路由都是通過實現 RouterFactory 介面。當前版本 dubbo-2.7.5 實現該介面類如下:

路由實現工廠類是在 router 包下

由於 RouterFactory 是 SPI 介面,同時在獲取路由 RouterFactory#getRouter 方法上有 @Adaptive("protocol") 註解,所以在獲取路由的時候會動態呼叫需要的工廠類。

可以看到 getRouter 方法返回的是一個 Router 介面,該介面資訊如下

其中 Router#route 是服務路由的入口,對於不同型別的路由工廠,有特定的 Router 實現類。

以上就是通過解析 URL,獲取到具體的 Router,通過呼叫 Router#router 過濾出符合當前路由規則的 invokers。

服務路由實現

上面展示了路由實現類,這幾個實現型別中,ConditionRouter 條件路由是最為常用的型別,由於文章篇幅有限,本文就不對全部的路由型別逐一分析,只對條件路由進行具體分析,只要弄懂這一個型別,其它型別的解析就能容易掌握。

條件路由引數規則

在分析條件路由前,先了解條件路由的引數配置,官方文件如下:

條件路由規則內容如下:

條件路由實現分析

分析路由實現,主要分析工廠類的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。

ConditionRouterFactory#getRouter

ConditionRouterFactory 中通過建立 ConditionRouter 物件來初始化解析相關引數配置。

在 ConditionRouter 建構函式中,從 URL 裡獲取 rule 的字串格式的規則,解析規則在 ConditionRouter#init 初始化方法中。

public void init(String rule) {
    try {
        if (rule == null || rule.trim().length() == 0) {
            throw new IllegalArgumentException("Illegal route rule!");
        }
        // 去掉 consumer. 和 provider. 的標識
        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: It should be determined on the business level whether the `When condition` can be empty or not.
        this.whenCondition = when;
        this.thenCondition = then;
    } catch (ParseException e) {
        throw new IllegalStateException(e.getMessage(), e);
    }
}

以路由規則字串中的=>為分隔符,將消費者匹配條件和提供者匹配條件分割,解析兩個路由規則後,賦值給當前物件的變數。

呼叫 parseRule 方法來解析消費者和服務者路由規則。

// 正則驗證路由規則
protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");


private static Map<String, MatchPair> parseRule(String rule)
        throws ParseException {
    /**
     * 條件變數和條件變數值的對映關係
     * 比如 host => 127.0.0.1 則儲存著 host 和 127.0.0.1 的對映關係
    */
    Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
    if (StringUtils.isBlank(rule)) {
        return condition;
    }
    // Key-Value pair, stores both match and mismatch conditions
    MatchPair pair = null;
    // Multiple values
    Set<String> values = null;
    final Matcher matcher = ROUTE_PATTERN.matcher(rule);
    while (matcher.find()) { 
        // 獲取正則前部分匹配(第一個括號)的內容
        String separator = matcher.group(1);
        // 獲取正則後部分匹配(第二個括號)的內容
        String content = matcher.group(2);
        // 如果獲取前部分為空,則表示規則開始位置,則當前 content 必為條件變數
        if (StringUtils.isEmpty(separator)) {
            pair = new MatchPair();
            condition.put(content, pair);
        }
        // 如果分隔符是 &,則 content 為條件變數
        else if ("&".equals(separator)) {
            // 當前 content 是條件變數,用來做對映集合的 key 的,如果沒有則新增一個元素
            if (condition.get(content) == null) {
                pair = new MatchPair();
                condition.put(content, pair);
            } else {
                pair = condition.get(content);
            }
        }
        // 如果當前分割符是 = ,則當前 content 為條件變數值
        else if ("=".equals(separator)) {
            if (pair == null) {
                throw new ParseException("Illegal route rule \""
                        + rule + "\", The error char '" + separator
                        + "' at index " + matcher.start() + " before \""
                        + content + "\".", matcher.start());
            }
            // 由於 pair 還沒有被重新初始化,所以還是上一個條件變數的物件,所以可以將當前條件變數值在引用物件上賦值
            values = pair.matches;
            values.add(content);
        }
        // 如果當前分割符是 = ,則當前 content 也是條件變數值
        else if ("!=".equals(separator)) {
            if (pair == null) {
                throw new ParseException("Illegal route rule \""
                        + rule + "\", The error char '" + separator
                        + "' at index " + matcher.start() + " before \""
                        + content + "\".", matcher.start());
            }
            // 與 = 時同理
            values = pair.mismatches;
            values.add(content);
        }
        // 如果當前分割符為 ',',則當前 content 也為條件變數值
        else if (",".equals(separator)) { // Should be separated by ','
            if (values == null || values.isEmpty()) {
                throw new ParseException("Illegal route rule \""
                        + rule + "\", The error char '" + separator
                        + "' at index " + matcher.start() + " before \""
                        + content + "\".", matcher.start());
            }
            // 直接向條件變數值集合中新增資料
            values.add(content);
        } else {
            throw new ParseException("Illegal route rule \"" + rule
                    + "\", The error char '" + separator + "' at index "
                    + matcher.start() + " before \"" + content + "\".", matcher.start());
        }
    }
    return condition;
}

上面就是解析條件路由規則的過程,條件變數的值都儲存在 MatchPair 中的 matches、mismatches 屬性中,=,的條件變數值放在可以匹配的 matches 中,!=的條件變數值放在不可匹配路由規則的 mismatches 中。賦值過程中,程式碼還是比較優雅。

實際上 matches、mismatches 就是儲存的是條件變數值。

ConditionRouter#route

Router#route的作用就是匹配出符合路由規則的 Invoker 集合。

// 在初始化中進行被複制的變數
// 消費者條件匹配規則
protected Map<String, MatchPair> whenCondition;
// 提供者條件匹配規則
protected Map<String, MatchPair> thenCondition;


public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
        throws RpcException {
    if (!enabled) {
        return invokers;
    }
    // 驗證 invokers 是否為空
    if (CollectionUtils.isEmpty(invokers)) {
        return invokers;
    }
    try {
        // 校驗消費者是否有規則匹配,如果沒有則返回傳入的 Invoker
        if (!matchWhen(url, invocation)) {
            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;
        }
        // 遍歷傳入的 invokers,匹配提供者是否有規則匹配
        for (Invoker<T> invoker : invokers) {
            if (matchThen(invoker.getUrl(), url)) {
                result.add(invoker);
            }
        }
        // 如果 result 不為空,或當前物件 force=true 則返回 result 的 Invoker 列表 
        if (!result.isEmpty()) {
            return result;
        } else if (force) {
            logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
            return result;
        }
    } catch (Throwable t) {
        logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
    }
    return invokers;
}

上面程式碼可以看到,只要消費者沒有匹配的規則或提供者沒有匹配的規則及 force=false 時,不會返回傳入的引數的 Invoker。

匹配消費者路由規則和提供者路由規則方法是 matchWhen 和 matchThen

這兩個匹配方法都是呼叫同一個方法 matchCondition 實現的。將消費者或提供者 URL 轉為 Map,然後與 whenCondition 或 thenCondition 進行匹配。

匹配過程中,如果 key (即 sampleValue 值)存在對應的值,則通過 MatchPair#isMatch 方法再進行匹配。

private boolean isMatch(String value, URL param) {
    // 存在可匹配的規則,不存在不可匹配的規則
    if (!matches.isEmpty() && mismatches.isEmpty()) {
        // 不可匹配的規則列表為空時,只要可匹配的規則匹配上,直接返回 true
        for (String match : matches) {
            if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                return true;
            }
        }
        return false;
    }
    // 存在不可匹配的規則,不存在可匹配的規則
    if (!mismatches.isEmpty() && matches.isEmpty()) {
        // 不可匹配的規則列表中存在,則返回false
        for (String mismatch : mismatches) {
            if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                return false;
            }
        }
        return true;
    }
    // 存在可匹配的規則,也存在不可匹配的規則
    if (!matches.isEmpty() && !mismatches.isEmpty()) {
        // 都不為空時,不可匹配的規則列表中存在,則返回 false
        for (String mismatch : mismatches) {
            if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {
                return false;
            }
        }
        for (String match : matches) {
            if (UrlUtils.isMatchGlobPattern(match, value, param)) {
                return true;
            }
        }
        return false;
    }
    // 最後剩下的是 可匹配規則和不可匹配規則都為空時
    return false;
}

匹配過程再呼叫 UrlUtils#isMatchGlobPattern 實現

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {
    // 如果以 $ 開頭,則獲取 URL 中對應的值
    if (param != null && pattern.startsWith("$")) {
        pattern = param.getRawParameter(pattern.substring(1));
    }
    // 
    return isMatchGlobPattern(pattern, value);
}



public static boolean isMatchGlobPattern(String pattern, String value) {
    if ("*".equals(pattern)) {
        return true;
    }
    if (StringUtils.isEmpty(pattern) && StringUtils.isEmpty(value)) {
        return true;
    }
    if (StringUtils.isEmpty(pattern) || StringUtils.isEmpty(value)) {
        return false;
    }
    // 獲取萬用字元位置
    int i = pattern.lastIndexOf('*');
    // 如果value中沒有 "*" 萬用字元,則整個字串值匹配
    if (i == -1) {
        return value.equals(pattern);
    }
    // 如果 "*" 在最後面,則匹配字串 "*" 之前的字串即可
    else if (i == pattern.length() - 1) {
        return value.startsWith(pattern.substring(0, i));
    }
    // 如果 "*" 在最前面,則匹配字串 "*" 之後的字串即可
    else if (i == 0) {
        return value.endsWith(pattern.substring(i + 1));
    }
    // 如果 "*" 不在字串兩端,則同時匹配字串 "*" 左右兩邊的字串
    else {
        String prefix = pattern.substring(0, i);
        String suffix = pattern.substring(i + 1);
        return value.startsWith(prefix) && value.endsWith(suffix);
    }
}

就這樣完成全部的條件路由規則匹配,雖然看似程式碼較為繁雜,但是理清規則、思路,一步一步還是較好解析,前提是要熟悉相關引數的用法及形式,不然程式碼較難理解。

最後

單純從邏輯上,如果能夠掌握條件路由的實現,去研究其它方式的路由實現,相信不會有太大問題。只是例如像指令碼路由的實現,你得先會使用指令碼執行引擎為前提,不然就不理解它的程式碼。最後,在 dubbo-admin 上可以設定路由,大家可以嘗試各種使用規則,通過實操才能更好掌握和理解路由機制的實現。

個人部落格: https://ytao.top
關注公眾號 【ytao】,更多原創好文

相關推薦

Dubbo 路由機制實現

Dubbo 路由機制是在服務間的呼叫時,通過將服務提供者按照設定的路由規則來決定呼叫哪一個具體的服務。 路由服務結構 Dubbo 實現路由都是通過實現 RouterFactory 介面。當前版本 dubbo-2.7.5 實現該介面類如下: 路由實現工廠類是在 router 包下 由於 Router

dubbo的TpsLimitFilter限流,說說dubbo的Filter實現機制

Dubbo提供了過程攔截(即Filter)功能。dubbo的大多數功能都基於此功能實現。在dubbo的服務端,提供了一個限流Filter(TpsLimitFilter),用於在服務端控制單位時間內(預設是60s)的呼叫數量tps。超過此數量,則服務端將會報錯。 一、TpsLimitF

dubbo spi擴充套件實現機制javassist

Dubbo為了實現基於spi思想的擴充套件特性,特別是能夠靈活新增額外功能,要能夠動態生成一個叫做控制或適配並實現擴充套件或策略選擇功能的類。當然對應已知需求如Protocol, ProxyFactory他們的策略選擇的適配類程式碼dubbo直接提供也無妨,但是dubbo作為

C#進階系列——WebApi 路由機制剖析:你準備好了嗎?

事先 blank path can tex 全局配置 dex 找不到 save 前言:從MVC到WebApi,路由機制一直是伴隨著這些技術的一個重要組成部分。 它可以很簡單:如果你僅僅只需要會用一些簡單的路由,如/Home/Index,那麽你只需要配置一個默認路由就能簡

利用JAVA反射機制實現調用私有方法

parse try ble cat 權限 利用 enabled tde mod 1.fragment是AccessibilityFragment的對象。須要被調用的方法的類。 setAccessible(true)並非將方法的訪問權限改成了public。而是取

oracle仿全文檢索切詞機制實現文本信息類似度查找

pos rom 排除 應用場景 popu ora mar 機制 一個 應用場景: 依據keyword查詢與此keyword相似的信息,當中一些keyword要排除掉比如:“有限公司”、“有限責任公司”、“股份有限公司”等

History路由機制

出棧 歷史 json del page add 路由 itl location 用戶訪問網頁的歷史記錄通常會被保存在一個類似於棧對象中,即history對象,點擊返回就出棧,跳下一頁就入棧。 它提供了一些方法來操作頁面的前進和後退: window.history.bac

php利用ob緩存機制實現頁面靜態化方法全解

常用函數 http協議 一個 src names too req 文件是否存在 復制 首先介紹一下php中ob緩存常用到的幾個常用函數 ob_start():開啟緩存機制 ob_get_contents():獲取ob緩存中的內容 ob_clean()清除ob緩存中的內容,但

dubbo之泛化實現

att num 客戶端 測試框架 say -s urn pan keyword 實現泛化調用 泛化接口調用方式主要用於客戶端沒有 API 接口及模型類元的情況,參數及返回值中的所有 POJO 均用 Map 表示,通常用於框架集成,比如:實現一個通用的服務測試框架,可通過

14.linux-platform機制實現驅動層分離(詳解)

擴展性 blank 事件處理 相關 技術分享 消息 驅動 array iou 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 本節目標: 學習platform機制,如何實現驅動層分離 1.先來看看我們之前分析輸入子系統的分層概念

關於Android路由實現

ace uid space php andro spa pac .cn album http://pic.cnhubei.com/space.php?uid=1774&do=album&id=1349287http://pic.cnhubei.com/spa

java反射機制實現攔截器

tor 攔截 stat 重要 obj static interface 程序 bject 實現一個攔截器必須要實現一下幾個類: 1 目標類接口:目標類要實現的接口。 package com.lanvis.reflect; public interface ITarge

配置RIP路由協議實現不同路由之間的互通

change fff detect val input building start fig abi 實驗名稱:配置RIP路由協議實現不同路由之間的互通 實驗拓撲:實驗環境:1、四臺路由器,2、兩臺PC 0 IP: 192.168.1.1/24網關:192.168.1.25

配置浮動路由實現鏈路冗余

同時 code nag 配置浮動路由 優先級 端口 http tin 查看 配置浮動路由,實現鏈路冗余 一、創建實驗環境1、路由器R1\R2 交換機SW1\SW2 PC1\PC2#配置端口IP地址R1 #inte

默認路由配置實現全網互通。(華為)

ati oss abi code -o p地址 net add face 實驗目的:配置接口IP地址並通過靜態路由、默認路由配置實現全網互通。實驗步驟:1.先給PC配置不同網段的IP地址;2.配置路由器實現全網互通;配置命令:路由器AR1: <Huawei>s

利用java反射機制實現List<Map<String, Object>>轉化為List<JavaBean>

tis one row 註解 網上 span mybatis star mod 最近在用mybatis做項目,遇到將date格式化顯示到easyui的問題,需要將List<Map<String, Object>>轉化為List<JavaBean

linux 多網卡多路由實現策略路由

文件名 多網卡 路由規則 策略 關機 name show scripts ip地址 linux kernel 2.2 開始支持多個路由表。routing policy database (RPDB)。 傳統路由表,基於目標地址做路由選擇。通過多個路由表,kernel支持實施

(轉)Linux下通過rsync與inotify(異步文件系統事件監控機制)實現文件實時同步

-a 推送 root started init.d log tool mysql同步 .tar.gz Linux下通過rsync與inotify(異步文件系統事件監控機制)實現文件實時同步原文:http://www.summerspacestation.com/linux%

華為S9300策略路由配置實現分流

華為S9300策略路由配置實現分流華為S9300策略路由配置實現分流 需求描述:公司目前有兩條帶寬,一條固定專線,一條撥號的專線!重要的流量走固定,刁民走撥號!1.創建ACL規則//需要策略路由的源地址acl number 2000rule 10 permit source 172.16.115.57 0//