1.redis锁介绍
一般来说,在对数据进行"加锁"时,程序首先需要通过获取(acquire)锁来得到对数据进行排他性访问的能力,然后才能对数据执行一系列操作, 最后还要将锁释放release给其他程序。对于能够被多个线程访问的共享内存数据结构(shared-memory data structure) 来说,这种“先获取锁,然 后执行操作,最后释放锁”的动作非常常见。
分布式锁也有类似的“首先获取锁,然后执行操作,最后释放锁”的动作,但这种锁既不是给同一个进程中的多个线程使用,也不是给同一台机器上 的多个进程使用,而是由不同机器上的不同Redis客户端进行获取和释放的。
2.实现redis锁要解决的4个问题
如果想要实现一个稳定的高性能的可用的redis锁,我们就不得不解决下面提到的4个问题。
实现redis锁,必须要解决的问题:
(1).持有锁的进程因为操作时间过长而导致锁被自动释放,但进程本身并不知晓这一点,甚至还可能会错误地释放掉了其他进程持有的锁。
(2).一个持有锁并打算执行长时间操作的进程已经崩溃
(3).在一个进程持有的锁过期之后,其他多个进程同时尝试去获取锁,并且都获得了锁
(4).第(1)种和第(3)种情况同时出现,导致多个进程获得了锁,而每个进程都以为自己是唯一一个获得锁的进程。
3.setnx命令和set命令以及getset命令
我们在实现redis锁时,会使用到set、setnx、getset这三个命令。关于这三个命令的使用,请查看:
等熟悉了,这三个命令之后,我们就可以开始手写一个简易版的redis锁。
4.简易锁
为了对数据进行排他性访问,程序首先要做的就是获取锁。SETNX命令天生就适合用来实现锁的获取功能,这个命令只会在键不存在的情况下为键设置值,而锁要做的就是将一个随机生成的128位UUID设置为键的值,并使用这个值来防止锁被其他进程取得。
上述代码的逻辑:
首先,它会使用setnx命令,尝试在代表锁的键不存在的情况下,为键设置一个值,以此来获取锁;在获取锁失败的时候,函数会在给定 的时限内进行重试,直到成功获取锁或者超过给定的时限为止。(默认的重试时限为10秒)。
补充说明:
注意,从Redis 2.6.12开始,redis的SET命令已经开始支持多个选项了:
SET resource_name my_random_value NX PX 30000
选项说明:
EX seconds --set the specified expire time ,in second
PX milliseconds --set the specified expire time in miliseconds
NX --Only set the key if it does not already exist
XX --Only set the key if it already exist
所以,我们推荐使用set命令来取代之前的setnx命令。
解决的问题:
要解决的问题:
由于锁的持有者在崩溃的时候不会自动释放锁,这将导致锁一直处于已被获取的状态。
解决方案:
为了给锁加上超时限制特性,程序将在取得锁之后,调用expire命令来为锁设置过期时间,使得redis可以自动删除超时的锁。为了确保锁在客 户端已经崩溃(客户端在执行介于setnx和expire之间的时候崩溃是最糟糕的)的情况下仍然能够自动被释放,客户端会在尝试获取锁失败之后,检 查锁的超时时间,并为未设置超时时间的锁设置超时时间。因此,锁总会带有超时时间, 并最终因为超时而自动被释放,使得其他客户端可以继续尝试获取已被释放的锁。
需要注意的一点是,因为多个客户端在同一时间内设置的超时时间基本上都是相同的,所以即使有多个客户端同时为同一个锁设置 超时时间,锁的超时时间也不会产生太大变化。
新的这个acquireLockWithTimeout()函数给锁增加了超时限制特性,这一特性确保了锁总会在有需要的时候被释放,而不会被某个客户端一直把持着。
释放锁:
在释放锁的时候,要解决的问题是:
(1).持有锁的进程因为操作时间过长而导致锁被自动释放,但进程本身并不知晓这一点,甚至还可能会错误地释放掉了其他进程持有的锁。
5.锁由于超时而被自动释放
假如 我们把锁的释放操作控制在锁的持有者手里,让其尽可能多地在持有者完成任务的情况下,手动释放锁,而不是由于锁的超时机制来自动释放。
因为无论在哪种模式下,只要出现" 锁持有者尚未完成当前工作,但锁已经被自动释放"这种情况,都会造成并发问题。
那么为了,解决这个问题,我们能不能手动地把锁的超时时间尽可能地设置大一点呢???
备注:
上面的代码都是伪代码,有些不是很严谨,主要表达的是redis锁的实现思路和原理。
另外,对于redis实现分布式锁,还有一些问题,没有弄太明白,等弄清楚了,再补充一版。
官方的Redis分布式锁实现,请参考redlock: redlock
At this point we need to better specify our mutual exclusion rule: it is guaranteed only as long as the client holding the lock will terminate its work within the lock validity time(as obtained in step 3),minus some time (just a few milliseconds in order to compensate for clock drift between processes).
要想保证redis分布式锁的可靠性和安全性,我们必须假定一个前题,那就是: 持有锁的线程能在锁的有效期内完成它的业务处理工作。
下面再给出几个关于redis分布式锁的blog: