Dubbo 服务发现与路由的概念有什么不同?
结论先行:服务发现回答“有哪些实例可用”,路由回答“这次调用允许去哪些实例”。发现面向注册中心,产出全量地址列表;路由面向调用规则,在列表上做减法。两者之后才是负载均衡,回答“具体选哪一台”。
一、一条链路上的三次筛选
Registry 推送全量地址
│
▼
Directory(目录)──── 保存 invoker 列表,注册中心变更时刷新
│
▼
Router(路由)────── 条件路由 / 标签路由 / 脚本路由,过滤出候选集
│
▼
LoadBalance(负载均衡)── 从候选集里挑一个
│
▼
Invoker(真正发起调用)
三者串行执行,顺序不能颠倒:路由必须看到全量列表才能做灰度比例切分,负载均衡必须看到过滤后的列表才不会把流量打到被排除的机器上。
二、概念对比
| 维度 | 服务发现 | 服务路由 |
|---|---|---|
| 数据来源 | 注册中心推送 | 规则配置(控制台、配置中心、代码) |
| 作用对象 | 全部实例 | 候选实例子集 |
| 变更频率 | 随实例上下线变化 | 随发布、灰度、容灾演练变化 |
| 失效后果 | 拿不到地址,调用直接失败 | 流量打错机器,可能跨机房或打到灰度 |
| 典型实现 | RegistryDirectory + NotifyListener | ConditionRouter、TagRouter、ScriptRouter |
三、路由的三种常见形态
| 类型 | 规则表达 | 典型用途 |
|---|---|---|
| 条件路由 | 消费者条件 => 提供者条件,支持 host、method、region 等键 | 读写分离、黑白名单、同机房优先 |
| 标签路由 | 请求打标 tag=gray,只匹配同标签实例 | 金丝雀发布、全链路灰度 |
| 脚本路由 | 用脚本语言写匹配逻辑 | 复杂业务规则、临时活动流量隔离 |
条件路由示例(YAML 形式,等价的规则串以 => 分隔消费方与提供方条件):
# 只允许上海机房的消费者调用上海机房的提供者
force: false
enabled: true
runtime: true
conditions:
- "region=shanghai => region=shanghai"
force 决定“过滤后为空”时的行为:false 表示降级为不过滤(宁可跨机房也别失败),true 表示严格失败。生产上灰度场景常用 force=true,容灾场景常用 force=false。
标签路由在代码里的写法:
RpcContext.getClientAttachment().setAttachment("tag", "gray");
userService.getById(1L);
提供者需要在启动参数里声明自己属于哪个标签:-Ddubbo.provider.tag=gray。
四、规则生效的两个时机
| 时机 | 行为 | 风险 |
|---|---|---|
| 启动时 | 从注册中心拉取全量规则,缓存在本地 | 注册中心不可用则回退到本地缓存文件 |
| 运行期 | 订阅 routers/configurators,变更即通知 | 规则写错会立刻影响全部消费端 |
所以灰度规则上线前要在测试环境验证,并保留一键回滚;force=false 是最后一道保险。
常见追问
- 追问:路由和负载均衡能合并吗?不能。路由是“能不能去”,属于可用性约束;负载均衡是“去哪个更好”,属于性能优化,语义完全不同。
- 追问:路由规则存在哪里?Dubbo 把规则写到注册中心的
routers或configurators目录,消费端订阅后本地生效,所以规则变更不需要重启应用。 - 追问:为什么路由要在消费端做?在消费端做可以感知全量实例、按调用维度灵活切换,且不增加提供者负担;代价是每个消费端都要同步规则。
小结
- 发现是“拉名单”,路由是“按规则筛名单”,负载均衡是“从名单里挑一个”。
- 条件路由管机房与黑白名单,标签路由管灰度,脚本路由管临时复杂规则。
- 记住
force参数:它决定筛选结果为空时是报错还是降级。 - 路由规则通过注册中心下发,运行期可改,不必重启。