【项目深入】三、分布式锁

1、Zookeeper

1.1 原理步骤

  1. 核心机制:临时顺序节点(Ephemeral Sequential Nodes)
    • 所有竞争锁的客户端在同一个父路径(如 /locks)下,创建临时顺序子节点,例如:
      Client A → /locks/lock-0000000001
      Client B → /locks/lock-0000000002
      Client C → /locks/lock-0000000003
    • 节点名中的 10 位递增序号由 ZooKeeper 服务端自动生成,保证全局唯一且有序。
  2. 获取锁的判断逻辑,每个客户端创建完自己的节点后,
    • 列出 /locks 下所有子节点并按字典序排序。
    • 如果自己的节点是序号最小的,则立即获得锁,进入临界区。
    • 否则,找到自己节点的前一个节点(直接前驱),并对其注册一个 Watcher 监听删除事件(NodeDeleted)
  3. 等待与唤醒机制(避免羊群效应)
    • 客户端只监听 自己的直接前驱节点,而不是最小节点。
    • 当前驱节点被释放(因客户端主动删除或会话超时自动消失),ZooKeeper 仅通知该等待者。
    • 被通知的客户端重新检查:若自己现在是最小节点,则获得锁;否则继续监听新的前驱。
  4. 锁的释放
    • 主动释放:客户端调用 delete() 删除自己的临时节点。
    • 被动释放:客户端崩溃或网络断开导致会话超时,ZooKeeper 自动清理其临时节点,锁自动释放,无死锁风险。

1.2 数据模型

  • 层级结构的数据模型,ZNode,唯一路径,可以存储少量数据,持久,临时,顺序类型
  • ZooKeeper 服务端内部维护一个全局单调递增的计数器,用于为顺序节点分配唯一后缀(如 /locks/lock-0000000001)。父节点的 cversion 属性记录其子节点变更次数,用于支持 Watcher 和原子操作。

1.3 ZAB

  • ZooKeeper Atomic Broadcast,原子广播协议
  • 一致性和高可用
  • 崩溃恢复,当集群启动或者Leader宕机的时候,会触发ZAB,重新选举Leader,根据zxid和myid,新Leader和Follower同步最新的事务日志,确保状态一致
  • 消息广播,所有的写请求都由Leader处理,Leader将请求转换为事务提案,广播给所有Follower,Follower收到后返回ack,leader再提交事务广播
  • ZooKeeper 采用 WAL(预写日志)机制:所有写操作先持久化到事务日志,再更新内存状态,并定期生成快照,确保崩溃后可恢复

1.4 Watcher机制

  • 客户端可以对某个ZNode注册Watcher
  • 当这个节点变更/新增/删除,ZooKeeper会异步通知客户端
  • 只能使用一次,在使用就需要重新注册

1.5 session管理

  • 客户端连接ZooKeeper有一个唯一的sessionId
  • 会话有超时时间,期间需定期发送心跳维持链接
  • 使用了Java NIO
  • ZooKeeper 的临时节点依赖 Session,Session 超时时间需合理配置(通常 10~30s)。

2、Redis

用Redission完成分布式加锁的步骤

  • 可重入锁
  • 自动续期(watchdog 机制)
  • 联锁(MultiLock)、红锁(RedLock)
  • 公平锁、读写锁等
  • Redisson 公平锁通过 ZSet 维护按时间排序的等待队列,并利用 Redis Pub/Sub 通知机制实现 FIFO 唤醒,从而保证加锁顺序的公平性。

2.1 原理步骤

  1. 使用 SET lock_key unique_value NX PX expire_time 原子命令加锁,确保加锁和设置 TTL 是原子的,避免死锁。
  2. 执行完任务后,通过lua脚本判断是否是当前值,并释放锁

2.2 可能有的问题

  • 如果业务还没执行完,锁的时间要到期了,就用watchdog 看门狗自动延长时间
  • 锁被其他客户端误删,加锁的时候,要验证value,确保value是本线程加的
  • Redis主从切换导致锁丢失

2.3 watchdog原理

  • 其实就是后台开启了一个定时任务,Redission在加锁时,同时保存了锁的名称,加锁的线程id,加锁使用的唯一标识,以及重入次数,所以会有一个定时任务不断去查询这个线程的锁是否释放了,没有的话,继续延期
  • 所以就算业务阻塞了,也一样可以延期
  • 如果因 Full GC 或线程调度问题导致 Watchdog 未能及时续期,锁可能提前过期。此时需依赖业务幂等性或数据库唯一约束兜底
  • 开启方式就是直接操作Redission的lock.lock,关闭就是lock.unlock

2.4 Redis主从切换导致的锁丢失

  • RedLock 试图通过多数派机制提升可靠性,但在网络分区或时钟漂移等极端情况下仍可能失效。官方已不将其作为强一致方案推荐,仅适用于'尽力而为'的场景。
  • 因为Redis是AP模式,如果该问题频发,只能切换为CP模式的ZooKeeper
  • Redis的主从,就是直接复制数据,所以会存在主数据写入还未同步宕机,导致从库没有数据的问题

2.5 联锁和读写锁

  • 联锁 按顺序尝试获取 lock1、lock2、lock3
  • 如果在超时时间内所有锁都获取成功 → 返回成功
  • 如果任何一个锁获取失败 → 立即释放已获取的锁,并返回失败(或重试)
  • 调用 multiLock.unlock() 会依次释放所有子锁
  • 即使中间某个解锁失败,也会继续尝试释放其他锁(尽力而为)
  • 读写锁 使用 一个 Hash 结构 + Pub/Sub 实现:
特性 MultiLock ReadWriteLock
目的 同时锁定多个资源 区分读/写权限
锁类型 多个独立排他锁 一个锁,两种模式
并发性 无并发(全排他) 读可并发,写独占
数据结构 多个 key 单个 Hash
适用场景 跨资源事务 读多写少

3、分布式锁

  • 分布式锁其实更接近于单体服务的悲观锁
  • 优先考虑乐观锁,幂等,队列等方式处理竞争问题
相关推荐
一天 24h1 小时前
从单体到分布式:JWT 如何彻底改变 Web 认证系统
前端·分布式
LCG元1 小时前
【Go后端开发】从 0 到生产级:高性能分布式网关全实现 + 接口限流熔断降级实战
分布式·golang·wpf
旺仔Sec2 小时前
HBase 分布式集群部署实战:从解压到启动的完整指南
数据库·分布式·hbase
晚霞的不甘19 小时前
CANN-MoE模型推理加速实战
人工智能·分布式·python
武子康21 小时前
Java-221 RocketMQ 消息存储核心原理:CommitLog、ConsumerQueue、IndexFile 与消息过滤机制
java·大数据·分布式·消息队列·rabbitmq·rocketmq·java-rocketmq
或与且与或非1 天前
rabbitmq选举集群搭建
分布式·rabbitmq·ruby
无心水1 天前
【分布式利器:金融级】金融级分布式架构开源框架全景解读
人工智能·分布式·金融·架构·开源·wpf·金融级框架
Swift社区1 天前
分布式能力在鸿蒙 PC 上到底怎么用?
分布式·华为·harmonyos
无心水1 天前
【分布式利器:SOAF】蚂蚁开源的金融级微服务全家桶:SOFAStack 核心架构与实战选型对比
人工智能·分布式·微服务·金融·架构·开源·分布式利器