bigywater.tech
ZooKeeper · 101

Dubbo 注册中心源码

Dubbo 是 ZooKeeper 最有名的使用者之一。这篇不凭印象写,真起一个 Dubbo 3.3.6 的提供者和消费者,看它在 ZooKeeper 里到底建了哪些节点,再读 ZookeeperRegistry 里注册那几行代码,最后杀进程、冻结进程、正常退出,量节点什么时候消失、调用是否失败。

2 套
Dubbo 3 同时做接口级(/dubbo)与应用级(/services)注册
16 s
默认 session 下 kill -9 后节点消失(60 s 被服务端截到 20 s),消费者失败 0 次
0.02 s
SIGTERM 正常退出时节点消失(shutdown hook 先注销)

Dubbo 在 ZooKeeper 里建了什么

实测 · 本机执行输出
  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&timestamp=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

杀进程 vs session

同一模块 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)。

缩短 session

实测 · 本机执行输出
  第 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 时有请求失败)。

分步演示

Dubbo 注册中心
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。Dubbo 3.3.6(dubbo-registry-zookeeper 3.3.6 源码来自 Maven Central 的 sources jar)+ Curator 5.1.0(Dubbo 依赖自带)+ ZooKeeper 3.9.6,tickTime=1000;提供者与消费者是独立 JVM 进程,消费者设 retries=0、timeout=1000 ms;输出里的本机局域网 IP 已替换为 <本机局域网IP>
    • 没有验证:dynamic=false 的持久注册、Dubbo 的服务端配置中心(/dubbo/config)与动态配置、元数据中心的内容含义、Dubbo 2.7 的行为、消费者为什么在冻结提供者时只失败 1 次(只测了 1 次)、多个注册中心、权重调整
    京ICP备2026067502号-1京公网安备11010502063487号