Dubbo 路由規則之標籤路由

前言

你們好,今天開始給你們分享 — Dubbo 專題之 Dubbo 路由規則之標籤路由。在前一個章節中咱們介紹了 Dubbo 路由規則之標籤路由,以及咱們也例舉了常見的使用場景而且進行了源碼解析來分析其實現原理,同時知道 Dubbo 中標籤路由其本質上是經過過濾器對服務提供者列表進行規則的匹配,若是匹配不上則過濾掉服務提供者。那接下來咱們解析討論標籤路由,什麼是標籤路由呢?有什麼使用場景呢?下面就讓咱們快速開始吧!java

1. 標籤路由簡介

首先小夥伴能夠經過《Dubbo 路由規則之條件路由》迴歸一下什麼是路由規則?下面咱們主要討論什麼標籤路由:git

標籤路由

上圖中咱們能夠看到有兩個機房分別是機房A、機房B,其中機房 A 只能訪問到 Service A 和 Service B ,而機房B 只能訪問到 Service C 和 Service D。要實現上面這種場景咱們就須要用到標籤路由。從機房 A 發起的調用攜帶標籤 TAG_A 訪問到 Service A 和 Service B,而從機房 B 發起的調用攜帶 TAG_B Service C 和 Service D 。那什麼是標籤路由呢?shell

  • 標籤路由:以服務提供者應用爲粒度配置路由規則,經過將某一個或多個服務的提供者劃分到同一個分組,約束流量只在指定分組中流轉,從而實現流量隔離的目的,能夠做爲藍綠髮布、灰度發佈等場景的能力基礎。標籤主要是指對Provider端應用實例的分組,目前有兩種方式能夠完成實例分組,分別是動態規則打標靜態規則打標,其中動態規則相較於靜態規則優先級更高,而當兩種規則同時存在且出現衝突時,將以動態規則爲準。

2. 使用方式

下面咱們簡單的討論下標籤路由的使用方式:apache

2.1 標籤路由

  • 動態規則打標,可隨時在服務治理控制檯下發標籤歸組規則編程

    # demo-provider應用增長了兩個標籤分組tag1和tag2
    # tag1包含一個實例 127.0.0.1:20880
    # tag2包含一個實例 127.0.0.1:20881
    ---
      force: false
      runtime: true
      enabled: true
      key: demo-provider
      tags:
        - name: tag1
          addresses: ["127.0.0.1:20880"]
        - name: tag2
        addresses: ["127.0.0.1:20881"]
  • 靜態打標緩存

    <dubbo:provider tag="tag1"/>

或者微信

<dubbo:service tag="tag1"/>

或者app

java -jar xxx-provider.jar -Ddubbo.provider.tag={the tag you want, may come from OS ENV}
Tips:消費端經過編程的方式使用 RpcContext.getContext().setAttachment(CommonConstants.TAG_KEY,"TAG_A")請求標籤的做用域爲每一次 invocation,使用 attachment 來傳遞請求標籤,注意保存在 attachment 中的值將會在一次完整的遠程調用中持續傳遞,得益於這樣的特性,咱們只須要在起始調用時,經過一行代碼的設置,達到標籤的持續傳遞。
  • 字段說明:
編號 字段名稱 說明 必填
1 scope 路由規則的做用粒度,scope的取值會決定key的取值。
service 服務粒度 application 應用粒度。
必填
2 Key 明確規則體做用在哪一個接口服務或應用。 scope=service時,
key取值爲[{group}:]{service}[:{version}]的組合 scope=application時,
key取值爲application名稱 。
必填
3 enabled enabled=true 當前路由規則是否生效,,缺省生效。 可不填
4 force force=false 當路由結果爲空時,是否強制執行,若是不強制執行,
路由結果爲空的路由規則將自動失效,缺省爲 false
可不填
5 runtime runtime=false 是否在每次調用時執行路由規則,
不然只在提供者地址列表變動時預先執行並緩存結果,
調用時直接從緩存中獲取路由結果。若是用了參數路由,必須設爲 true
須要注意設置會影響調用的性能,缺省爲 false
可不填
6 priority priority=1 路由規則的優先級,用於排序,優先級越大越靠前執行,缺省爲 0 可不填
7 tags 定義具體的標籤分組內容,可定義任意n(n>=1)個標籤併爲每一個標籤指定實例列表。其中name爲標籤名稱 必填

2.2 降級約定

  • request.tag=tag1 時優先選擇標記了tag=tag1provider。若集羣中不存在與請求標記對應的服務,默認將降級請求 tag爲空的provider;若是要改變這種默認行爲,即找不到匹配tag1provider返回異常,需設置request.tag.force=true
  • request.tag未設置時,只會匹配tag爲空的provider。即便集羣中存在可用的服務,若 tag 不匹配也就沒法調用,這與約定1不一樣,攜帶標籤的請求能夠降級訪問到無標籤的服務,但不攜帶標籤/攜帶其餘種類標籤的請求永遠沒法訪問到其餘標籤的服務。
Tips: 2.6.x 版本以及更早的版本請使用 老版本路由規則,自定義路由參考 路由擴展

3. 使用場景

