目录

分布式微服务-ZooKeeper作为分布式锁

分布式微服务–ZooKeeper作为分布式锁

看这篇博客之前可以先去了解博主的另一篇讲解ZooKeeper的博客:

1. 为什么需要分布式锁?

https://i-blog.csdnimg.cn/direct/df55b6bc37d34770be21ca0f98928020.png

在分布式系统中,多个服务节点可能同时访问或修改同一份共享资源(例如数据库记录、缓存数据、文件等)。
如果不加限制,容易出现数据不一致或竞争条件。
因此,需要一种机制来保证:同一时刻只有一个节点能访问某资源。这就是分布式锁的意义。

常见分布式锁实现方式:

  • 基于 Redis
  • 基于 ZooKeeper
  • 基于 数据库

其中 ZooKeeper 实现分布式锁更安全可靠,因为它的核心是 强一致性 + 临时顺序节点机制


2. ZooKeeper 的分布式锁原理

https://i-blog.csdnimg.cn/direct/5a32cc368a6f4860ac89a66432788b2d.png

ZooKeeper 提供的 临时节点(Ephemeral)有序节点(Sequential) 是实现分布式锁的关键。

核心思想:

  1. 创建锁节点
    所有客户端到某个固定路径(如 /lock)下创建一个 临时顺序节点,例如:

    /lock/lock_00000001
    /lock/lock_00000002
    /lock/lock_00000003
    • 临时节点保证客户端宕机或断开时自动删除。
    • 顺序节点保证多个客户端的排队顺序。
  2. 获取锁
    客户端判断自己创建的节点是否是 序号最小的节点

    • 如果是 → 获取锁成功。
    • 如果不是 → 监听比自己小的前一个节点,等待其释放。
  3. 释放锁
    客户端执行完业务逻辑后,删除自己的节点 → 唤醒下一个等待者。


3. 分布式锁实现步骤

https://i-blog.csdnimg.cn/direct/5a32cc368a6f4860ac89a66432788b2d.png

假设我们要对某个共享资源 order 加锁。

1)获取锁

  • 客户端在 /lock/order 下创建临时顺序节点,例如:
    /lock/order/lock_00000010
  • 获取 /lock/order 下所有子节点,并排序。
  • 如果自己是最小的节点 → 获得锁。
  • 否则 → 监听自己前一个节点。

2)释放锁

  • 客户端完成业务逻辑后,删除自己的节点。
  • ZooKeeper 会通知下一个等待者。

3)异常情况

  • 如果客户端宕机或与 ZooKeeper 断开连接,ZooKeeper 会自动删除临时节点,从而避免锁“死锁”。

4. ZooKeeper 分布式锁代码示例

https://i-blog.csdnimg.cn/direct/abd30576f71d4deca66203efd28bb80c.png

基于 Curator(推荐)

Curator 是 ZooKeeper 的一个高级客户端,封装了分布式锁。

![](https://i-blog.csdnimg.cn/direct/11104ca1df66425e822bdab5db0d93f0.png)

样例代码(本次使用的锁是****InterProcessMutex):

xml

  <!--curator-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.0.0</version>
        </dependency>

代码


/**
 * 模拟12306售票系统 —— 使用 Zookeeper 分布式锁保证并发安全
 */
public class Ticket12306 implements Runnable{

    // 模拟数据库中的票数
    private int tickets = 10;

    // 分布式可重入锁对象(Curator 提供)
    private InterProcessMutex lock ;

    public Ticket12306(){
        // 1. 定义重试策略:初始等待时间 3 秒,最多重试 10 次
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000, 10);

        // 2. 通过 builder 模式创建 CuratorFramework 客户端
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString("127.0.0.1:2181") // Zookeeper 连接地址
                .sessionTimeoutMs(60 * 1000) // 会话超时时间
                .connectionTimeoutMs(15 * 1000) // 连接超时时间
                .retryPolicy(retryPolicy) // 指定重试策略
                .build();

        // 3. 开启连接
        client.start();

        // 4. 创建分布式锁对象,指定锁的路径(ZK 节点)
        //   不同客户端只要路径一样,就能实现互斥
        lock = new InterProcessMutex(client,"/lock");
    }

    @Override
    public void run() {
        while(true){
            try {
                // 1. 尝试获取锁,最多等待 3 秒
                lock.acquire(3, TimeUnit.SECONDS);

                // 2. 拿到锁后执行业务逻辑 —— 卖票
                if(tickets > 0){
                    System.out.println(Thread.currentThread()+":" + tickets);
                    Thread.sleep(100); // 模拟业务处理耗时
                    tickets--; // 卖出一张票
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 3. 无论如何,最后都要释放锁,避免死锁
                try {
                    lock.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

测试:

/**
 * 测试类:模拟多个客户端同时抢票
 */
public class LockTest {

    public static void main(String[] args) {
        // 创建一个 Ticket12306 对象(共享 10 张票)
        Ticket12306 ticket12306 = new Ticket12306();

        // 创建两个线程,模拟两个不同的售票平台(携程、飞猪)
        Thread t1 = new Thread(ticket12306,"携程");
        Thread t2 = new Thread(ticket12306,"飞猪");

        // 启动线程,同时卖票
        t1.start();
        t2.start();
    }
}

总结:

  1. InterProcessMutex 是 Curator 提供的 可重入分布式锁,底层用 Zookeeper 的临时顺序节点实现。
  2. lock.acquire() 获取锁,获取不到会阻塞(或超时返回 false)。
  3. lock.release() 释放锁,必须写在 finally,避免异常导致死锁。
  4. 这种方式可以模拟 多进程/多机器的并发安全,保证同一时刻只有一个客户端在修改票数。

5. ZooKeeper 分布式锁的优缺点

✅ 优点

  • 强一致性:ZK 的数据一致性保证了锁的可靠性。
  • 自动释放:客户端宕机,临时节点会自动删除,避免死锁。
  • 公平性:顺序节点机制,保证先来先服务。

❌ 缺点

  • 性能较低:每次加锁/解锁都需要与 ZK 通信,适合低并发场景。
  • 部署复杂:需要搭建 ZooKeeper 集群。
  • 羊群效应:节点删除时可能触发大量客户端通知(不过监听前一个节点可以缓解)。

6. 使用场景

  • 订单系统(防止超卖)
  • 分布式定时任务(同一时间只允许一个节点执行)
  • 共享资源访问控制(文件、缓存等)

7. 总结

  • ZooKeeper 分布式锁依赖 临时顺序节点 + watcher 机制
  • Curator 封装了常用的锁(InterProcessMutex),开发更方便。
  • 适合一致性要求高、并发量不是极高的业务场景。
  • 高并发下更推荐 Redis 分布式锁(性能更好,但需要妥善解决可靠性问题)。