Dubbo標(biāo)簽路由原理分析

定義&應(yīng)用場(chǎng)景

標(biāo)簽路由通過(guò)將某一個(gè)或多個(gè)服務(wù)的提供者劃分到同一個(gè)分組恒序,約束流量只在指定分組中流轉(zhuǎn),從而實(shí)現(xiàn)流量隔離的目的,可以作為藍(lán)綠發(fā)布、灰度發(fā)布等場(chǎng)景的能力基礎(chǔ)伺糠。

下圖是標(biāo)簽路由的一個(gè)典型場(chǎng)景蓄喇,應(yīng)用A和應(yīng)用B都要訪問(wèn)同一組dubbo服務(wù)骗污,應(yīng)用A請(qǐng)求dubbo接口時(shí)傳遞TagA肌括,請(qǐng)求就會(huì)被路由到服務(wù)中紅色框的providerA、providerB泛范,因?yàn)閜roviderA让虐、providerB被打上了TagA;同理罢荡,應(yīng)用B因?yàn)閿y帶了TagB赡突,請(qǐng)求會(huì)被路由到providerC、providerD区赵,因?yàn)楹笳弑淮蛏狭薚agB惭缰。


標(biāo)簽路由示意圖.png

降級(jí)約定:
1、consumer攜帶request.tag=tag1 時(shí)優(yōu)先選擇 標(biāo)記了tag=tag1 的 provider笼才。若集群中不存在與請(qǐng)求標(biāo)記對(duì)應(yīng)的服務(wù)漱受,默認(rèn)將降級(jí)請(qǐng)求 tag為空的provider;如果要改變這種默認(rèn)行為患整,即找不到匹配tag1的provider返回異常拜效,需設(shè)置request.tag.force=true。
2各谚、comsumer側(cè)request.tag未設(shè)置時(shí),只會(huì)匹配tag為空的provider到千。即使集群中存在可用的服務(wù)昌渤,若tag不匹配也就無(wú)法調(diào)用,這與約定1不同憔四,攜帶標(biāo)簽的請(qǐng)求可以降級(jí)訪問(wèn)到無(wú)標(biāo)簽的服務(wù)膀息,但不攜帶標(biāo)簽/攜帶其他種類標(biāo)簽的請(qǐng)求永遠(yuǎn)無(wú)法訪問(wèn)到其他標(biāo)簽的服務(wù)。

標(biāo)簽路由使用方式

到處我們基本清楚了什么是標(biāo)簽路由了赵,標(biāo)簽路由的作用是什么潜支。那下面我們來(lái)看下怎么使用標(biāo)簽路由。

provider端標(biāo)簽設(shè)置
靜態(tài)標(biāo)簽

靜態(tài)標(biāo)簽配置也有三種方式 :
1柿汛、dubbo xml配置文件中添加如下配置指定標(biāo)簽
<dubbo:provider tag="tag1"/>
或者
<dubbo:service tag="tag1"/>

2冗酿、通過(guò)注解指定標(biāo)簽
@org.apache.dubbo.config.annotation.Service(tag = "tag1")

3、通過(guò)jvm參數(shù)或os環(huán)境變量指定。如下:
java -jar xxx-provider.jar -Ddubbo.provider.tag=tag1

動(dòng)態(tài)標(biāo)簽

在系統(tǒng)后臺(tái)下發(fā)如下的yaml配置:dubbo-tag-route-demo應(yīng)用增加了兩個(gè)標(biāo)簽組裁替,tag1包含一個(gè)實(shí)例127.0.0.1:20880项玛,tag2包含一個(gè)實(shí)例127.0.0.1:20881

force: false
runtime: true
enabled: true
key: dubbo-tag-route-demo
tags:
  - name: tag1
    addresses: ["127.0.0.1:20880"]
  - name: tag2
  addresses: ["127.0.0.1:20881"]
consumer傳遞標(biāo)簽
靜態(tài)標(biāo)簽

consumer也可以通過(guò)dubbo xml配置指定標(biāo)簽:

