1、Zookeeper
1.1 原理步骤
- 核心机制:临时顺序节点(Ephemeral Sequential Nodes)
- 所有竞争锁的客户端在同一个父路径(如 /locks)下,创建临时顺序子节点,例如:
Client A → /locks/lock-0000000001
Client B → /locks/lock-0000000002
Client C → /locks/lock-0000000003 - 节点名中的 10 位递增序号由 ZooKeeper 服务端自动生成,保证全局唯一且有序。
- 所有竞争锁的客户端在同一个父路径(如 /locks)下,创建临时顺序子节点,例如:
- 获取锁的判断逻辑,每个客户端创建完自己的节点后,
- 列出 /locks 下所有子节点并按字典序排序。
- 如果自己的节点是序号最小的,则立即获得锁,进入临界区。
- 否则,找到自己节点的前一个节点(直接前驱),并对其注册一个 Watcher 监听删除事件(NodeDeleted)
- 等待与唤醒机制(避免羊群效应)
- 客户端只监听 自己的直接前驱节点,而不是最小节点。
- 当前驱节点被释放(因客户端主动删除或会话超时自动消失),ZooKeeper 仅通知该等待者。
- 被通知的客户端重新检查:若自己现在是最小节点,则获得锁;否则继续监听新的前驱。
- 锁的释放
- 主动释放:客户端调用 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 原理步骤
- 使用 SET lock_key unique_value NX PX expire_time 原子命令加锁,确保加锁和设置 TTL 是原子的,避免死锁。
- 执行完任务后,通过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、分布式锁
- 分布式锁其实更接近于单体服务的悲观锁
- 优先考虑乐观锁,幂等,队列等方式处理竞争问题