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实现的分布式锁为啥要设置过期时间?的主要内容,如果未能解决你的问题,请参考以下文章

Redis实现分布式锁

redis实现分布式锁

Redis的分布式锁

用redis构建分布式锁

redis分布式锁的原理

分布式锁(2) ----- 基于redis的分布式锁