bigywater.tech
ZooKeeper · 88

用 ZooKeeper 做服务注册与发现

服务发现是 ZooKeeper 与 Dubbo、早期 Spring Cloud 的结合点:提供者建临时节点、消费者 watch 目录。这篇起几个真实的提供者进程,量上线与下线各要多久、下线期间消费者会发出多少失败请求、以及提供者「活着却被摘掉」之后会发生什么。

80 ms
新提供者上线到消费者看到
3.1 s
kill -9 后被摘除(期间 9 次请求失败,全部能靠重试挽回)
18 ms
主动注销后消费者摘除

注册与发现

实测 · 本机执行输出
  消费者看到的列表: ['127.0.0.1:7701', '127.0.0.1:7702', '127.0.0.1:7703', '127.0.0.1:7704', '127.0.0.1:7705']
  /svc/orders/127.0.0.1:7701 的数据: {"port": 7701, "weight": 1} (临时节点,ephemeralOwner ≠ 0: True )

注册信息用临时节点,所以提供者与 ZooKeeper 的会话断了节点会自己消失。数据模型细节见下一篇。

上线

实测 · 本机执行输出
  从启动进程(含 Python 启动、连 ZooKeeper、建节点)到消费者看到它:80 ms

下线:被摘除前请求还会打过来

实测 · 本机执行输出
  kill -9:消费者的列表在 3076 ms 后才去掉它;期间失败 9 次、成功 191 次;失败后立刻换一个实例重试成功 9 次
  SIGTERM(进程被终止、连接断开但没有主动注销):消费者的列表 3464 ms 后去掉它;失败 20 次(进程被终止时没有注销节点:连接断了,但会话要等到超时才过期,节点才消失)

摘除延迟等于会话超时(3 s 内加上一点调度)。这期间消费者仍可能向已死的实例发请求,所以消费者要做失败重试或熔断,不能只靠注册中心。

主动注销

实测 · 本机执行输出
  主动 delete 节点后消费者 17 ms 内去掉它(进程仍在运行但已不再被调用,可以先摘流量、再关进程)

活着却被摘掉

实测 · 本机执行输出
  起始列表里有 7711、7712、7713: True
  冻结 5 秒后列表里的这三个: [] (都因会话过期被摘除)
  解冻 6 秒后三个进程都还活着: True | 重新出现在列表里的: {'naive': False, 'rereg_sync': False, 'rereg_thread': True}

kazoo 状态监听器是在连接线程里回调的,如果在里面同步调用 create,我观察到节点没有被重新建立(我没有进一步查到根因,只确认了新开线程再调用就能成功)。无论用什么客户端,都要处理「会话过期后重新注册」。

分步演示

服务发现
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。提供者是独立的 Python 子进程(本机 TCP 小服务),消费者每 20 ms 随机选一个当前列表里的实例发请求,失败后换一个重试;会话超时 3 s
    • 没有验证:Dubbo / Spring Cloud 的实际注册行为(见第 101、106 篇)、消费者本地缓存列表在 ZooKeeper 整体不可用时的表现、权重与路由、注册中心自我保护
    京ICP备2026067502号-1京公网安备11010502063487号