0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Dubbo通過RouterFactory接口實(shí)現(xiàn)路由機(jī)制服務(wù)

電子設(shè)計(jì) ? 來源:博客園 ? 作者:佚名 ? 2020-04-16 07:43 ? 次閱讀

Dubbo路由機(jī)制是在服務(wù)間的調(diào)用時(shí),通過將服務(wù)提供者按照設(shè)定的路由規(guī)則來決定調(diào)用哪一個(gè)具體的服務(wù)。

路由服務(wù)結(jié)構(gòu)

Dubbo實(shí)現(xiàn)路由都是通過實(shí)現(xiàn)RouterFactory接口。當(dāng)前版本 dubbo-2.7.5 實(shí)現(xiàn)該接口類如下:

路由實(shí)現(xiàn)工廠類是在 router 包下

由于 RouterFactory 是 SPI 接口,同時(shí)在獲取路由 RouterFactory#getRouter 方法上有 @Adaptive(“protocol”) 注解,所以在獲取路由的時(shí)候會(huì)動(dòng)態(tài)調(diào)用需要的工廠類。

可以看到 getRouter 方法返回的是一個(gè) Router 接口,該接口信息如下

其中 Router#route 是服務(wù)路由的入口,對于不同類型的路由工廠,有特定的 Router 實(shí)現(xiàn)類。

以上就是通過解析 URL,獲取到具體的 Router,通過調(diào)用 Router#router 過濾出符合當(dāng)前路由規(guī)則的 invokers。

服務(wù)路由實(shí)現(xiàn)

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

條件路由參數(shù)規(guī)則

在分析條件路由前,先了解條件路由的參數(shù)配置,官方文檔如下:

條件路由規(guī)則內(nèi)容如下:

條件路由實(shí)現(xiàn)分析

分析路由實(shí)現(xiàn),主要分析工廠類的 xxxRouterFactory#getRouter 和 xxxRouter#route 方法。

ConditionRouterFactory#getRouter

ConditionRouterFactory 中通過創(chuàng)建 ConditionRouter 對象來初始化解析相關(guān)參數(shù)配置。

在 ConditionRouter 構(gòu)造函數(shù)中,從 URL 里獲取 rule 的字符串格式的規(guī)則,解析規(guī)則在 ConditionRouter#init 初始化方法中。

public void init(String rule) {

try {

if (rule == null || rule.trim().length() == 0) {

throw new IllegalArgumentException(“Illegal route rule!”);

}

// 去掉 consumer. 和 provider. 的標(biāo)識

rule = rule.replace(“consumer.”, “”).replace(“provider.”, “”);

// 獲取 消費(fèi)者匹配條件 和 提供者地址匹配條件 的分隔符

int i = rule.indexOf(“=》”);

// 消費(fèi)者匹配條件

String whenRule = i 《 0 ? null : rule.substring(0, i).trim();

// 提供者地址匹配條件

String thenRule = i 《 0 ? rule.trim() : rule.substring(i + 2).trim();

// 解析消費(fèi)者路由規(guī)則

Map《String, MatchPair》 when = StringUtils.isBlank(whenRule) || “true”.equals(whenRule) ? new HashMap《String, MatchPair》() : parseRule(whenRule);

// 解析提供者路由規(guī)則

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);

}

}

以路由規(guī)則字符串中的=》為分隔符,將消費(fèi)者匹配條件和提供者匹配條件分割,解析兩個(gè)路由規(guī)則后,賦值給當(dāng)前對象的變量。

調(diào)用 parseRule 方法來解析消費(fèi)者和服務(wù)者路由規(guī)則。

// 正則驗(yàn)證路由規(guī)則

protected static final Pattern ROUTE_PATTERN = Pattern.compile(“([&!=,]*)\s*([^&!=,\s]+)”);

private static Map《String, MatchPair》 parseRule(String rule)

