
路由发生在负载均衡之前。正常消费端路径:

1. AbstractClusterInvoker.invoke(Invocation) 向目录请求可用调用器
2. DynamicDirectory 或 ServiceDiscoveryRegistryDirectory 返回当前调用器列表
3. RouterChain 为当前地址快照选择 SingleRouterChain
4. SingleRouterChain.simpleRoute(...) 先应用状态路由器
5. 若存在非状态/普通路由器,在状态路由器后应用
6. 具体集群调用器收到路由后的列表
7. 具体集群调用器调用 select(...)
8. LoadBalance 从路由候选集中选择一个提供者
重要顺序:
服务发现 / 注册中心通知
-> 目录调用器刷新
-> 路由链过滤
-> 集群容错
-> 负载均衡选择
-> 提供者调用
RouterChain.buildSingleChain(...) 加载遗留 RouterFactory 扩展和 StateRouterFactory 扩展。普通路由器按 Router::compareTo 排序,状态路由器链接成以 TailStateRouter 结尾的链。
SingleRouterChain.simpleRoute(...) 先通过 headStateRouter 路由,之后才将 BitList 结果转为普通列表并运行普通路由器。因此负载均衡器永不见完整注册中心地址列表,除非路由让其不变。
状态路由器注册于:
dubbo-cluster/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory
当前注册:
MockStateRouterFactory本文聚焦条件路由、标签路由、脚本路由、应用级服务发现路由和灰度/金丝雀路由模式。


