有赞一面:ZooKeeper是如何保证创建的节点是唯一的?

662 阅读4分钟

文章内容收录到个人网站,方便阅读:hardyfish.top/

资料分享

从Paxos到Zookeeper 分布式一致性原理与实践:

1. Zookeeper 保证唯一性的机制

1.1 所有写请求由 Leader 处理

Zookeeper 集群中,所有写请求都会由 Leader 节点处理。

即使客户端请求写操作到达 Follower 节点,也会被转发给 Leader,由 Leader 进行写入处理。

这种集中式写入机制避免了分布式环境中多个节点同时修改同一数据的冲突问题。

1.2 Leader 内部的线程安全机制

在 Leader 节点处理写操作时,Zookeeper 通过以下两种方式保证节点创建的唯一性:

  1. 父节点加锁 在节点创建时,首先会对父节点加锁,确保同一时刻只有一个线程可以操作该父节点。
  2. ConcurrentHashMap Zookeeper 使用 ConcurrentHashMap 存储所有节点的信息,节点的创建、更新、删除操作都通过线程安全的 put 方法完成,保证并发安全。

2. 源码分析

2.1 加锁(synchronized 锁)

DataTree#createNode 方法中,为了避免多个线程同时创建同一父节点下的子节点,首先对父节点加锁。

synchronized (parent) {
    // 检查是否已存在子节点
    if (parent.getChildren().contains(childName)) {
        throw new NodeExistsException();
    }
    // 创建子节点
    DataNode child = new DataNode(data, acls, stat);
    parent.addChild(childName);
    nodes.put(path, child);
}

加锁范围只限于当前父节点,减少了锁的粒度。确保在锁内,只有一个线程可以操作父节点的子节点列表,避免并发创建冲突。

2.2 判断节点是否存在

在锁内,首先检查父节点的子节点列表中是否已经存在待创建的节点。

如果已存在,抛出 NodeExistsException 异常,阻止重复创建。

2.3 使用 ConcurrentHashMap 存储节点

所有节点的数据都存储在一个全局的 NodeHashMapImpl 中,该结构是基于 ConcurrentHashMap 实现的。

public DataNode put(String path, DataNode node) {
    DataNode oldNode = nodes.put(path, node);
    addDigest(path, node);
    if (oldNode != null) {
        removeDigest(path, oldNode);
    }
    return oldNode;
}

ConcurrentHashMap 的作用是确保多个线程并发访问节点时的线程安全性。

即使两个线程同时尝试写入相同的键,最终也只有一个写入会成功。

3. Zookeeper 唯一性保证的流程

  1. 写请求路由到 Leader 所有客户端的写请求都会被路由到 Leader 节点,由 Leader 负责处理。
  2. 加锁保护父节点 在 Leader 上处理写请求时,会对目标节点的父节点加锁,确保并发安全。
  3. 检查子节点是否已存在 在锁内检查父节点的子节点列表,如果子节点已存在,则抛出异常。
  4. 创建节点 如果子节点不存在,则创建新节点,并将其加入全局的 NodeHashMap
  5. 同步到 Follower Leader 将节点创建操作通过 ZAB 协议同步到所有 Follower 节点,确保集群一致性。

4. 顺序节点的唯一性

对于顺序节点(如持久顺序节点和临时顺序节点),除了上述机制外,Zookeeper 还通过以下方式保证唯一性:

  1. 父节点维护子节点的创建顺序 每个父节点会记录子节点的创建顺序。
  2. 自动追加编号 在创建顺序节点时,Zookeeper 会自动为节点名添加一个递增的数字后缀,例如 /node0000000000

这种机制确保即使多个客户端并发创建顺序节点,也不会产生重复。

5. 核心设计总结

Zookeeper 在节点创建时,通过以下机制保证唯一性:

  1. 集中式写入:所有写操作由 Leader 处理。
  2. 加锁机制:对父节点加锁,确保同一时刻只有一个线程操作。
  3. 重复校验:在创建前检查是否已存在目标节点。
  4. 线程安全的数据结构:使用 ConcurrentHashMap 存储节点信息,保证并发安全。
  5. 顺序节点的附加机制:自动维护子节点的创建顺序,生成唯一的编号。

这种多层次的保护机制确保 Zookeeper 的节点在分布式环境下能安全、高效地保持唯一性。