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锁_分析redis锁的实现原理》 简易的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之间的时候崩溃是最糟糕的)的情况下仍然能够自动被释放,客户端会在尝试获取锁失败之后,检  查锁的超时时间,并为未设置超时时间的锁设置超时时间。因此,锁总会带有超时时间, 并最终因为超时而自动被释放,使得其他客户端可以继续尝试获取已被释放的锁。

需要注意的一点是,因为多个客户端在同一时间内设置的超时时间基本上都是相同的,所以即使有多个客户端同时为同一个锁设置 超时时间,锁的超时时间也不会产生太大变化。

《Redis锁_分析redis锁的实现原理》 为锁加上超时时间,防止由于锁的持有者崩溃而导致的锁无法释放问题

新的这个acquireLockWithTimeout()函数给锁增加了超时限制特性,这一特性确保了锁总会在有需要的时候被释放,而不会被某个客户端一直把持着。

释放锁:

在释放锁的时候,要解决的问题是:

(1).持有锁的进程因为操作时间过长而导致锁被自动释放,但进程本身并不知晓这一点,甚至还可能会错误地释放掉了其他进程持有的锁。

《Redis锁_分析redis锁的实现原理》 释放锁时,可能出现的问题

5.锁由于超时而被自动释放

假如 我们把锁的释放操作控制在锁的持有者手里,让其尽可能多地在持有者完成任务的情况下,手动释放锁,而不是由于锁的超时机制来自动释放。

因为无论在哪种模式下,只要出现” 锁持有者尚未完成当前工作,但锁已经被自动释放”这种情况,都会造成并发问题。

那么为了,解决这个问题,我们能不能手动地把锁的超时时间尽可能地设置大一点呢???

《Redis锁_分析redis锁的实现原理》 遗留问题

备注:

  上面的代码都是伪代码,有些不是很严谨,主要表达的是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锁的实现原理》 保证redis锁安全性的前提

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

martin.kleppmann_how to do distributed lock

    原文作者:Tesfaye
    原文地址: https://www.jianshu.com/p/0d258a8d0716
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