Redis锁_分析redis锁的实现原理

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这三个命令。关于这三个命令的使用,请查看:

setnx命令

getset

set命令

等熟悉了,这三个命令之后,我们就可以开始手写一个简易版的redis锁。


4.简易锁


为了对数据进行排他性访问,程序首先要做的就是获取锁。SETNX命令天生就适合用来实现锁的获取功能,这个命令只会在键不存在的情况下为键设置值,而锁要做的就是将一个随机生成的128位UUID设置为键的值,并使用这个值来防止锁被其他进程取得。


简易的redis锁

上述代码的逻辑:

首先,它会使用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锁安全性的前提

下面再给出几个关于redis分布式锁的blog:

martin.kleppmann_how to do distributed lock

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容