Redis实现分布式锁的官方文档介绍
Redis的官方文档对用Redis分布式锁的难点以及解决方案的考虑做了一些说明,具体内容参见:/docs/reference/patterns/distributed-locks/,译文参考:《Redis官方文档》用Redis构建分布式锁 | 并发编程网 – ifeve.com
场景一:
保存锁的Redis的master节点挂了。这里不是说有slave就没问题了,因为Redis的数据复制功能(replication)是异步的,存在这样的可能,线程1获取到锁了,该锁在从master复制到slave之前,master挂掉了,然后slave提升为了master,这个时候线程2从新的master节点上是可以获取到锁的,之前的锁丢失了,这样会导致安全问题。
场景二:
对于锁“lock123”(名字随便取的),线程一获取到锁,设置过期时间为30s,但是真正业务执行时间超过了30s,比如说40s。在32s的时候线程二尝试获取锁,并且成功了,因为这个锁的超时时间是30s,在32s的时候,该锁已经失效了。当40s的时候,线程一业务执行完毕,这个时候尝试去释放锁,即删除锁,很不幸,这个时候线程二锁持有的锁会被线程一误删掉,因为它们的key相同。
解决方案:
set操作的时候,设置一个random value作为该key的value,在解锁的时候,判断当前锁的value和该value是相同的,这样才可以执行删除操作,就可以解决上面场景中的误删问题了。但是其实并不完美,因为在第一个线程执行完任务之前,线程二不应该获取锁,但是该锁的过期时间比业务执行时间短,从而导致了线程二仍然获取到了锁。
Redisson的解决方案
在redis的文档中提到了一个算法,如果按照这个算法来实现分布式锁的话,可以解决之前提到的那些问题。
Redisson的实现在RedissonMultiLock中,核心算法在tryLock方法中:
这里管理了多个锁,为的就是解决某一个Redis节点上保存的锁因为该Redis节点出现问题而导致锁不可用的情况。for循环对每个锁进行尝试加锁操作,这里有两种情况:
①如果获取锁成功,那么就将当前锁添加到已获取锁的队列中去
②如果加锁失败,那么判断当前成功的锁的个数是否达标,如果达标,那么跳出循环。如果不达标,那么失败限制计数减一(因为上面再次循环成功的次数有可能加一,所以这里失败限制要减一)。然后检查是否已经超时,如果超时,说明在超时限定时间内没有获取到锁,即获取锁失败,这时释放所有已经成功加锁的所有锁。如果加锁成功的话,那么给所有已经加锁成功的锁设置超时时间。