用Redis实现分布式锁一般是用 setnx (set if not exist)
来实现,如果可以设置成功,表示拿到锁,用完之后再用del
来释放。
setnx lock true
OK
... do something critical ...
del lock
(integer) 1
但是如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。
一般会在拿到锁之后给锁设置一个超时时间,如60s,这样即使出现异常,在60s后锁还是会被释放。
setnx lock true
OK
expire lock 60
... do something critical ...
del lock
(integer) 1
如果在 setnx 和 expire 之间发生异常,会导致 expire 得不到执行,也会造成死锁。
最主要原因就是setnx和expire不是原子操作,在Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得setnx和expire可以一起执行。
set lock true ex 5 nx
OK
... do something critical ...
del lock
多参数的set命令语法如下:
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
-
EX seconds
− 设置指定的到期时间(以秒为单位)。 -
PX milliseconds
- 设置指定的到期时间(以毫秒为单位)。 -
NX
- 仅在键不存在时设置键。 -
XX
- 只有在键已存在时才设置。
Redis分布式锁不适合执行时间很长的任务,因为如果执行时间比设定的失效时间长,就可能会造成同时有多个线程同时执行这一段业务逻辑。
有一个稍微安全一点的解决方案,就是在set的时候将value设置为一个随机数uuid,在线程执行完任务释放锁的时候,要判断这个uuid是否跟上锁时候一致,如果一致则可以释放。
以上做法可以保证一个线程上的锁不会被另外一个线程释放(当然,还需要保证匹配value的值和释放锁是一个原子操作才可以),但是如果锁是过期了自动释放,另外一个线程还是可以重新获得锁从而可以执行同一段任务。
参考资料:
[1]Redis深度历险 https://juejin.im
网友评论