throws ParseException {

/**

* 條件變量和條件變量值的映射關(guān)系

* 比如 host =》 127.0.0.1 則保存著 host 和 127.0.0.1 的映射關(guān)系

*/

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()) {

// 獲取正則前部分匹配(第一個(gè)括號)的內(nèi)容

String separator = matcher.group(1);

// 獲取正則后部分匹配(第二個(gè)括號)的內(nèi)容

String content = matcher.group(2);

// 如果獲取前部分為空,則表示規(guī)則開始位置,則當(dāng)前 content 必為條件變量

if (StringUtils.isEmpty(separator)) {

pair = new MatchPair();

condition.put(content, pair);

}

// 如果分隔符是 &,則 content 為條件變量

else if (“&”.equals(separator)) {

// 當(dāng)前 content 是條件變量,用來做映射集合的 key 的,如果沒有則添加一個(gè)元素

if (condition.get(content) == null) {

pair = new MatchPair();

condition.put(content, pair);

} else {

pair = condition.get(content);

}

}

// 如果當(dāng)前分割符是 = ,則當(dāng)前 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 還沒有被重新初始化,所以還是上一個(gè)條件變量的對象,所以可以將當(dāng)前條件變量值在引用對象上賦值

values = pair.matches;

values.add(content);

}

// 如果當(dāng)前分割符是 = ,則當(dāng)前 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());

}

// 與 = 時(shí)同理

values = pair.mismatches;

values.add(content);

}

// 如果當(dāng)前分割符為 ‘,’,則當(dāng)前 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());

}

// 直接向條件變量值集合中添加數(shù)據(jù)

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;

}

上面就是解析條件路由規(guī)則的過程,條件變量的值都保存在 MatchPair 中的 matches、mismatches 屬性中,=和,的條件變量值放在可以匹配的 matches 中,!=的條件變量值放在不可匹配路由規(guī)則的 mismatches 中。賦值過程中,代碼還是比較優(yōu)雅。

實(shí)際上 matches、mismatches 就是保存的是條件變量值。

ConditionRouter#route

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

// 在初始化中進(jìn)行被復(fù)制的變量

// 消費(fèi)者條件匹配規(guī)則

protected Map《String, MatchPair》 whenCondition;

// 提供者條件匹配規(guī)則

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;

}

// 驗(yàn)證 invokers 是否為空

if (CollectionUtils.isEmpty(invokers)) {

return invokers;

}

try {

// 校驗(yàn)消費(fèi)者是否有規(guī)則匹配,如果沒有則返回傳入的 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,匹配提供者是否有規(guī)則匹配

for (Invoker《T》 invoker : invokers) {

if (matchThen(invoker.getUrl(), url)) {

result.add(invoker);

}

}

// 如果 result 不為空,或當(dāng)前對象 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;

}

上面代碼可以看到,只要消費(fèi)者沒有匹配的規(guī)則或提供者沒有匹配的規(guī)則及 force=false 時(shí),不會(huì)返回傳入的參數(shù)的 Invoker。

匹配消費(fèi)者路由規(guī)則和提供者路由規(guī)則方法是 matchWhen 和 matchThen

這兩個(gè)匹配方法都是調(diào)用同一個(gè)方法 matchCondition 實(shí)現(xiàn)的。將消費(fèi)者或提供者 URL 轉(zhuǎn)為 Map,然后與 whenCondition 或 thenCondition 進(jìn)行匹配。

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

private boolean isMatch(String value, URL param) {

// 存在可匹配的規(guī)則,不存在不可匹配的規(guī)則

if (!matches.isEmpty() && mismatches.isEmpty()) {

// 不可匹配的規(guī)則列表為空時(shí),只要可匹配的規(guī)則匹配上,直接返回 true

for (String match : matches) {

if (UrlUtils.isMatchGlobPattern(match, value, param)) {

return true;

}

}

return false;

}

// 存在不可匹配的規(guī)則,不存在可匹配的規(guī)則

if (!mismatches.isEmpty() && matches.isEmpty()) {

// 不可匹配的規(guī)則列表中存在,則返回false

for (String mismatch : mismatches) {

if (UrlUtils.isMatchGlobPattern(mismatch, value, param)) {

return false;

}

}

return true;

}

// 存在可匹配的規(guī)則,也存在不可匹配的規(guī)則

if (!matches.isEmpty() && !mismatches.isEmpty()) {

// 都不為空時(shí),不可匹配的規(guī)則列表中存在,則返回 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;

}

// 最后剩下的是 可匹配規(guī)則和不可匹配規(guī)則都為空時(shí)

return false;

}

