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_…