前言
本文将着重介绍Redis中的分布式锁及其与出现的死锁和锁误删问题
什么是分布式锁
首先问题就是什么是分布式锁,分布式锁就是分布式系统中实现并发控制的一种锁机制,它可以保证多个节点在同一个时间只有有一个能成功竞争到系统资源(共享资源或执行关键代码),防止造成并发安全问题。图示如下:
Redis中的分布式锁
在Redis中分布式锁使用 setnx (set if no exists) 实现,当我们使用setnx创建锁的时候,成功则返回1,否则返回0
127.0.0.1:6379> setnx lock true
(integer) 1 #创建锁成功
#逻辑业务处理...
127.0.0.1:6379> del lock
(integer) 1 #释放锁5
当我们进行重复加锁时,会出现下面这种情况:
127.0.0.1:6379> setnx lock true # 第一次加锁
(integer) 1
127.0..1:6379> setnx lock true # 第二次加锁
(integer) 0
Redis中的分布式锁问题
死锁问题
一个很明显的问题是,假如我上锁并且没有设置过期时间,但是忘记释放锁了,就会造成死锁问题,系统资源一直无法释放,应用无法正常执行,那么要怎么解决这个问题呢???
如果我们在上锁后对其进行设置超时时间,那么上锁和设置时间这个两个操作并不是原子性的,还是会存在并发安全问题。
在后续的Redis2.6.12版本之后,新增了了一个expire,setnx可以和他搭配使用,做到原子性的上锁和设置超时时间
127.0.0.1:6379> set lock true ex 30 nx
OK #创建锁成功
127.0.0.1:6379> set lock true ex 30 nx
(nil) #在锁被占用的时候,企图获取锁失败
其中 ex 为设置超时时间, nx 为元素非空判断,用来判断是否能正常使用锁的。
锁误删问题
在出现expire命令和setnx命令组合使用可以解决死锁问题后,出现了新的问题锁误删问题
锁误删问题就是一个应用在获取锁后,他执行任务的时间超过了设置的超时时间,锁就会被其他应用争取到,但是当前应用任然在进行,当当前应用在执行结束后,执行了del lock命令就会把锁释放掉,那么其他线程刚刚竞争到的锁又会被其他应用争取到,恶性循环下去。图示如下:
如果要解决锁误删问题,就需要我们在设置锁的时候,多设置版本号,就解决跟CAS锁中ABA一样。
127.0.0.1:6379> set lock thread ex 30 nx
OK #创建锁成功
这里面thread1就是锁归属线程的标识,我们在进行删除锁的时候后,需要进行判断
+删除
就能解决一个误删问题了
但是新的问题又出现了,重点还是判断
+删除
它并不是原子性的,所以为了解决这个原子性问题,可以使用lua脚本或者Ression框架下来解决。具体可以去翻阅资料,这里就不做过多赘述。