匹配過程再調(diào)用 UrlUtils#isMatchGlobPattern 實(shí)現(xiàn)

public static boolean isMatchGlobPattern(String pattern, String value, URL param) {

// 如果以 $ 開頭,則獲取 URL 中對應(yīng)的值

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中沒有 “*” 通配符,則整個(gè)字符串值匹配

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));

}

// 如果 “*” 不在字符串兩端,則同時(shí)匹配字符串 “*” 左右兩邊的字符串

else {

String prefix = pattern.substring(0, i);

String suffix = pattern.substring(i + 1);

return value.startsWith(prefix) && value.endsWith(suffix);

}

}

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

最后

單純從邏輯上,如果能夠掌握條件路由的實(shí)現(xiàn),去研究其它方式的路由實(shí)現(xiàn),相信不會(huì)有太大問題。只是例如像腳本路由的實(shí)現(xiàn),你得先會(huì)使用腳本執(zhí)行引擎為前提,不然就不理解它的代碼。最后,在 dubbo-admin 上可以設(shè)置路由,大家可以嘗試各種使用規(guī)則,通過實(shí)操才能更好掌握和理解路由機(jī)制的實(shí)現(xiàn)。

責(zé)任編輯:gt


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8673

    瀏覽量

    151554
  • 路由
    +關(guān)注

    關(guān)注

    0

    文章

    278

    瀏覽量

    41883
