唯's Blog

笔者是一个热爱编程的 Java 程序员。

0%

分布式锁的核心思想

分布式锁的四个必要条件

  • 互斥性:在任意时刻只有一个客户端能够获取锁资源。
  • 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 的删除事件,一旦监听到删除事件(主动删除或者租约到期删除)则自己获取到锁。

优化

  • 在任何场景都添加互斥锁,性能会急剧下降,所以引出了读写锁