使用redis实现分布式锁

Posted 蘑菇骑士团

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用redis实现分布式锁相关的知识,希望对你有一定的参考价值。

在分布式系统中,我们常常会遇到多个进程对共享资源进行互斥访问的场景,使用redis实现分布式锁是一种简单、高效的解决方案。本文简要讨论下使用redis实现分布式锁以及需要注意的点,看看你的实现中是不是也有没考虑到的坑呢?


安全性与活性(Safety & Liveness

一个正确的分布式锁实现应该同时满足安全性和活性。

  • 安全性:访问互斥,任一时刻只能有一个客户端持有锁。

  • 活性包含两点:

    • 避免死锁。需要保证锁最终一定能被获得,即使某个在客户端释放锁之前down机或者发生网络不可达。

    • 容错性。需要保证只要redis集群中多数节点存活,客户端就能正常获取释放锁。


基于单机redis的分布式锁

我们首先看基于单个redis节点的分布式锁如何实现。相信很多人可以马上按下面的思路写好代码:

  • setnx创建key,创建成功便获得锁。

  • 访问共享资源,执行业务逻辑。

  • 操作完成,删除key,释放锁。


正常情况下上面的逻辑能够正常执行,但考虑如果客户端A获取锁后挂掉了或由于网络不可达,其他客户端就再不能获取锁了。这时我们可以考虑对key设置一个过期时间,并设置一个唯一值来标识每个客户端,可以使用下面命令来获取锁:


    SET resource_name my_random_value NX PX 30000


注意这是一个原子操作,如果分开成 设置key—设置失效时间 两步,如果设置key后客户端挂掉,仍然可能造成死锁。并且只有 value='my_random_value' 才能释放锁,否则可能导致一个客户端释放另一个客户端的锁。对于释放锁,可以使用redis lua脚本:

    

    if redis.call("get",KEYS[1]) == ARGV[1] then

        return redis.call("del",KEYS[1])

    else

        return 0

    end


注意del key实际上是一个非原子操作,需要先拿到key、判断、删除。使用lua脚本可以将这个过程转化为原子操作,否则进行到判断 value='my_random_value' 要删除后,由于gc或网络原因导致删除命令延迟到达,会将其他客户端的锁释放掉。

结合上面的分析,我们总结下单机版redis分布式锁的需要考虑的点:

  • 需要设置锁的失效时间

  • 需要对每个客户端设置唯一的标识

  • 需要保证获取锁、释放锁是原子操作


基于redis集群的分布式锁

单机redis节点如果故障了,服务便不可用了。一般这种情况下,我们会有一个热切换方案,在主节点(Master)上挂一台从节点(Slave),当主节点挂掉了,Slave会升级为主节点。但由于主从节点的数据同步是异步的,可能会导致锁丢失的情况。这种情况下,redis的作者给出了一个叫 Redlock 的算法。假设环境是N个Redis Master节点,这些节点相互独立,无需备份。算法步骤如下:

  • 获取当前时间

  • 依次向每个Master实例尝试获取锁。在获得锁的过程中,为每一个锁操作设置一个快速失败时间,它要远小于锁的有效时间,通过快速失败的方式,尽快尝试与所有节点通信以获取锁

  • 计算整个获取锁的过程消耗了多长时间,如果获取锁的时间小于有效时间,并且半数以上的Redis节点(>= N/2+1)成功获取到了锁,那么认为客户端最终获取锁成功

  • 如果获得锁成功,从新计算锁的有效时间(扣除获取锁的时间)

  • 如果获取锁失败,客户端尝试在所有的master节点中释放锁


那么Redlock是完美的吗?考虑以下序列,假设有5个节点A、B、C、D、E:

  • 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。

  • 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。

  • 节点C重启后,客户端2锁住了C, D, E,获取锁成功。


这样,客户端1和客户端2就都获得了锁,这显然是有问题的。为了解决上面的问题,Redlock又给出了一个延迟重启的概念,保证重启后对现有锁不造成影响。但这样就是安全的了吗?考虑下面这个执行序列:

客户端1在获得锁后,暂停了很久,以至于锁过期并且客户端2也获取了锁。当客户端1恢复后再去操作共享数据,这就打破了数据的安全性。


所以从以上分析上看,设计一个完美的分布式锁是非常困难的,我们需要基于自己的业务场景,选择合适的方案,希望大家在设计分布式锁时能考虑到本文提出的一些注意事项。


参考:

        https://redis.io/topics/distlock


以上是关于使用redis实现分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

真正的 Redis 分布式锁,就该是这样实现的

使用Redis实现分布式锁

使用Redis实现分布式锁

redis 分布式锁

分布式锁Redis分布式锁注解灵活实现

Redis 作者 Antirez 讲如何实现分布式锁?Redis 实现分布式锁天然的缺陷分析&Redis分布式锁的正确使用姿势!...