如何通过 Zookeeper 实现服务注册与发现?

#中间件#面试八股

如何通过 Zookeeper 实现服务注册与发现?

结论先行:提供者在 /服务名/providers 下写临时节点,节点名就是可调用的 URL;消费者 watch 该目录的子节点变化,拿到全量列表并缓存在本地。会话超时或主动关闭时临时节点消失,即完成“自动下线”。

一、目录约定

/dubbo/com.demo.UserService
├── providers
│   ├── dubbo://10.0.0.1:20880/com.demo.UserService?timeout=3000&weight=100
│   └── dubbo://10.0.0.2:20880/com.demo.UserService?timeout=3000&weight=100
├── consumers
│   └── consumer://10.0.0.9/com.demo.UserService?application=web
├── routers
│   └── condition://0.0.0.0/com.demo.UserService?category=routers&rule=...
└── configurators
    └── override://0.0.0.0/com.demo.UserService?timeout=500

把“参数”放在节点路径而不是节点数据里,好处是 ls 一次就能拿到全部信息,不用逐个 get。

二、注册与订阅时序

Provider                          ZK                      Consumer
   │  create EPHEMERAL              │                          │
   │  /providers/dubbo://...        │                          │
   ├───────────────────────────────>│                          │
   │                               │<──── getChildren + watch ─┤
   │                               │──── 返回列表 + 注册 watch ─>│
   │                               │                          │ 本地缓存
   │                               │<──── 调用(直连,不经 ZK)─┤
   │  心跳(会话保活)              │                          │
   ├───────────────────────────────>│                          │
   │  进程崩溃 / kill -9            │                          │
   │        (不再续约会话)         │                          │
   │                               │── 会话超时,删临时节点 ───>│
   │                               │── NodeChildrenChanged ───>│
   │                               │                          │ 重新 getChildren

关键点:调用流量不走 ZK,ZK 只负责名单;因此 ZK 短暂不可用不会立刻中断已有调用。

三、会话与超时的调优

# 服务端
tickTime=2000
maxSessionTimeout=40000
minSessionTimeout=4000

# 客户端(Curator)
sessionTimeoutMs=10000
connectionTimeoutMs=5000
参数过小过大
sessionTimeout网络抖动就误摘除,实例反复上下线宕机后长时间仍有流量打向死实例
connectionTimeout重连风暴启动期等待过久

经验值:会话超时取 10~30 秒,配合优雅停机主动反注册,避免只依赖超时清理。

四、消费者侧的两个必备兜底

// 1. 本地文件缓存:ZK 不可用时仍能启动并调用
//    Dubbo 默认把地址列表写入 ~/.dubbo/dubbo-registry-*.cache
// 2. 列表为空时不要清空可用地址,保留上一次快照并告警
if (invokers.isEmpty()) {
    log.warn("empty provider list, keep last snapshot");
    return lastSnapshot;
}

常见追问

  • 追问:为什么用临时节点而不是持久节点加心跳字段?临时节点由服务端在会话失效时自动清理,不依赖客户端自觉,避免“僵尸节点”长期占位。
  • 追问:ZK 挂了新实例还能注册吗?不能。新实例无法被发现,但老实例之间的调用不受影响,这就是“降级可用”。
  • 追问:和 Nacos 的差别?Nacos 用长连接推送 + 健康检查,注册数据可按应用聚合,且支持配置管理;ZK 是纯目录 + watch,节点数随“接口 × 实例”膨胀。

小结

  • 临时节点 + 会话 = 自动上下线;watch + 本地缓存 = 动态名单。
  • 路径携带参数,一次 ls 拿全量,减少往返。
  • 调用直连,ZK 只做名单,所以 ZK 抖动不等于业务中断。
  • 会话超时是双刃剑,配合优雅停机才能既快又准。
笔记加载中…