<dubbo:reference id="xxxxxxApi" interface="com.XXXXXApi" tag="tag1"/>

動(dòng)態(tài)標(biāo)簽

在發(fā)起調(diào)用dubbo前,通過(guò)RpcContext.getContext().setAttachment(Constants.TAG_KEY,"TAG_A")攜帶標(biāo)簽弱判。請(qǐng)求標(biāo)簽的作用域?yàn)槊恳淮?invocation襟沮,使用 attachment 來(lái)傳遞請(qǐng)求標(biāo)簽,注意保存在 attachment 中的值將會(huì)在一次完整的遠(yuǎn)程調(diào)用中持續(xù)傳遞昌腰,得益于這樣的特性开伏,我們只需要在起始調(diào)用時(shí),通過(guò)一行代碼的設(shè)置遭商,達(dá)到標(biāo)簽的持續(xù)傳遞固灵。

關(guān)于標(biāo)簽傳遞:因?yàn)镃onsumerContextFilter在每次請(qǐng)求之后會(huì)清空Attachments,所以dubbo調(diào)用A攜帶的tagA株婴,要傳遞到下次dubbo調(diào)用B怎虫,需要應(yīng)用自己來(lái)做。具體的解法可以將標(biāo)簽放到theadlocal中困介,這樣dubbo調(diào)用B可以從threadlocal中獲取到tagA大审,然后傳遞下去。
還有一個(gè)場(chǎng)景也是需要注意的 座哩,如果dubbo調(diào)用是在一個(gè)單獨(dú)的線程或線程池中發(fā)起的 徒扶,標(biāo)簽的傳遞也要解決跨線程傳遞的問(wèn)題,無(wú)侵入跨線程傳遞參數(shù)的解決方案有阿里開源的transmittable-thread-local根穷。

標(biāo)簽路由原理

上面已經(jīng)清楚的標(biāo)簽路由的使用方式和應(yīng)用場(chǎng)景姜骡,那dubbo的標(biāo)簽路由具體是怎么實(shí)現(xiàn)的呢?
我們先來(lái)看看整體流程屿良。從下圖知圈澈,在發(fā)起dubbo調(diào)用之后,最終會(huì)走到RouterChain來(lái)做服務(wù)調(diào)用路由尘惧。如果TagRouter在RouterChain里康栈,則會(huì)由TagRouter來(lái)做標(biāo)簽路由的邏輯。
從圖中可以看到具體的路由邏輯在TagRouter喷橙,而TagRouter使用的路由規(guī)則來(lái)自配置中心的啥么。在動(dòng)態(tài)標(biāo)簽的場(chǎng)景,其他系統(tǒng)將標(biāo)簽路由規(guī)則寫到配置中心贰逾,配置中心通過(guò)event將路由規(guī)則通知給TagRouter悬荣。


dubbo標(biāo)簽路由整體流程.png

