Redis 并发锁

Posted 飞鱼的梦呓

tags:

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

阅读本文大约需要 5 分钟


大家好,我是飞鱼。又到周末了,今天给大家分享下通过 Redis 实现并发锁的相关知识。


使用场景

首先使用分布式锁的场景一般是并发读取公有资源,例如加减库存这类业务场景。

大体的思路就是在读取公共资源前,先去获取锁,如果能抢到锁,则进行下一步操作,否则操作失败。

分类

分布式锁可以分为单机锁与分布式锁。

单机锁指的是锁存储在单个节点上,分布式锁就是存储在多节点上。

1.单机锁

单机锁在 Redis 中可以用一个 string 类型的数据表示,假设键名为 lock ,键值为 1 或 0,其中 1 代表有客户端获取到了锁,0 代表没有客户端获取到锁。


加锁流程

客户端的获取锁的步骤为

  1. 通过 get lock 命令获取锁;

    1. 若结果为 1,代表成功获取到锁,执行后续操作2;

    2. 若结果为 0,代表获取锁失败,返回失败提示;

  2. 执行业务逻辑,完成后通过 del lock 命令释放锁

上面流程对应的伪代码如下

其中 acquire_lock 函数包括了获取锁变量,判断锁变量值,设置锁变量这三个步骤,为了保证这三个步骤的原子性,需要通过 Redis 的原子命令或者 lua 脚本完成,原子命令的优先级由于比 lua 脚本简单易实现,因此选择原子命令的优先级更高。


Redis 中的 SETNX 命令可以解决加锁进行的相关操作。SETNX 命令,它用于设置键值对的值。具体来说,就是这个命令在执行时会判断键值对是否存在,如果不存在,就设置键值对的值,如果存在,就不做任何设置。


因此,通过 SETNX 和 DEL 命令实现分布式锁的获取与释放的伪代码如下

Redis 并发锁

简单分析下上面的代码,如果客户端在成功获取到锁之后,在执行业务逻辑阶段由于故障宕机了,就会导致执行不到 DEL lock 阶段,也就是说锁永远都无法释放了。这样肯定是不行的,因此,一般来说,都会给 lock 设置一个过期时间来防止这种情况的发生。


那么问题又来了,过期时间设置多久好呢?

这个过期时间需要根据实际业务来设置,至少要大于业务逻辑执行的时间,不然业务还没执行完,锁就被释放了,肯定也是不行的。


设置了过期时间还不够,再来考虑下面的一个场景

如果客户端 A 执行了 SETNX 命令加锁后,假设客户端 B 执行了 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,就可以成功获得锁,进而开始操作共享数据。这样一来,客户端 A 和 C 同时在对共享数据进行操作,数据就会被修改错误。


为了应对上面的场景,需要把锁变量值设置为客户端的唯一标识,只能由自己释放自己加的锁。

因此,可以使用下面命令完成加锁

Redis 并发锁


解锁流程

讲完了加锁的实现,接下来看看释放锁的具体实现。

由于释放锁时会涉及到:获取锁变量,判断锁变量以及删除锁变量这三个步骤,并且这三个步骤需要保证原子性。


Redis 中并不存在能够完成这三个步骤的单命令,因此释放锁需要通过 Lua 脚本来完成。

对应的伪代码如下所示


其中,KEYS[1]表示 lock_key,ARGV[1]是当前客户端的唯一标识,当我们执行下面的命令,就可以完成锁释放操作了。


小结

基于 Redis 单节点实现的分布式锁,先通过 set ex nx 命令获取到锁,如果可以获取到锁,就执行业务逻辑,再通过 Lua 脚本来释放锁。


2.分布式锁

上面的单节点锁由于存在单点故障的原因,由此便产生了分布式锁,也叫做 RedLock。

Redlock 加锁算法的基本思路

让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么就认为客户端成功地获得分布式锁了,否则加锁失败。

这样一来,即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,所以,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。


整个加锁过程涉及下面三步操作

1.获取当前时间

2.依次向 Redis 实例请求加锁

3.计算获取到锁的耗时


其中,客户端成功获取到锁时必须同时满足以下两个条件:

1.从超过半数的实例获取锁成功

2.获取锁的总耗时没有超过锁的有效时间

如果没能满足上面的条件,就需要依次向 Redis 实例执行释放锁命令,具体实现与单节点版一致,都是通过 Lua 脚本完成。


参考

《极客时间 Redis 核心技术与实战》


总结

通过在共享存储区中设置变量,可以实现分布式锁。Redis 本身具备高可用的共享存储功能 ,因此使用 Redis 来实现分布式锁十分常见。

单节点锁主要通过 set nx ex 命令完成加锁,再通过 Lua 脚本来释放锁。由于单节点本身并不可靠,因此又诞生了 RedLock 锁。

RedLock 锁作为一种高可用分布式锁的解决方案,避免了单点故障的问题,即使集群中有某个节点下线,也可以保证锁能够正常被获取,大大提高了可用性。




以上是关于Redis 并发锁的主要内容,如果未能解决你的问题,请参考以下文章

redis锁 和悲观锁的并发问题

锁Redis锁 处理并发 原子性

使用Redis构建全局并发锁

redis基于redis实现分布式并发锁

php redis 常见应用 并发锁。(悲观锁)

redis内存锁,PHP防止并发操作