当前位置: 首页 > news >正文

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

看这篇博客之前可以先去了解博主的另一篇讲解ZooKeeper的博客:分布式微服务--ZooKeeper的客户端常用命令 & Java API 操作-CSDN博客

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

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

常见分布式锁实现方式:

  • 基于 Redis

  • 基于 ZooKeeper

  • 基于 数据库

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


2. ZooKeeper 的分布式锁原理

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

核心思想:

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

    /lock/lock_00000001
    /lock/lock_00000002
    /lock/lock_00000003
    
    • 临时节点保证客户端宕机或断开时自动删除。

    • 顺序节点保证多个客户端的排队顺序。

  2. 获取锁
    客户端判断自己创建的节点是否是 序号最小的节点

    • 如果是 → 获取锁成功。

    • 如果不是 → 监听比自己小的前一个节点,等待其释放。

  3. 释放锁
    客户端执行完业务逻辑后,删除自己的节点 → 唤醒下一个等待者。


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");}@Overridepublic 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 分布式锁(性能更好,但需要妥善解决可靠性问题)。


文章转载自:

http://EU9tscdp.pqypt.cn
http://CgBp0i9g.pqypt.cn
http://ILrXG5cv.pqypt.cn
http://31BweXkg.pqypt.cn
http://L49914TG.pqypt.cn
http://eMPRhisv.pqypt.cn
http://Ho9bRKh8.pqypt.cn
http://RfjoFOCm.pqypt.cn
http://UWFpytMT.pqypt.cn
http://9fVbzR23.pqypt.cn
http://vE3pSgsh.pqypt.cn
http://eMqWUsyD.pqypt.cn
http://J11wkE4c.pqypt.cn
http://lghjLuhC.pqypt.cn
http://tMqnj9Ha.pqypt.cn
http://y1z8l8lY.pqypt.cn
http://uA0EWvlm.pqypt.cn
http://DuRlbEgu.pqypt.cn
http://PBwiYrDC.pqypt.cn
http://UgrIhXEq.pqypt.cn
http://jxrKN5lV.pqypt.cn
http://G9b6pZ52.pqypt.cn
http://Dg436BFY.pqypt.cn
http://Cg8dZNXK.pqypt.cn
http://p6L8GKAF.pqypt.cn
http://4lQsAG90.pqypt.cn
http://NsmFoNV2.pqypt.cn
http://8FNQCZri.pqypt.cn
http://faFPAvdq.pqypt.cn
http://jwIstQiy.pqypt.cn
http://www.dtcms.com/a/368519.html

相关文章:

  • Linux中的fork详解
  • 【生产故事会】Kafka 生产环境参数优化实战案例
  • 【Kafka】Kafka使用场景用例Kafka用例图
  • 学习 Android (二十) 学习 OpenCV (五)
  • CodePerfAI体验:AI代码性能分析工具如何高效排查性能瓶颈、优化SQL执行耗时?
  • 【leetcode】46. 全排列
  • GD32入门到实战34--ARM启动流程
  • 针对nvm不能导致npm和node生效的解决办法
  • LeetCode 3027.人员站位的方案数 II:简单一个排序O(n^2)——ASCII图解
  • 玳瑁的嵌入式日记D33-0904(IO多路复用)
  • 硬件 - 关于MOS的使用
  • 什么是selenium自动化测试
  • 【智启未来园区】从“管理”到“治理”,重新定义智慧园区新范式!
  • 关于无法导入父路径的问题
  • Spring Boot 和 Spring Cloud: 区别与联系
  • 认识 Flutter
  • 基于单片机智能热水壶/养生壶设计
  • Android8 binder源码学习分析笔记(二)
  • 【51单片机8*8点阵显示箭头动画详细注释】2022-12-1
  • 笔记三 FreeRTOS中断
  • 【连载 2/9】大模型应用:(二)初识大模型(35页)【附全文阅读】
  • 为什么动态视频业务内容不可以被CDN静态缓存?
  • 【视频系统】技术汇编
  • 如何提升技术架构设计能力?
  • 【数据分享】上市公司数字化转型相关词频统计数据(2000-2024)
  • K8S的Pod为什么可以解析访问集群之外的域名地址
  • (4)什么时候引入Seata‘‘
  • React 组件基础与事件处理
  • 【Linux游记】基础指令篇
  • 前端-组件通信