redis实现的分布式锁为啥要设置过期时间?
Posted 孙晓凯
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis实现的分布式锁为啥要设置过期时间?相关的知识,希望对你有一定的参考价值。
Redis分布式锁利用setnx,如果进程中的线程成功设置了setnx,则证明获取到了锁,然后执行业务逻辑代码,然后释放锁。
//试图获得锁
1.if (redisClient.setnx(xx,xx))
2. do xxxxx something
3. //释放锁
4. redisClient.del(xx)
5.
这种方式有个问题就是如果第4行之前出现错误,执行不到第4行的话,锁会释放不了,然后锁死。
于是一个新的方案出炉:
//试图获得锁
1.if (redisClient.setnx(xx,xx))
2. //设置过期时间
3. expire xx 5
4. do xxxxx something
5. //释放锁
6. redisClient.del(xx)
7.
给锁设置过期时间,这样一来,即使执行不到第6行(释放锁的哪一行),等过期时间到了,也会自动释放锁。
然而这个方案乍一看是那么回事,其实没啥卵用,因为加锁和设置过期时间不是原子操作,加完锁后,设置过期时间前出现问题,照样玩完。
于是接下来,很多人想方设法吧这两个操作变成原子操作,先有第三方包,后有官方实现版本(set ex nx)。
手动分割线,以下才是正文,哈哈。
这个问题确实是解决了,但是为啥一定要用这种方式解决呢?对于Java来说,直接在finally中释放锁不就行了吗?就像ReentrantLock不就是在finally中释放锁的吗?
其实这里是不一样的,ReentranLock加锁和释放锁是在自己的进程里面,即使进程被kill掉,锁来不及释放,下次启动进程的时候,再次获取锁就是了。
但是Redis分布式锁是第三方的东西,程序就是Redis节点的客户端,一旦客户端没有释放锁,服务端就会一直持有这个锁的,其他进程中的线程是获取不了锁的。
比如以下这两种情况(如果只在finally中释放锁,不设置过期时间):
1.网络抖动
进程A中的一个线程获取到了锁,然后执行finally中的释放锁的代码时,由程序到Redis的网络不好了,所以释放锁失败。此时对于redis服务端来说,它可不知道客户端曾经试图释放过锁,它会一直把锁给A,如此一来,其他进程的线程再也不能获取到这个锁了。
如果用设置过期时间的方式,即使客户端和服务端的网络不通了,服务端依然在进行时间的计算,时间到了直接把锁释放掉,等网络通了,不影响获取锁。
2.服务端宕机
进程A获取到了锁,Redis服务器宕机了,所以锁没有释放。等到Redis再次恢复的时候,Redis服务端还会保持这这个锁给到A,就会锁死。
如果是设置了过期时间的话,服务器恢复后就会继续倒计时,时间到了服务器自动把锁释放。
说白了,分布式锁用的是第三方的东西,所以要在第三方设置,不能只在客户端保证所的释放。
one more thing:
其实用set ex nx虽然加锁和设置过期时间变成了原子性,但是依然会有问题,因为一般来说Redis在生产环境中并不是单节点的,而是一个集群。
那么会存在这种情况,比如A进程中的一个线程获取到了锁,是设置到了master节点,但是当master的数据还没有同步到slaver之前,master死了。此时从slaver中选举中来的新master并不知道有这个锁,所以新的线程B来获得锁,也会获取成功。这就造成了两个进程获取同一把锁。
解决这种问题有一个RedLock算法,留待以后研究。
以上是关于redis实现的分布式锁为啥要设置过期时间?的主要内容,如果未能解决你的问题,请参考以下文章