基于redis实现的分布式锁

分布式锁是不同进程互斥操作共享资源的唯一办法,可以用来做秒杀等功能,本文章主要记录在redis中使用分布式锁的一种方式。 就redis来说实现分布式锁的方式有几种:

一、利用SETNX实现分布式锁

通过这个命令去设置,成功得到锁返回1,返回0代表被其他客户端获得了锁

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"
redis>

只要一个客户端设置了值,如果再设置一个值的话就会返回0,这样就实现了一个锁机制。

从理论上的角度来说上面的方法是没有问题的,但是从实践来说,我们相对考虑的问题就会比较多,作为编码人员,需要把一切东西都考虑到如果它挂掉这一个层面上,上述方法十分容易出现死锁,当获取锁的客户端没有对锁进行释放,就会产生死锁情况。

针对这种情况我们可以考虑超时的问题,一旦超时的话可以使用客户端去删除锁,这样就可以解决死锁的问题。目前我们只需要增加种操作,把锁的value设置成超时时间。让客户端不断的去获取锁的值,然后跟当前之间进行比较,一旦超时就可以把锁删除了,然后进行锁的获取。但是这个也会存在一种问题,可以看下面的分析:

C0 操作超时了,但它还持有着锁,C1 和C2 读取lock.foo检查时间戳,然后发现超时了;

C1 发送DEL lock.foo;

C1 发送SETNX lock.foo 并且成功了;

C2 发送DEL lock.foo;

C2 发送SETNX lock.foo 并且成功了。

上面这种情况来说,两个客户端都获取到了锁,锁的安全性被破坏了,之前我们一直采用上面的方案,导致我们偶尔会死锁。我们继续看另外一种相对比较安全的方案

二、GETSET命令

下面两个客户端简称c3和c4

C3 发送SETNX lock.foo 想要获得锁,由于 C0 还持有锁,所以Redis返回给 C3 一个0;

C3 发送GET lock.foo 以检查锁是否超时了,如果没超时,则等待或重试;

反之,如果已超时,C3 通过下面的操作来尝试获得锁:


GETSET lock.foo <current Unix time + lock timeout + 1>

通过GETSET,C3 拿到的时间戳如果仍然是超时的,那就说明,C3 如愿以偿拿到锁了。

如果在 C3 之前,有个叫 C4 的客户端比 C3 快一步执行了上面的操作,那么 C3 拿到的时间戳是个未超时的值,这时,C3 没有如期获得锁,需要再次等待或重试。

留意一下,尽管 C3 没拿到锁,但它改写了 C4 设置的锁的超时值,不过这一点非常微小的误差带来的影响可以忽略不计。

为什么用GETSET命令可以实现这个功能呢,因为redis是单线程。在执行set操作的时候只能同时有一个getset

总的来说这种方式也有缺陷,具体如下:

多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在串锁的情况。要有适度的机制,承受小概率的事件产生。因为分布式中各种系统会有一定的概率导致各个机器之间的时间不一致。无论是在什么情况,分布式环境中尽量少用时间来进行操作。

三、具体实现

其实我只是大自然的搬运工而已,具体spring integration中已经实现这样的功能了。具体实现源码链接,具体源码分析可以查看gitbook链接

results matching ""

    No results matching ""