Zookeeper协调服务

5 minIntermediate2026/6/14

ZooKeeper架构、ZAB协议、Watcher机制、Leader选举与分布式协调应用。

1. ZooKeeper架构

ZooKeeper 是一个分布式协调服务,为分布式应用提供一致性管理、配置维护、组服务和命名等功能。

1.1 集群架构

┌──────────────────────────────────────────┐
│              Client                       │
└──────┬──────────┬──────────┬─────────────┘
       │          │          │
       ▼          ▼          ▼
┌──────────┐┌──────────┐┌──────────┐
│ Leader   ││Follower  ││Follower  │
│ (读写)   ││ (读+转发)││ (读+转发)│
│ 事务处理 ││ 投票参与 ││ 投票参与 │
└──────────┘└──────────┘└──────────┘
       │          │          │
       └──────────┼──────────┘

         ┌──────────────┐
         │  Quorum      │
         │  过半协议     │
         └──────────────┘

1.2 核心概念

概念说明
ZNode数据节点,似文件系统中的文件/目录
zxid事务ID,全局单调递增,标识操作顺序
epochLeader周期号,每次选举递增
Quorum法定人数,集群半数以上节点
Session客户端与服务器之间的会话

1.3 数据模型

ZooKeeper 的数据模型是树形命名空间,每个节点(ZNode)可以存储数据(默认1MB上限):

/
├── services
│   ├── service-a
│   │   └── instance-1  (data: "host1:8080")
│   │   └── instance-2  (data: "host2:8080")
│   └── service-b
├── config
│   └── db-config       (data: "jdbc:mysql://...")
└── leaders
    └── job-leader      (data: "node-3")

ZNode类型

说明创建方式
持久节点持久存储,客户端断开后不删除create /path
临时节点客户端会话结束自动删除create -e /path
持久顺序节点持久 + 自动递增序号后缀create -s /path
临时顺序节点临时 + 自动递增序号后缀create -s -e /path
容器节点最后一个子节点删除后自动删除create -c /path
TTL节点超时未修改自动删除create -t /path

2. ZAB协议

ZAB(ZooKeeper Atomic Broadcast)是 ZooKeeper 的核心一致性协议,保证所有事务按顺序广播到所有节点。

2.1 协议状态

                ┌──────────┐
                │ LOOKING  │ ← 选举中
                └────┬─────┘
                     │ 选举完成
            ┌────────┴────────┐
            ▼                 ▼
     ┌────────────┐    ┌────────────┐
     │ FOLLOWING  │    │ LEADING    │
     │ 跟随Leader │    │ 成为Leader │
     └────────────┘    └────────────┘

2.2 消息广播(Broadcast)

Leader 将客户端请求转化为事务提案(Proposal),通过两阶段提交广播:

Leader                Follower1    Follower2
  │                       │            │
  │── Proposal(zxid) ──→│            │
  │── Proposal(zxid) ──────────────→│
  │                       │            │
  │←── ACK(zxid) ───────│            │
  │←── ACK(zxid) ───────────────────│
  │                       │            │
  │  过半ACK,提交        │            │
  │── Commit(zxid) ────→│            │
  │── Commit(zxid) ────────────────→│

关键保证

  • 所有事务按 zxid 顺序提交
  • 过半节点ACK即可提交(不需要全部)
  • Leader崩溃时,已完成的事务不会丢失

2.3 崩溃恢复(Recovery)

当Leader崩溃或失去过半Follower时,进入崩溃恢复模式:

  1. 选举阶段:所有节点进入LOOKING状态,选举新Leader
  2. 发现阶段:新Leader与Follower同步事务日志
  3. 同步阶段:确保所有节点数据一致
  4. 广播阶段:新Leader开始处理客户端请求

选举约束

  • 新Leader必须拥有最完整的事务日志(最大zxid)
  • 已提交的事务不能丢失
  • 未提交的事务需要被丢弃

2.4 zxid结构

zxid=epoch32counter\text{zxid} = \text{epoch} \ll 32 \mid \text{counter}

部分位数说明
epoch高32位Leader周期号
counter低32位周期内事务计数

每次Leader选举,epoch递增,counter归零。

3. Watcher机制

Watcher 是 ZooKeeper 的事件通知机制,客户端可以在ZNode上注册监听器,当ZNode发生变化时收到通知。

3.1 事件

事件触发条件注册方法
NodeCreatedZNode被创建exists
NodeDeletedZNode被删除exists/getData/getChildren
NodeDataChanged数据变更exists/getData
NodeChildrenChanged子节点变更getChildren

3.2 Watcher特性

  • 一次性触发:Watcher触发后自动失效,需要重新注册
  • 有序性事件按zxid顺序触发,客户端看到的事件顺序与服务器一致
  • 轻量级:通知只包含事件型,不包含变更后的数据
// 注册Watcher
zk.exists("/config", new Watcher() {
    @Override
    public void process(WatchedEvent event) {
        if (event.getType() == EventType.NodeDataChanged) {
            // 重新读取数据并重新注册Watcher
            byte[] data = zk.getData("/config", this, null);
        }
    }
});

4. Leader选举

4.1 选举算法(FastLeaderElection)

每个节点投票: (self_id, self_zxid)

Round 1:
  Node1 投票: (1, zxid_1) → 发送给所有节点
  Node2 投票: (2, zxid_2) → 发送给所有节点
  Node3 投票: (3, zxid_3) → 发送给所有节点

比较规则:
  1. 比较 epoch(大的优先)
  2. 比较 zxid(大的优先)
  3. 比较 myid(大的优先)

假设 zxid_3 > zxid_2 > zxid_1:
  Node1 收到 (2,zxid_2) → 更新投票为 (2,zxid_2)
  Node1 收到 (3,zxid_3) → 更新投票为 (3,zxid_3)
  Node2 收到 (3,zxid_3) → 更新投票为 (3,zxid_3)

  Node3 获得过半投票 → 成为Leader

4.2 分布式锁实现

利用临时顺序节点实现公平锁:

1. 在 /locks 下创建临时顺序节点 → /locks/lock-0000000001
2. 获取 /locks 下所有子节点并排序
3. 如果自己是最小节点 → 获得锁
4. 如果不是 → Watch前一个节点的删除事件
5. 前一个节点删除 → 重新检查是否获得锁
6. 释放锁:删除自己的临时节点
public class DistributedLock {
    private final ZooKeeper zk;
    private final String lockPath = "/locks";
    private String currentLock;

    public void lock() throws Exception {
        // 创建临时顺序节点
        currentLock = zk.create(lockPath + "/lock-",
            new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);

        // 获取所有子节点
        List<String> children = zk.getChildren(lockPath, false);
        Collections.sort(children);

        // 检查是否是最小节点
        String currentNode = currentLock.substring(lockPath.length() + 1);
        int index = children.indexOf(currentNode);

        if (index == 0) {
            return; // 获得锁
        }

        // 等待前一个节点删除
        String prevNode = lockPath + "/" + children.get(index - 1);
        final CountDownLatch latch = new CountDownLatch(1);
        zk.exists(prevNode, event -> {
            if (event.getType() == EventType.NodeDeleted) {
                latch.countDown();
            }
        });
        latch.await();
    }

    public void unlock() throws Exception {
        zk.delete(currentLock, -1);
    }
}

5. 典型应用场景

场景实现方式说明
服务注册与发现临时节点 + Watcher服务上线创建临时节点,下线自动删除
分布式锁临时顺序节点公平锁实现
配置中心持久节点 + Watcher配置变更通知
Leader选举临时节点主备切换
命名服务顺序节点全局唯一ID生成
集群管理临时节点成员管理与存活检测