下面結(jié)合代碼來(lái)看下,TagRouter是如何做路由的疙剑。
從下面代碼看到氯迂,在沒(méi)有動(dòng)態(tài)標(biāo)簽路由規(guī)則時(shí)践叠,會(huì)根據(jù)靜態(tài)標(biāo)簽路由規(guī)則來(lái)路由,路由的邏輯就是按照上面提到的降級(jí)約定來(lái)做的囚戚。
存在動(dòng)態(tài)標(biāo)簽路由規(guī)則時(shí)酵熙,會(huì)先按照動(dòng)態(tài)標(biāo)簽路由規(guī)則來(lái)路由,如果動(dòng)態(tài)路由規(guī)則路由到的adress為空驰坊,則會(huì)嘗試使用靜態(tài)標(biāo)簽路由規(guī)則來(lái)路由一次匾二。
如果請(qǐng)求沒(méi)有攜帶標(biāo)簽,則只能路由到?jīng)]有打標(biāo)簽的adress拳芙,不能降級(jí)到打了標(biāo)簽的adress察藐,同降級(jí)約定一致。

 public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }

        // since the rule can be changed by config center, we should copy one to use.
        final TagRouterRule tagRouterRuleCopy = tagRouterRule;
        if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || !tagRouterRuleCopy.isEnabled()) {
            return filterUsingStaticTag(invokers, url, invocation);
        }

        List<Invoker<T>> result = invokers;
        String tag = StringUtils.isEmpty(invocation.getAttachment(Constants.TAG_KEY)) ? url.getParameter(Constants.TAG_KEY) :
                invocation.getAttachment(Constants.TAG_KEY);

        // if we are requesting for a Provider with a specific tag
        if (StringUtils.isNotEmpty(tag)) {
            List<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
            // filter by dynamic tag group first
            if (CollectionUtils.isNotEmpty(addresses)) {
                result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
                // if result is not null OR it's null but force=true, return result directly
                if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
                    return result;
                }
            } else {
                // dynamic tag group doesn't have any item about the requested app OR it's null after filtered by
                // dynamic tag group but force=false. check static tag
                result = filterInvoker(invokers, invoker -> tag.equals(invoker.getUrl().getParameter(Constants.TAG_KEY)));
            }
            // If there's no tagged providers that can match the current tagged request. force.tag is set by default
            // to false, which means it will invoke any providers without a tag unless it's explicitly disallowed.
            if (CollectionUtils.isNotEmpty(result) || isForceUseTag(invocation)) {
                return result;
            }
            // FAILOVER: return all Providers without any tags.
            else {
                List<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
                        tagRouterRuleCopy.getAddresses()));
                return filterInvoker(tmp, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(Constants.TAG_KEY)));
            }
        } else {
            // List<String> addresses = tagRouterRule.filter(providerApp);
            // return all addresses in dynamic tag group.
            List<String> addresses = tagRouterRuleCopy.getAddresses();
            if (CollectionUtils.isNotEmpty(addresses)) {
                result = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(), addresses));
                // 1. all addresses are in dynamic tag group, return empty list.
                if (CollectionUtils.isEmpty(result)) {
                    return result;
                }
                // 2. if there are some addresses that are not in any dynamic tag group, continue to filter using the
                // static tag group.
            }
            return filterInvoker(result, invoker -> {
                String localTag = invoker.getUrl().getParameter(Constants.TAG_KEY);
                return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
            });
        }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舟扎,一起剝皮案震驚了整個(gè)濱河市分飞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌睹限,老刑警劉巖譬猫,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異羡疗,居然都是意外死亡染服,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門叨恨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)柳刮,“玉大人,你說(shuō)我怎么就攤上這事痒钝”牛” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵送矩,是天一觀的道長(zhǎng)蚕甥。 經(jīng)常有香客問(wèn)我,道長(zhǎng)栋荸,這世上最難降的妖魔是什么梢灭? 我笑而不...
    開封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮蒸其,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘库快。我一直安慰自己摸袁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開白布义屏。 她就那樣靜靜地躺著靠汁,像睡著了一般蜂大。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝶怔,一...
    開封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天奶浦,我揣著相機(jī)與錄音,去河邊找鬼踢星。 笑死澳叉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沐悦。 我是一名探鬼主播成洗,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼藏否!你這毒婦竟也來(lái)了瓶殃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤副签,失蹤者是張志新(化名)和其女友劉穎遥椿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淆储,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡冠场,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了遏考。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慈鸠。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖灌具,靈堂內(nèi)的尸體忽然破棺而出青团,到底是詐尸還是另有隱情,我是刑警寧澤咖楣,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布督笆,位于F島的核電站,受9級(jí)特大地震影響诱贿,放射性物質(zhì)發(fā)生泄漏娃肿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一珠十、第九天 我趴在偏房一處隱蔽的房頂上張望料扰。 院中可真熱鬧,春花似錦焙蹭、人聲如沸晒杈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拯钻。三九已至帖努,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粪般,已是汗流浹背拼余。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留亩歹,地道東北人匙监。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像捆憎,于是被迫代替她去往敵國(guó)和親舅柜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容