为什么 Spring 提供的 Redis 插件中的 setIfAbsent 方法调用的是 set 命令

Spring Redis 插件中的分布式锁


org.springframework.data.redis.core.ValueOperations#setIfAbsent(K, V, long, java.util.concurrent.TimeUnit)
通过注释可见,其意义为【当key不存在时,设置这个key,并设置过期时间】

为什么不使用 setnx 命令?

在通常的印象中,分布式锁命令不应该是setnx嘛?
setnx命令在Redis官方文档中的释义如下

要注意的是该命令并不包含过期时间的设置,通常在设置一个分布式锁的时候都需要给锁加一个过期时间防止程序异常而没有释放锁,那么如果使用setnx命令,就需要额外给这个key设置一个过期时间,即expire命令,那么使用setnx命令去实现分布式锁就变成了2条命令,即
1.setnx key value
2.expire key seconds
此时加锁的命令就无法保证原子性,如果在setnx成功之后,系统异常导致expire没有成功执行,这个锁就变成了永不过期

为什么是 set 命令?

Redis在2.6.12版本中,为set命令加入了新的参数,使该命令直接支持了分布式锁,而加锁的命令为 set key value ex timeout nx,其含义为【当key不存在时,设置这个key,过期时间为timeout,单位为秒】,此命令保证了操作的原子性,故Spring插件的分布式锁命令是set

加锁解锁中的并发问题

在上述的问题中,我们已经发现,加锁需要保证操作的原子性,那么在解锁的过程中呢?例如A线程加锁,过期30秒,而A线程执行的时间已经超过了30秒,锁已经自动释放;此时B线程加锁成功,开始执行,而A线程执行结束,删除了锁,但此时删除的锁实际为B线程加的;此时C线程又加锁成功,开始执行;在此案例中,就无法满足ABC三个线程的串行执行;
在此案例中,就引出了3个问题
1.需要当前线程只能解除自己加的锁
2.解锁需要保证原子性操作
3.锁过期时间不够时需要续期操作

如何保证解锁的线程是加锁的线程?

线程应该使用自身的唯一标识来加锁,解锁时使用此唯一标识校验,如ThreadId,亦或uuid,只要保证不重复即可

如何保证解锁的原子性?

即使加入了解锁唯一性校验,在判断成功开始解锁的瞬间,也可能出现B线程加锁成功的情况,那么此时就需要保证判断能够解锁与解锁的这个过程是原子性的,具体可以参考org.redisson.RedissonLock#unlockInnerAsync

分布式锁如何续期?

加锁时,可以启动一个监视线程,通过定期轮循的方式来判断持有锁的线程有没有执行完毕,如果没有执行完就自动续期,具体可以参考
org.redisson.RedissonLock#tryAcquireOnceAsync

在上述方法中,成功设置了锁之后会启动一个自动renew(也就是续期)的方法,在此方法中,如果持有锁的线程没有执行结束,就会将这个锁延长过期

参考资料:
https://blog.csdn.net/weixin_...
https://blog.csdn.net/weixin_...

你可能感兴趣的:(javaredis)