收藏 人收藏

    評論

    相關(guān)推薦

    聊聊Dubbo - Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)

    的做到了上面兩點(diǎn)。這要得益于Dubbo的微內(nèi)核+插件的機(jī)制。接下來的章節(jié)中我們會(huì)慢慢揭開Dubbo擴(kuò)展機(jī)制的神秘面紗。2. 可擴(kuò)展的幾種解決方案通??蓴U(kuò)展的
    發(fā)表于 06-04 17:33

    聊聊Dubbo - Dubbo可擴(kuò)展機(jī)制源碼解析

    摘要: 在Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)中,我們了解了Dubbo擴(kuò)展機(jī)制的一些概念,初探了Dubbo中LoadBalance的
    發(fā)表于 06-05 18:43

    Dubbo開源現(xiàn)狀與未來規(guī)劃

    循環(huán)依賴和反向依賴的出現(xiàn)。Dubbo 還有一個(gè)很重要的設(shè)計(jì)哲學(xué)就是平等對待第三方的擴(kuò)展,即 Dubbo 內(nèi)建的功能也是通過同樣的擴(kuò)展機(jī)制提供出來的,第三方的擴(kuò)展和內(nèi)建功能可以相互取代。
    發(fā)表于 07-05 15:21

    攜程的 Dubbo 之路

    用戶自行擴(kuò)展,我們在PB序列化器的實(shí)現(xiàn)上增加了擴(kuò)展接口,允許用戶在外圍繼續(xù)增加數(shù)據(jù)壓縮的功能。整體序列化器的實(shí)現(xiàn)并不是很難,倒是有一點(diǎn)需要注意的是,由于 Dubbo
    發(fā)表于 10-12 15:05

    基于FPGA的VGA接口實(shí)現(xiàn)和字符顯示

    基于FPGA的VGA接口實(shí)現(xiàn)和字符顯示論文
    發(fā)表于 10-29 17:18 ?8次下載

    如何通過STM32的串口實(shí)現(xiàn)簡易脫機(jī)編程器

    如何通過STM32的串口實(shí)現(xiàn)簡易脫機(jī)編程器如何通過STM32的串口實(shí)現(xiàn)簡易脫機(jī)編程器如何通過STM32的串
    發(fā)表于 04-25 09:38 ?60次下載

    Dubbo源代碼實(shí)現(xiàn)服務(wù)調(diào)用的動(dòng)態(tài)代理和負(fù)載均衡

    我們知道,Dubbo服務(wù)調(diào)用封裝成普通的Spring的Bean,于是我們可以像使用本地的Spring Bean一樣,來調(diào)用遠(yuǎn)端的Dubbo服務(wù),并有LoadBalance和Failo
    發(fā)表于 03-12 14:35 ?0次下載

    多態(tài)路由機(jī)制研究

    如何基于有限且確定的路由結(jié)構(gòu)來支持多樣化服務(wù)是當(dāng)前研究面臨的問題,采用路由結(jié)構(gòu)的自組織和自調(diào)節(jié)來實(shí)現(xiàn)路由與業(yè)務(wù)的自適配,提出一種面向多樣化
    發(fā)表于 03-13 16:43 ?2次下載
    多態(tài)<b class='flag-5'>路由</b><b class='flag-5'>機(jī)制</b>研究

    服務(wù)化改造實(shí)踐(一)| Dubbo + ZooKeeper

    ?GreetingService?{String?sayHello(String?name);}2、服務(wù)端:服務(wù)實(shí)現(xiàn)實(shí)現(xiàn) ?GreetingService接口,并通過 @Service
    發(fā)表于 08-27 16:36 ?244次閱讀
    <b class='flag-5'>服務(wù)</b>化改造實(shí)踐(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper

    服務(wù)化改造實(shí)踐(一)| Dubbo + ZooKeeper

    GreetingService接口,并通過 @Service 來標(biāo)注其為Dubbo的一個(gè)服務(wù)。@Servicepublic?class?AnnotatedGreetingServic
    發(fā)表于 08-27 17:25 ?314次閱讀
    <b class='flag-5'>服務(wù)</b>化改造實(shí)踐(一)| <b class='flag-5'>Dubbo</b> + ZooKeeper

    dubbo-go 中的 TPS Limit 設(shè)計(jì)與實(shí)現(xiàn)

    限流,比如說限制一分鐘內(nèi)某個(gè)接口只能訪問 200 次,超過這個(gè)次數(shù),則會(huì)被拒絕服務(wù)。在 Dubbo 的 Java 版本上,只有一個(gè)實(shí)現(xiàn),就是 DefaultTPSLimiter 。 D
    發(fā)表于 03-17 15:27 ?647次閱讀

    Dubbo 如何成為連接異構(gòu)微服務(wù)體系的最佳服務(wù)開發(fā)框架

    從編程開發(fā)的角度來說,Apache Dubbo (以下簡稱 Dubbo )首先是一款 RPC 服務(wù)框架,它最大的優(yōu)勢在于提供了面向接口代理的服務(wù)
    發(fā)表于 03-12 17:04 ?953次閱讀
    <b class='flag-5'>Dubbo</b> 如何成為連接異構(gòu)微<b class='flag-5'>服務(wù)</b>體系的最佳<b class='flag-5'>服務(wù)</b>開發(fā)框架

    ARM與FPGA的接口實(shí)現(xiàn)的解析

    ARM與FPGA的接口實(shí)現(xiàn)的解析(應(yīng)廣單片機(jī))-該文檔為ARM與FPGA的接口實(shí)現(xiàn)的解析詳述資料,講解的還不錯(cuò),感興趣的可以下載看看…………………………
    發(fā)表于 07-22 09:47 ?14次下載
    ARM與FPGA的<b class='flag-5'>接口實(shí)現(xiàn)</b>的解析

    C#-Interface接口實(shí)現(xiàn)

    C#-Interface接口實(shí)現(xiàn)(安徽理士電源技術(shù)有限公司招聘信息)-該文檔為C#-Interface接口實(shí)現(xiàn)講解文檔,是一份還算不錯(cuò)的參考文檔,感興趣的可以下載看看,,,,,,,,,,,,,,,,,,
    發(fā)表于 09-28 09:42 ?3次下載
    C#-Interface<b class='flag-5'>接口實(shí)現(xiàn)</b>

    通過標(biāo)準(zhǔn)的CAN接口實(shí)現(xiàn)的測試臺自動(dòng)化解決方案

    通過標(biāo)準(zhǔn)的CAN接口實(shí)現(xiàn)的測試臺自動(dòng)化解決方案
    的頭像 發(fā)表于 10-11 14:17 ?1860次閱讀