從上面的簡單介紹咱們能夠大體瞭解到,標籤路由經過將某一個或多個服務的提供者劃分到同一個分組,約束流量只在指定分組中流轉,從而實現流量隔離的目的。咱們平常工做中經常使用的場景有:藍綠髮布、灰度發佈等場景的能力基礎等。分佈式

4. 示例演示

咱們以獲取圖書列表爲例進行實例演示,其中咱們會啓動兩個服務提供者配置兩個端口:2088020881,而後分別指定兩個服務標籤爲:TAG_ATAG_B。項目結構圖以下:ide

idea

這裏咱們使用動態打標的方式全部 XML 中的配置維持之前案例的配置,咱們主要看看 Dubbo Admin 中的配置:

idea1

# demo-provider 應用增長了兩個標籤分組 TAG_A 和 TAG_B
# TAG_A 包含一個實例 127.0.0.1:20880
# TAG_B 包含一個實例 127.0.0.1:20881
force: true
enabled: true
runtime: false
tags:
 - name: TAG_A
   addresses: [192.168.0.1:20880]
 - name: TAG_B
   addresses: [192.168.0.2:20881]

以上動態打標配置表示:當消費端指定標籤爲 TAG_A 時調用 127.0.0.1:20880 服務提供者,標籤爲 TAG_B 時調用 127.0.0.1:20881 服務。

Tips: 小夥伴經過在消費端動態切換標籤 TAG_ATAG_A來查看效果,服務端只需啓動一個端口爲20880的服務便可。

5. 實現原理

根據前面的介紹咱們知道在消費端調用遠程服務時經過路由規則進行服務的過濾,那麼咱們經過源碼簡單的分析下這個處理過程。這裏咱們直接看到路由規則的調用核心代碼`org.apache.dubbo.rpc.cluster.
RouterChain#route`核心方法以下:

public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

下面展現了咱們運行過程當中的路由規則:

idea3

其中TagRouter就是咱們的標籤路由核心代碼以下:

/**
     *
     * 標籤路由
     *
     * @author liyong
     * @date 4:48 PM 2020/11/29
     * @param invokers
     * @param url
     * @param invocation
     * @exception
     * @return java.util.List<org.apache.dubbo.rpc.Invoker<T>>
     **/
    @Override
    public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }

        // 這裏由於配置中心可能更新配置,全部使用另一個常量引用(相似複製)
        final TagRouterRule tagRouterRuleCopy = tagRouterRule;
        //若是動態規則不存在或無效或沒有激活,使用靜態標籤
        if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || !tagRouterRuleCopy.isEnabled()) {
            //處理靜態標籤
            return filterUsingStaticTag(invokers, url, invocation);
        }

        List<Invoker<T>> result = invokers;
        //獲取上下文中Attachment的標籤參數,這個參數由客戶端調用時候寫入
        String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) ? url.getParameter(TAG_KEY) :
                invocation.getAttachment(TAG_KEY);

        // 若是存在傳遞標籤
        if (StringUtils.isNotEmpty(tag)) {
            //經過傳遞的標籤找到動態配置對應的服務地址
            List<String> addresses = tagRouterRuleCopy.getTagnameToAddresses().get(tag);
            // 經過標籤分組進行過濾
            if (CollectionUtils.isNotEmpty(addresses)) {
                //獲取匹配地址的服務
                result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
                //若是返回結果不爲null 或者 返回結果爲空可是配置force=true也直接返回
                if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
                    return result;
                }
            } else {
                //檢測靜態標籤
                result = filterInvoker(invokers, invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY)));
            }
            //若是提供者沒有配置標籤 默認force.tag = false 表示能夠訪問任意的提供者 ,除非咱們顯示的禁止
            if (CollectionUtils.isNotEmpty(result) || isForceUseTag(invocation)) {
                return result;
            }
            else {
                //返回全部的提供者,不須要任意標籤
                List<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
                        tagRouterRuleCopy.getAddresses()));
                //查找提供者標籤爲空
                return filterInvoker(tmp, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY)));
            }
        } else {
            //返回全部的 addresses
            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;
                }
            }
           //繼續使用靜態標籤過濾
            return filterInvoker(result, invoker -> {
                String localTag = invoker.getUrl().getParameter(TAG_KEY);
                return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
            });
        }
    }

上面的代碼中把主要的流程進行註釋,請小夥伴自行進行代碼調試查看。

6. 小結

在本小節中咱們主要學習了 Dubbo 中路由規則之標籤路由以及使用方式。同時也分析了標籤路由規則實現的原理:若是消費端傳遞標籤則和配置的動態規則和靜態規則進行匹配,若是消費端未傳遞標籤則使用服務提供端的本地配置的靜態標籤和動態配置標籤進行匹配。

Tips: 動態規則相較於靜態規則優先級更高,而當兩種規則同時存在且出現衝突時,將以動態規則爲準。

本節課程的重點以下:

  1. 理解 Dubbo 標籤路由
  2. 瞭解了標籤路由使用方式
  3. 瞭解標籤路由實現原理
  4. 瞭解標籤路由使用場景

做者

我的從事金融行業,就任過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就任於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公衆號和博客站點對知識體系進行分享。關注公衆號: 青年IT男 獲取最新技術文章推送!

博客地址: http://youngitman.tech

微信公衆號:

相關文章
相關標籤/搜索