Dubbo 是 ZooKeeper 最有名的使用者之一。这篇不凭印象写,真起一个 Dubbo 3.3.6 的提供者和消费者,看它在 ZooKeeper 里到底建了哪些节点,再读 ZookeeperRegistry 里注册那几行代码,最后杀进程、冻结进程、正常退出,量节点什么时候消失、调用是否失败。
dubbo
Greeter
configurators
providers
dubbo%3A%2F%2F<本机局域网IP>%3A20881%2FGreeter%3Fapplication%3Dprov-app%26deprecated%3Dfal [临时节点]
config
DUBBO_SERVICEDISCOVERY_MIGRATION
dubbo
mapping
Greeter
metadata
Greeter
1.0
provider
prov-app
services
prov-app
<本机局域网IP>:20881 [临时节点]
接口级节点名 URL 解码后: dubbo://<本机局域网IP>:20881/Greeter?application=prov-app&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=Greeter&methods=hi&prefer.serialization=hessian2,fastjson2&release=3.3.6&revision=1.0&service-name-mapping=true&side=provider×tamp=1790833142831&version=1.0
/services/prov-app 下实例节点的数据: {"name":"prov-app","id":"<本机局域网IP>:20881","address":"<本机局域网IP>","port":20881,"sslPort":null,"payload":{"@class":"org.apache.dubbo.registry.zookeeper.ZookeeperInstance","id":"<本机局域网IP>:20881","name":"prov-app","metadata":{"dubbo.endpoints":"[{\"port\":20881,\"protocol\":\"dubbo\"}]","d
/dubbo/mapping/Greeter 的数据: prov-app (接口名 → 应用名的映射)注册的代码很短:
org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java(dubbo-registry-zookeeper 3.3.6) public void doRegister(URL url) { try { checkDestroyed(); zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true), true); ... } private String toUrlPath(URL url) { return toCategoryPath(url) + PATH_SEPARATOR + URL.encode(url.toFullString()); }
URL.encode 把完整服务 URL 编码成节点名,所以节点名里没有 / 号(见注册中心数据模型)。create(path, dynamic, true) 里的第二个参数是 dynamic(默认 true),决定是临时节点;我没有测 dynamic=false(应该是持久节点,只读了代码)。
消费者启动后新增的节点:
consumers
consumer%3A%2F%2F<本机局域网IP>%2FGreeter%3Fapplication%3Dcons-app%26background%3Dfalse%26 [临时节点]
routers
consumer
cons-app
最近的调用: ['hi x from P1', 'hi x from P1', 'hi x from P1']消费者自己也注册,仅用于监控与治理,提供者不依赖它。
20 秒内的调用分布: {'P1': 60, 'P2': 34}
/dubbo/Greeter/providers 子节点数 = 2同一模块 util/CuratorFrameworkUtils.java protected static int DEFAULT_SESSION_TIMEOUT_MS = 60 * 1000; ... int sessionTimeoutMs = connectionURL.getParameter(SESSION_KEY, DEFAULT_SESSION_TIMEOUT_MS);
kill -9 后 /dubbo/Greeter/providers 下的 P1 节点在 16.1 s 后消失(Dubbo 默认注册中心 session 60 s,服务端 maxSessionTimeout = 20 × tickTime = 20 s) 这期间消费者共 83 次调用,失败 0 次(错误类型 []) —— 我的推断(没有进一步验证):进程被 kill -9 时操作系统关闭了它的 TCP 连接,Dubbo 消费者据此不再向它发请求,没有等 ZooKeeper 的通知 节点移除之后的 5 秒内失败次数: 0
这里 Dubbo 默认请求 60 s,服务端按 会话超时的协商规则截断。实测 cons 里这两个会话的 to=20000(session=3000 时是 3000)。
第 1 次:P1 节点在 6.8 s 后消失;期间调用 40 次,失败 0 次 第 2 次:P1 节点在 2.9 s 后消失;期间调用 21 次,失败 0 次 第 3 次:P1 节点在 3.0 s 后消失;期间调用 21 次,失败 0 次
冻结后 P1 的节点在 2.9 s 后消失;期间消费者调用 16 次,失败 1 次(错误类型 ['ERR RpcException']) 最后一次失败在冻结后 0.3 s;节点消失之后 3 秒内失败 0 次
SIGTERM 后 P2 的节点 0.02 s 消失;SIGTERM 后 4 秒内消费者调用 10 次,失败 0 次
三种「下线」方式对消费者的影响:正常退出(先注销)几乎无感;进程被杀(TCP 断开)时我观察到没有失败(推断是 Dubbo 自己发现了连接断开);进程冻结或网络断开(TCP 没断)才真正依赖 ZooKeeper 的会话过期。对照服务发现那篇自己写的消费者(没有这层保护,kill -9 时有请求失败)。