RouterChain 现维护两个 SingleRouterChain 实例:mainChain 和 backupChain。地址通知期间将流量切换到备份链,刷新主链,切换目录地址引用,再将流量切回主链并刷新备份链。
此设计存在因为路由状态绑定到特定调用器列表。请求不能用新 BitList 穿旧链路由,或用旧 BitList 穿新链路由。
历史注记:
• 提交 3531a7d681([3.0] Feature/state router (#6844))引入状态路由器、BitList 和状态路由器工厂注册。原设计包含路由计算地址列表缓存
• 提交 77e6708ebd([3.0] Refactor state router iterator to router chain (#9424))将状态路由器重构为后续使用的链模型
• 提交 4cc3b28a3e([3.0] Add Router Snapshot Print Support (#9517))添加路由快照,使空路由诊断可见
• 提交 5ef3b4fb2e(Add dual chain support for state router, prevent update when invoke (#10691))将 RouterChain 拆为双 SingleRouterChain 实例,避免调用路由时更新链
• 提交 b56687957e(Fix invokers has changed when router chain switch (#11248))修复调用器快照与路由链切换时可能不匹配的竞争。当前代码保持临时 notifyingInvokers 引用以决定用刷新主链还是当前备份链
操作含义:
• 路由非对任意列表应用的无状态过滤器。链与 BitList 源列表耦合
• 地址通知、规则刷新和请求路由对并发敏感
• 路由快照是"路由后无提供者"情况的一级诊断路径
ConditionStateRouter 实现经典规则形状:
when 条件 => then 条件
左侧匹配消费者 URL 和调用。右侧过滤提供者 URL。示例:
method=sayHello => region=hangzhou
attachments[user_id]=1~100 => region=hangzhou
arguments[0]=vip => env=gray
重要行为:
• 空 or truewhen 表示规则对所有匹配请求适用
• 空 or falsethen 表示显式空结果,对匹配消费者实际是黑名单规则
• 若 when 不匹配,返回原始调用器列表
• 若 then 匹配提供者,返回匹配子集
• 若 then 匹配无提供者且 force=true,返回空列表
• 若 then 匹配无提供者且 force=false,路由器回退到原始输入列表
• 匹配委托给 ConditionMatcherFactory 实现
动态条件规则由 ListenableStateRouter 子类处理:
• ServiceStateRouter 监听 service-key.condition-router
• AppStateRouter 监听 application.condition-router
• ProviderAppStateRouter 在看到提供者地址后订阅提供者应用的 .condition-router 规则
历史注记:
• 早期条件路由用硬编码 MatchPair 模型处理 URL 参数和参数
• 提交 8c786cdf01(Add 'label match' support for override and tag rules (#11094))将条件匹配移到匹配器 SPI 抽象。添加参数匹配、附件匹配、URL 参数匹配、范围匹配、通配符匹配和 ProviderAppStateRouter
• 提交 31e1aa6b59(Add volatile modifier to some variables in ListenableRouter (#8579))是小但重要的并发标记:规则可从配置中心通知异步更新
• 提交 a93624e8cf(New condition router v3.1 (#14456))添加 MultiDestConditionRouter,支持单条件规则下多加权目标
风险轮廓:
• force=true 可故意路由到空提供者集。对黑名单和流量切断有用,但规则格式错误时危险
• force=false 通过回退原始列表保护可用性,但无提供者匹配时可静默忽略预期隔离
• 匹配器可扩展性强大,但每个匹配器参与热路径路由
v3.1 条件路由将单 then 目标泛化为多加权目标:
configVersion: v3.1
scope: service
force: false
runtime: true
enabled: true
conditions:
- from:
match: env=gray
to:
- match: env=gray
weight: 900
- match: env=normal
weight: 100
MultiDestConditionRouter 评估 from.match 条件。对每个 to 分支构建提供者子集。非空子集以权重加入 DestinationSet,然后随机选一个目标子集。
这是与灰度/金丝雀流量最直接对齐的条件路由形式。仍返回提供者子集而非最终提供者。路由器选目标子集后,负载均衡仍在该子集内选具体提供者。
风险轮廓:
• 加权目标选择发生在负载均衡前
• 权重控制目标子集概率,非每提供者概率
• 若一目标子集有多个提供者,另一子集少,最终每提供者流量分布受目标权重和下游负载均衡共同影响
TagStateRouter 分离带标签和不带标签流量。请求标签先来自调用附件 dubbo.tag,再来自消费者 URL 参数。提供者标签可为静态 URL 参数或配置中心动态标签规则。
重要行为:
• dubbo.tag=* 跳过标签路由,返回所有当前调用器
• 若无动态标签规则,用静态提供者 URL 标签
• 带标签请求先试动态标签地址
• 若无动态标签地址,检查静态提供者标签
• 若无匹配带标签提供者且 force.tag 非 true,回退不带标签提供者
• 若 force.tag=true,空带标签结果保持空
• 不带标签请求排除动态标签分组中的提供者,再排除静态标签提供者
• 动态标签规则按提供者应用订阅
多级选择器通过渐进修剪后缀支持标签回退:
beta|team1|partner1
-> beta|team1|partner1
-> beta|team1
-> beta
强制模式启用时,此回退禁用,只选精确标签。
历史注记:
• 提交 8c786cdf01(Add 'label match' support for override and tag rules (#11094))添加 v3 标签规则按提供者 URL 参数匹配,非仅显式地址列表。并警告 v3 标签规则偏好 match 而非 addresses
• 提交 679ce4496c(tag support multi level (#12673))用 | 作分隔符添加分层标签回退
• 提交 c63d247ed7(fix(cluster): support broadcast and wildcard tag routing (#15876))添加通配符标签路由。关键行为是 dubbo.tag=* 返回所有提供者而非应用标签隔离
风险轮廓:
• 标签路由有可用性回退到不带标签提供者,除非 force.tag=true
• 此回退对金丝雀安全方便,但可违反严格隔离预期
• 通配符标签路由刻意绕过标签过滤;对广播或管理调用有用,但不应与正常带标签请求混淆
ScriptStateRouter 评估 JSR-223 脚本并期望脚本返回列表或数组调用器。向脚本传这些绑定:
invokers
invocation
context
重要行为:
• 脚本引擎类型从 type URL 参数读,默认 Dubbo 默认脚本类型
• 脚本引擎按类型缓存
• 规则在构造器中编译一次
• 若编译失败,路由器记录日志并回退原始调用器列表
• 若执行失败,记录日志并返回原始调用器列表
• 返回调用器与原始 BitList 取交集,脚本不能添加当前候选集外的提供者
• 执行在受限 AccessControlContext 中运行,只授予 RuntimePermission("accessDeclaredMembers")
动态应用级脚本路由由 AppScriptStateRouter 处理,监听提供者应用的 .script-router 规则并从 YAML 配置重建 ScriptStateRouter。
历史注记:
• 脚本路由早于当前状态路由器模型,但现作为状态路由器参与
• 提交 e07e5e294b(Fix get method name from invocation (#12519))改变方法名日志使用 RpcUtils.getMethodName(invocation)
• 提交 d2eb0ef458(Optimize ConcurrentHashMap#computeIfAbsent have performance problem in jdk1.8 (#11326))将直接 ConcurrentHashMap.computeIfAbsent 替换为 Dubbo 工具助手处理引擎缓存查找,因脚本路由在热路径
风险轮廓:
• 脚本路由最灵活但最难推理
• 依赖运行时脚本引擎存在
• 格式错误脚本失败开放,返回原始调用器列表
• 脚本执行受限但仍比声明式条件或标签规则实质上风险更高
应用级服务发现不只一个路由类。它在路由开始前改变地址源。
ServiceDiscoveryRegistryDirectory 收服务实例通知,转实例 URL 和元数据为协议服务键,创建或复用调用器,构建 BitList,刷新集群层其余部分使用的同一 RouterChain。
重要行为:
• buildRouterChain(...) 构建路由链前添加 registry-type=service
• 实例地址在调用器创建前去重
• 不支持协议跳过
• 元数据用于匹配服务接口、分组、版本、协议和端口
• 若消费者协议未指定,存在时先选偏好协议;否则用主非额外协议
• 应用级和服务级配置器可在调用器创建前覆盖目录和提供者 URL
• 多分组引用可将每分组调用器包装在 StaticDirectory,它本身有路由链
历史注记:
• 提交 ff39bf36c3(Add 3.0 Multi Instance Support (#8662))是主要服务发现迁移基线
• 提交 1dfb114734([3.0] Multi Instances Related Refactor (#8737))和后续服务发现提交精化多实例行为
• 提交 7c7788d8b7(Add preferred protocol support for application service discovery (#14849))在回退默认主协议前添加偏好协议选择
• 提交 a35af2742a(Remove duplicated invokers in registry notification (#15060))在调用器创建前添加注册通知 URL 去重
• 提交 2170f0fa90(fix thread safety issue of ConsumerConfigurationListener (#15707))将目录监听器 ArrayList 替换为 CopyOnWriteArrayList
风险轮廓:
• 应用级发现改变候选宇宙,在路由规则运行前
• 服务元数据正确性直接影响哪些协议服务键可引用
• 重复通知数据、偏好协议选择和配置器覆盖都影响最终路由提供者集
MigrationInvoker 管理接口级发现与应用级服务发现间迁移。支持三步:
FORCE_INTERFACE
APPLICATION_FIRST
FORCE_APPLICATION
APPLICATION_FIRST 中,Dubbo 可按推广百分比和可用性偏好服务发现调用器同时回退接口级调用器:
• promotion < 100 触发按比例随机回退接口模式
• decideInvoker() 只在当前可用时偏好服务发现
• 地址比较器在强制迁移前检查迁移是否满足配置阈值
• 规则支持全局默认、接口特定覆盖和应用映射特定覆盖
历史注记:
• 提交 0ae2ccc99c(optimization MigrationInvoker promotion (#9270) (#9271))修复推广检查,使 promotion=100 不必要运行随机比较路径
• 提交 c4847d5934([3.0] Add dynamic decide invoker in APPLICATION_FIRST mode (#9967))使 APPLICATION_FIRST 每次调用重检查服务发现调用器可用性,而非坚持过期偏好调用器
• 提交 819375bf44([3.0] Always get mapping from remote when migrate (#9988))表示迁移正确性依赖最新远程服务名映射
• 提交 fc0f88f4f2(Fix MigrationRule changing when load consumer default value (#13237))阻止默认值查找改变规则对象
此迁移机制是发现模式本身的金丝雀路径。与标签金丝雀或条件规则金丝雀不同,但有类似发布控制:百分比、阈值、延迟和强制。
Dubbo 无需名为 GrayRouter 或 CanaryRouter 的单类实现灰度发布。当前代码库通过多种可组合机制支持灰度/金丝雀行为:

模式 | 机制 | 注记 |
|---|---|---|
头/附件基金丝雀 | 条件路由 attachments[...] => provider-label | 声明式易诊断 |
参数基金丝雀 | 条件路由 arguments[n] => provider-label | 对租户或用户 ID 路由有用 |
提供者标签金丝雀 | 标签 v3 match 规则 | 从提供者参数构建标签分组 |
分层金丝雀 | 标签选择器 beta|team|partner | 非强制时回退更广标签 |
加权金丝雀 | 多目标条件路由 | 负载均衡前按权重选目标子集 |
Mesh 风格金丝雀 | 标准 Mesh 规则路由器 | 用虚拟服务 + 目标规则子集 |
发现模式金丝雀 | MigrationInvoker APPLICATION_FIRST + promotion | 从接口发现向应用发现滚动流量 |
关键区别是机制先过滤还是先选择加权目标:
• 条件/标签路由通常过滤到一子集
• 多目标和 Mesh 路由先按权重选目标子集,再返回该子集
• 路由子集产生后负载均衡仍发生

路由回答:
哪些提供者对该调用可用?
负载均衡回答:
哪一个提供者应处理本次尝试?
此区别对解释重要:
• 若路由返回 10 提供者,负载均衡从这 10 中选择
• 若路由返回 1 提供者,负载均衡实际绕过
• 若强制路由返回 0 提供者,集群层收到无提供者
• 若非强制路由无法匹配任何提供者,可返回原始列表,负载均衡继续如同规则未窄化流量
• failover 中,每次重试重新列表和重新路由再选择
未来路由变更应包含测试:
• 请求并发路由时 RouterChain 地址更新
• 目录与路由链间 BitList 源列表一致性
• 空路由结果的路由快照输出
• 条件路由 force=true 和 force=false 空结果行为
• 附件、参数、URL 参数、通配符和范围匹配器
• 多目标加权选择和子集大小与负载均衡交互
• 标签路由动态规则、静态标签、不带标签回退、force.tag 和 dubbo.tag=*
• 分层标签回退带和不带强制模式
• 脚本编译失败和执行失败回退
• 并发路由创建下脚本引擎缓存行为
• 带重复通知和偏好协议元数据的应用级服务发现
• 迁移 APPLICATION_FIRST 可用性回退和推广百分比
• 配置中心删除事件、格式错误规则和规则监听器清理