分布式微服务-ZooKeeper作为分布式锁
目录
分布式微服务–ZooKeeper作为分布式锁
看这篇博客之前可以先去了解博主的另一篇讲解ZooKeeper的博客:
1. 为什么需要分布式锁?
在分布式系统中,多个服务节点可能同时访问或修改同一份共享资源(例如数据库记录、缓存数据、文件等)。
如果不加限制,容易出现数据不一致或竞争条件。
因此,需要一种机制来保证:同一时刻只有一个节点能访问某资源。这就是分布式锁的意义。常见分布式锁实现方式:
- 基于 Redis
- 基于 ZooKeeper
- 基于 数据库
其中 ZooKeeper 实现分布式锁更安全可靠,因为它的核心是 强一致性 + 临时顺序节点机制。
2. ZooKeeper 的分布式锁原理
ZooKeeper 提供的 临时节点(Ephemeral) 和 有序节点(Sequential) 是实现分布式锁的关键。
核心思想:
创建锁节点:
所有客户端到某个固定路径(如/lock
)下创建一个 临时顺序节点,例如:/lock/lock_00000001 /lock/lock_00000002 /lock/lock_00000003
- 临时节点保证客户端宕机或断开时自动删除。
- 顺序节点保证多个客户端的排队顺序。
获取锁:
客户端判断自己创建的节点是否是 序号最小的节点。
- 如果是 → 获取锁成功。
- 如果不是 → 监听比自己小的前一个节点,等待其释放。
释放锁:
客户端执行完业务逻辑后,删除自己的节点 → 唤醒下一个等待者。3. 分布式锁实现步骤
假设我们要对某个共享资源
order
加锁。1)获取锁
- 客户端在
/lock/order
下创建临时顺序节点,例如:/lock/order/lock_00000010
- 获取
/lock/order
下所有子节点,并排序。- 如果自己是最小的节点 → 获得锁。
- 否则 → 监听自己前一个节点。
2)释放锁
- 客户端完成业务逻辑后,删除自己的节点。
- ZooKeeper 会通知下一个等待者。
3)异常情况
- 如果客户端宕机或与 ZooKeeper 断开连接,ZooKeeper 会自动删除临时节点,从而避免锁“死锁”。
4. ZooKeeper 分布式锁代码示例
基于 Curator(推荐)
Curator 是 ZooKeeper 的一个高级客户端,封装了分布式锁。

样例代码(本次使用的锁是****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(); } }
✅ 总结:
InterProcessMutex
是 Curator 提供的 可重入分布式锁,底层用 Zookeeper 的临时顺序节点实现。lock.acquire()
获取锁,获取不到会阻塞(或超时返回false
)。lock.release()
释放锁,必须写在finally
,避免异常导致死锁。- 这种方式可以模拟 多进程/多机器的并发安全,保证同一时刻只有一个客户端在修改票数。
5. ZooKeeper 分布式锁的优缺点
✅ 优点
- 强一致性:ZK 的数据一致性保证了锁的可靠性。
- 自动释放:客户端宕机,临时节点会自动删除,避免死锁。
- 公平性:顺序节点机制,保证先来先服务。
❌ 缺点
- 性能较低:每次加锁/解锁都需要与 ZK 通信,适合低并发场景。
- 部署复杂:需要搭建 ZooKeeper 集群。
- 羊群效应:节点删除时可能触发大量客户端通知(不过监听前一个节点可以缓解)。
6. 使用场景
- 订单系统(防止超卖)
- 分布式定时任务(同一时间只允许一个节点执行)
- 共享资源访问控制(文件、缓存等)
7. 总结
- ZooKeeper 分布式锁依赖 临时顺序节点 + watcher 机制。
- Curator 封装了常用的锁(
InterProcessMutex
),开发更方便。- 适合一致性要求高、并发量不是极高的业务场景。
- 高并发下更推荐 Redis 分布式锁(性能更好,但需要妥善解决可靠性问题)。