Redis 分布式锁
Redis分布式锁最简单的实现
分布式锁使用场景:
- 客户端1 申请加锁,加锁成功
- 客户端2 申请加锁,因为它后到达,加锁失败
- 客户端1 释放锁
- 客户端2 申请加锁,并加锁成功
SETNX
想要实现分布式锁,必须要求Redis
有「互斥」的能力,这里我们就要提到SETNX 命令
,这个命令表示SET if Not Exists
,即如果key
不存在,才会设置它的值,否则什么也不做。
两个客户端进程可以执行这个命令,就达到互斥的效果,实现了分布式锁。
实现场景的加锁与释放锁
加锁 setnx lock 1
释放锁 del lock
问题1 死锁
问题:
当客户端1 拿到锁后,如果发生下面的场景,就会造成「死锁」:
1. 程序处理业务逻辑异常,没及时释放锁
2. 进程挂了,没机会释放锁
解决方式:
给lock
设置过期时间。例如,设置 10s 过期。这样永远不会死锁
SETNX lock 1 // 加锁
EXPIRE lock 10 // 10s后自动过期
加锁、设置过期是 2 条命令,不能保证是原子操作(一起成功),存在潜在风险。所以,在 Redis 2.6.12 之后,Redis 扩展了 SET 命令的参数,用这一条命令就可以了:
// 设置 lock 值为 1,存活时间为 10秒。
// ex 为秒表示,px 为毫秒标识
// nx 键值不存在执行,xx 键值存在执行
SET lock 1 EX 10 NX
问题2 锁被别人释放
解决办法
客户端在加锁时,设置一个只有自己知道的「唯一标识」进去,作为value存储。
// UUID
SET lock $uuid EX 20 NX
之后,在释放锁时,要先判断这把锁是否还归自己持有,伪代码可以这么写:
if redis.get("lock") == $uuid:
redis.del("lock")
这里释放锁使用的是 GET + DEL 两条命令,这时,又会遇到我们前面讲的原子性问题了。这里可以使用lua脚本来解决。
// 安全释放锁的 Lua 脚本
if redis.call("GET",KEYS[1]) == ARGV[1]
then
return redis.call("DEL",KEYS[1])
else
return 0
end
小结
基于 Redis 实现的分布式锁,一个严谨的的流程如下:
1、加锁
SET lock_key $uuid EX $expire_time NX
2、操作共享资源
3、释放锁:Lua 脚本,先 GET 判断锁是否归属自己,再DEL 释放锁