分布式锁的四个必要条件
- 互斥性:在任意时刻只有一个客户端能够获取锁资源。
- Redis 单线程保证了节点在高并发创建时的互斥 set key value 1 px nx
- ETCD Revision 机制 revision值依次从小到大获取锁,公平锁
- Zookeeper 容器 顺序节点 处理指令为单线程 保证互斥
- 安全性:避免在异常情况下产生死锁线程异常中断 一直无法解锁,需要保证线程没有正常解锁的情况下,有机制保证解锁
- Redis key 过期机制
- ETCD 租约机制
- Zookeeper 临时节点
- 可用性 :加锁、释放锁的过程性能开销要尽量低,同时当提供锁服务的节点发生宕机等不可恢复性故障时,“热备” 节点能够接替故障的节点继续提供服务,并保证自身持有的数据与故障节点一致。
- Redis 主从哨兵、cluster 性能(内存操作)
- ETCD 集群 内存操作 性能(内存操作)
- Zookeeper 主从(Leader - follower -) 性能(内存操作)
- 对称性:对于任意时刻,加解锁均是同一个客户端(线程)
- Redis key ()
- etcd key
- zookeeper key
特殊问题
主从结构下,数据不一致(同步时延导致)导致锁资源不互斥,同一时刻存在多个客户端获取到锁的情况
Redis redlock 锁多个实例,同时向大多数 Redis 实例申请成功之后才算 锁成功
Zookeeper ZAP 保证 写一致性(写入大部分节点成功,才算写入成功),leader 节点出现故障,重新选举会选择最新数据的follower节点作主 (写节点永远都是一个节点)
ETCD 采用 raft 算法 写一致性
过期时间,业务系统还没执行完,锁 失效了
Redis watch dog (底层大致原理异步线程,续约过期时间)
Zookeeper 临时节点,会话断开临时节点便会删除,不用自定义过期时间,但是也存在问题,Zookeeper 服务器维护一个Session 依赖客户端发送定时心跳来维持连接,客服端由于网络延迟,GC 等情况定时心跳没发送过来,Zookeeper 也会认为 Session 过期,删除该Session,另外一个客户端就能获取到锁
ETCD 的租约机制 保证了安全性,线程异常中断,没有解锁,租约到期自动删除解锁 (watch dog 续约)
由于网络原因,GC 原因没续约,导致锁资源不互斥
客服端由于网络延迟,GC 等情况 Watch dog没续约,Zookeeper 客服端没有发送心跳
解决方案:要存在一个兜底的策略,分布式锁不是绝对安全。
羊群效应,在并发量大的情况下,线程争夺锁资源激烈,可能存在多个线程等待获取锁资源的情况,那么一个线程的解锁会导致多个线程再次去争夺临界资源。
Redis,notify ()
Zookeeper 创建顺序节点,从小到大依次排列,利用 Zookeeper waitch 机制,后一个节点只监听前一个节点的状态变化,避免了羊群效应
ETCD watch 机制,客户端获取锁的列表 /lock/mylock 读取 Key 的值列表(列表中带有 Key 对应的 Revision),判断自己 Key 的 Revision 是否是当前节点最小的,如果是则获取到锁,否则监听列表中前一个比自己小的 Key 的删除事件,一旦监听到删除事件(主动删除或者租约到期删除)则自己获取到锁。
优化
- 在任何场景都添加互斥锁,性能会急剧下降,所以引出了读写锁