Skip to main content

什么是Dubbo标签路由?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

回答话术

1. 什么是标签路由

Dubbo 的标签路由功能提供了一种灵活的服务路由机制,允许开发者基于 Tag 定义路由规则。

在日常的开发中,标签路由方式可以增强服务的可管理性、扩展性以及微服务架构的整体灵活性。通过标签路由,我们可以有效地控制服务之间流量的走向,优化系统的性能和资源利用率。

标签路由的核心思想就是通过标签将服务提供者划分成不同的组,然后在请求时传递携带特定的标签,这样请求就只会路由到对应标签的服务组。

2. 标签路由应用场景

假设我们有两个应用 A 和 B ,它们都需要访问同一组 Dubbo 服务。我们可以在这些 Dubbo 服务的提供者上打标签,比如 ProviderA 和 ProviderB 打上 TagA, ProviderC 和 ProviderD 打上 TagB。这样应用 A 在调用 Dubbo 接口时,就可以指定传递 TagA ,那么它的请求只会被路由到那两个带 TagA 的提供者。应用 B 也是一样的道理。

这样通过标签路由,我们就可以非常灵活地控制不同应用的流量只在特定的服务提供者间流转,实现流量隔离的效果。

image.png

消费者端可以通过编程方式设定每次调用的请求标签。这种方法利用请求传递元数据标签的机制,其中元数据中的值会在一次完整的远程调用过程中保持不变。由于这个特点,我们只需在初始调用时设置一行代码,就可以实现标签在整个调用链中的连续传递。

RpcContext.getContext().setAttachment(CommonConstants.TAG_KEY"TAG_A")

问题详解

1. 标签路由原理

Dubbo 服务调用发起后,会进入 RouterChain 进行服务调用路由。

RouterChain 是一个用于管理和执行路由规则的链式结构。它允许多个路由器(Router)按照特定的顺序和条件对服务的调用进行路由和过滤。

当一个服务消费者发起一个服务调用时,Dubbo 会根据预定义的路由规则和策略来确定如何选择合适的服务提供者。这个选择过程通常是通过一个或多个路由器(Router)来实现的。

如果 TagRouter 存在于 RouterChain 中,则会由 TagRouter 来实现标签路由逻辑。TagRouter 中的具体路由规则来源于配置中心。在动态标签场景下,其他系统会将标签路由规则写入配置中心,配置中心再通过事件通知服务将规则更新到 TagRouter 中。

TagRouter 指的是基于标签(或标签组)的路由器。

通过这个流程,我们可以看到 TagRouter 是实现标签路由的核心组件。它维护了标签路由规则,并根据这些规则来选择服务提供者。配置中心用于集中管理路由规则,并在规则变更时通知 TagRouter。所以 Dubbo 通过 TagRouter + 配置中心的方式实现了标签路由以及动态标签路由。

image.png

2. 标签路由对比条件路由

标签路由的优势在于,它提供了一种更为直观和简洁的方式来实现服务实例的分组和流量隔离,这在复杂的发布和测试场景中非常有用。而条件路由则提供了更为细粒度的控制,允许开发者根据具体的业务需求来定制路由规则,这在需要根据多种条件动态路由时显得更为灵活。

在选择路由策略时,需要根据实际的业务场景和需求来决定。如果需要进行服务版本的灰度发布或者需要对特定的服务实例进行隔离,标签路由可能是更好的选择。而如果需要根据调用的具体情况来动态选择服务提供者,条件路由可能更加合适。

3. 静态标签和动态标签

动态标签可以通过 DubboAdmin 或者其他外部系统进行规则下发,示例如下:

# 省略无关配置
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="TagA"/><dubbo:service tag="TagA"/>

再或者使用 JVM 启动参数传递。

java -jar xxx-provider.jar -Ddubbo.provider.tag={环境变量或者传递自定义值}

4. 源码解析

/**
*
* 标签路由
*
* @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);
});
}
}

5. 文末总结

Dubbo3 的标签路由具有以下特点和优势:它提供了一种灵活的服务路由机制,允许开发者基于标签定义路由规则,从而实现了服务的可管理性、可扩展性和流量隔离。

标签路由可用于将服务提供者划分成不同组,使请求仅路由到带有特定标签的服务组,从而优化系统性能和资源利用率。主要应用场景包括 VIP 用户和普通用户隔离、蓝绿发布等,使 Dubbo 服务更加灵活和可控。