ZooKeeper临时顺序节点实现锁

wen java案例 3

本文目录导读:

ZooKeeper临时顺序节点实现锁

  1. 核心概念回顾
  2. 实现原理与流程
  3. 代码示例 (Java + Apache Curator)
  4. 核心原理对比:临时顺序节点 vs 临时节点
  5. 优缺点分析
  6. 应用场景

ZooKeeper 利用临时顺序节点可以实现一套高效且公平的分布式锁(通常称为 “排他锁”“写锁” )。

核心思想是:每个客户端尝试加锁时,在锁路径下创建一个临时顺序节点,通过比较自己创建的节点序号是否为当前路径下最小的,来判断是否获得锁,如果没获得锁,则监听前一个节点的删除事件。

以下是基于临时顺序节点实现分布式锁的完整原理、流程和代码示例。

核心概念回顾

  • 临时节点 (Ephemeral):客户端会话断开后,节点自动删除,有效防止死锁(客户端崩溃锁自动释放)。
  • 顺序节点 (Sequential):ZooKeeper 自动在节点名后追加一个全局唯一且递增的序号(如 lock-0000000001)。
  • Watch 机制:客户端可以监听节点的变化(如删除、数据变化)。

实现原理与流程

假设锁的根节点为 /locks

  1. 创建锁节点:每个想获取锁的客户端,在 /locks 下创建临时顺序节点

    • 客户端 A 创建 /locks/lock-0000000001
    • 客户端 B 创建 /locks/lock-0000000002
    • 客户端 C 创建 /locks/lock-0000000003
  2. 判断获取锁:客户端获取 /locks 下的所有子节点列表,并按序号排序。

    • 如果自己创建的节点是列表中序号最小的节点,则成功获取锁。
    • 如果自己不是最小的,说明锁已被其他客户端持有。
  3. 等待与监听

    • 如果未获取到锁,客户端不会盲目轮询,而是监听比自己节点序号小的最后一个节点(即前一个节点)。
    • 客户端 B 监听 /locks/lock-0000000001;客户端 C 监听 /locks/lock-0000000002
    • 这种机制形成了一个等待队列,保证了公平性(FIFO,先进先出)。
  4. 释放锁

    • 正常释放:客户端处理完业务逻辑后,主动删除自己创建的临时节点。
    • 异常释放:客户端会话过期或宕机,ZooKeeper 自动删除该临时节点。
  5. 唤醒后续节点

    • 客户端 A 释放锁(删除 /locks/lock-0000000001)。
    • 客户端 B 监听到 A 节点被删除的事件,重新执行步骤 2,检查自己是否为最小序号节点,此时它是最小的,因此成功获取锁。

代码示例 (Java + Apache Curator)

在实际开发中,强烈建议使用 Curator 框架,它已经将上述流程封装成优雅的 InterProcessMutex,并处理了各种边界情况(如羊群效应、惊群效应等)。

虽然问题要求实现原生逻辑,但出于实用性,这里提供 Curator 的使用方式:

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.concurrent.TimeUnit;
public class ZkLockExample {
    private static final String ZK_ADDRESS = "127.0.0.1:2181";
    private static final String LOCK_PATH = "/locks/my_lock";
    public static void main(String[] args) throws Exception {
        // 1. 创建 Curator 客户端
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                ZK_ADDRESS,
                new ExponentialBackoffRetry(1000, 3)
        );
        client.start();
        // 2. 创建分布式锁(本质就是临时顺序节点)
        InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);
        try {
            // 3. 获取锁(支持超时)
            if (lock.acquire(10, TimeUnit.SECONDS)) {
                System.out.println("获取锁成功,执行业务逻辑...");
                // 模拟业务操作
                Thread.sleep(5000);
            } else {
                System.out.println("获取锁失败,超时!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 4. 释放锁
            if (lock.isAcquiredInThisProcess()) {
                lock.release();
                System.out.println("释放锁成功");
            }
        }
        // 关闭客户端
        client.close();
    }
}

核心原理对比:临时顺序节点 vs 临时节点

特性 临时顺序节点 (推荐) 临时节点 (不推荐)
公平性 公平(FIFO),按请求顺序排队 不公平,抢锁顺序不确定
羊群效应 ,只监听前一个节点(链式监听) ,所有等待者监听同一个锁节点,释放时惊群
性能 高(O(n) 仅监听一个节点) 低(O(N^2) 所有节点同时被唤醒)
实现复杂度 稍复杂(需要排序和链式监听) 简单,但性能差
死锁处理 自动删除(临时 + 会话超时) 自动删除(临时 + 会话超时)

优缺点分析

优点:

  • 公平锁:严格按照请求顺序获取锁,避免了线程“饥饿”问题。
  • 避免羊群效应:每个客户端只监听前一个节点,锁释放时只有一个客户端被唤醒,而不是所有客户端都去抢锁。
  • 没有死锁:利用临时节点特性,客户端崩溃会自动释放锁,服务端无需额外处理。
  • 高可用:ZooKeeper 集群保证锁服务的高可用。

缺点:

  • 性能瓶颈:每次锁操作都需要与 ZooKeeper 集群进行多次网络通信(创建节点、获取子节点列表、设置监听),在高并发场景下,ZooKeeper 可能成为瓶颈(吞吐量通常低于 Redis)。
  • 需考虑会话超时:如果业务执行时间过长,超过 ZooKeeper 会话超时时间,锁会被自动释放,导致其他客户端获取到锁(可能产生并发问题),需要权衡业务耗时和会话超时时间。
  • 实现复杂度:相比 Redis 的 Redlock 或简单的 SETNX,手动实现原生 API 稍复杂(Curator 已解决此问题)。

应用场景

  • 需要严格公平锁的场景:如任务调度、全局 ID 生成、资源顺序访问。
  • 对锁可靠性要求极高的场景:如金融交易、库存扣减、关键配置更新。
  • 对高吞吐量要求不极端,但对一致性要求高的场景

使用 ZooKeeper 临时顺序节点实现分布式锁:

  1. 核心机制:创建临时顺序节点 + 监听前一个节点。
  2. 最大优势:公平、避免羊群效应、自动释放(安全)。
  3. 工业推荐:直接使用 Apache CuratorInterProcessMutex,生产验证、简单可靠。
  4. 注意事项:合理设置会话超时时间,避免业务未完成锁被提前释放。

抱歉,评论功能暂时关闭!