Dubbo标签路由原理分析

定义&应用场景

标签路由通过将某一个或多个服务的提供者划分到同一个分组,约束流量只在指定分组中流转,从而实现流量隔离的目的,可以作为蓝绿发布、灰度发布等场景的能力基础。

下图是标签路由的一个典型场景,应用A和应用B都要访问同一组dubbo服务,应用A请求dubbo接口时传递TagA,请求就会被路由到服务中红色框的providerA、providerB,因为providerA、providerB被打上了TagA;同理,应用B因为携带了TagB,请求会被路由到providerC、providerD,因为后者被打上了TagB。
标签路由示意图.png

降级约定:
1、consumer携带request.tag=tag1 时优先选择 标记了tag=tag1 的 provider。若集群中不存在与请求标记对应的服务,默认将降级请求 tag为空的provider;如果要改变这种默认行为,即找不到匹配tag1的provider返回异常,需设置request.tag.force=true。
2、comsumer侧request.tag未设置时,只会匹配tag为空的provider。即使集群中存在可用的服务,若tag不匹配也就无法调用,这与约定1不同,携带标签的请求可以降级访问到无标签的服务,但不携带标签/携带其他种类标签的请求永远无法访问到其他标签的服务。

标签路由使用方式

到处我们基本清楚了什么是标签路由,标签路由的作用是什么。那下面我们来看下怎么使用标签路由。
#####provider端标签设置
######静态标签
静态标签配置也有三种方式 :
1、dubbo xml配置文件中添加如下配置指定标签
<dubbo:provider tag=“tag1”/>
或者
<dubbo:service tag=“tag1”/>

2、通过注解指定标签
@org.apache.dubbo.config.annotation.Service(tag = “tag1”)

3、通过jvm参数或os环境变量指定。如下:
java -jar xxx-provider.jar -Ddubbo.provider.tag=tag1

动态标签

在系统后台下发如下的yaml配置:dubbo-tag-route-demo应用增加了两个标签组,tag1包含一个实例127.0.0.1:20880,tag2包含一个实例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传递标签
静态标签

consumer也可以通过dubbo xml配置指定标签:

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

动态标签

在发起调用dubbo前,通过RpcContext.getContext().setAttachment(Constants.TAG_KEY,“TAG_A”)携带标签。请求标签的作用域为每一次 invocation,使用 attachment 来传递请求标签,注意保存在 attachment 中的值将会在一次完整的远程调用中持续传递,得益于这样的特性,我们只需要在起始调用时,通过一行代码的设置,达到标签的持续传递。

关于标签传递:因为ConsumerContextFilter在每次请求之后会清空Attachments,所以dubbo调用A携带的tagA,要传递到下次dubbo调用B,需要应用自己来做。具体的解法可以将标签放到theadlocal中,这样dubbo调用B可以从threadlocal中获取到tagA,然后传递下去。
还有一个场景也是需要注意的 ,如果dubbo调用是在一个单独的线程或线程池中发起的 ,标签的传递也要解决跨线程传递的问题,无侵入跨线程传递参数的解决方案有阿里开源的transmittable-thread-local。

标签路由原理

上面已经清楚的标签路由的使用方式和应用场景,那dubbo的标签路由具体是怎么实现的呢?
我们先来看看整体流程。从下图知,在发起dubbo调用之后,最终会走到RouterChain来做服务调用路由。如果TagRouter在RouterChain里,则会由TagRouter来做标签路由的逻辑。
从图中可以看到具体的路由逻辑在TagRouter,而TagRouter使用的路由规则来自配置中心的。在动态标签的场景,其他系统将标签路由规则写到配置中心,配置中心通过event将路由规则通知给TagRouter。
dubbo标签路由整体流程.png

下面结合代码来看下,TagRouter是如何做路由的。
从下面代码看到,在没有动态标签路由规则时,会根据静态标签路由规则来路由,路由的逻辑就是按照上面提到的降级约定来做的。
存在动态标签路由规则时,会先按照动态标签路由规则来路由,如果动态路由规则路由到的adress为空,则会尝试使用静态标签路由规则来路由一次。
如果请求没有携带标签,则只能路由到没有打标签的adress,不能降级到打了标签的adress,同降级约定一致。

 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);
            });
        }
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值