-
基本概况
- 分布式锁的要求:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 独立性。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
- 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
- 互斥性
SET key value [EX seconds] [PX milliseconds] [NX|XX] releaseLockTime
- NX – 不存在时才能设置成功. XX – 存在时才能设置成功
- EX = seconds; PX = milliseconds
- 单语句保证了原子性操作,即加锁判断和设置过期时间在一个原子操作中完成.
DEL key
- 分布式锁的要求:
-
单节点下的redis分布式锁
-
持锁节点宕机(死锁)
解决方案: 加入超时时间releaseLockTime
-
持锁线程的任务执行时间大于超时时间(独立性)
解决方案:
利用value给锁加上"签名",在删除锁时,需要验证签名.签名不对不允许删除.
-
持锁线程通过验证时,超时锁自动释放(原子性问题)
解决方案: Lua脚本(redis的eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。)
-
思考:保证任务执行时间小于锁的释放时间
-
进一步: 锁的重入性
解决方案:利用value值进行校验,通过校验则允许持有锁(保证原子性),同时维护一个计数器,记录持锁深度,当深度为0时才能进行解锁操作.
-
-
防止redis挂掉(稳定性)
- RedLock:利用多独立节点进行容灾。
- 算法逻辑(redlock)
- 记录开始时间
- 依次向每个节点尝试获取锁(set失败后,检查value有效性,原子操作),超时默认失败
- 当请求到超过半数的成功后,计算当前时间是否已经到达锁释放时间(防止时钟漂移)
- 若未到达释放时间,则表示获得锁
- 获取锁失败或者结束时向所有节点释放锁
- 注意,节点宕机后,再次重启时间应该大于释放时间(防止其他线程协调失败节点和宕机节点获取到锁)
- 注意,竞争失败后应该稍等一段时间,避免冲突,导致谁都拿不到过半的锁。
- 注意的问题
- 释放时间和业务时间的判断
-
分布式环境
- 主从——客户端分片
- 集群——服务器端分片
- 哨兵——代理端分片
-
其他分布式锁的实现方式
- MySQL
- ZK
- Redis
- 自研分布式锁:如谷歌的 Chubby。
参考: