使用 Redis 如何设计一个分布式锁

前言

现在的业务应用通常都是微服务架构,如果一个应用部署多个进程,那这多个进程如果需要修改操作同一行记录时,为了避免操作乱序导致数据错误,此时,我们就需要引入分布式锁来解决这个问题了。

而实现分布式锁,大多有以下三种方式实现:

  • 使用 MySQL 实现
  • 使用 Redis 等缓存系统实现
  • 使用 Zookeeper 实现

下面我们以 Redis 来讲解如何实现分布式锁,以及分布式锁的各种安全性问题。

想要实现分布式锁,关键是使用 SETNX 指令。

SETNX

SETNX key value

这个命令执行时,如果 key 不存在,则设置 key 值为 value;如果 key 已经存在,则不执行赋值操作。并使用不同的返回值标识

SETNX 实现分布式锁

接下来我们对比下面的几种实现分布式锁的方式:

方式 1、SETNX + DEL

客户端 A 申请加锁,加锁成功:

> setnx name 1
(integer) 1

客户端 B 申请加锁,加锁失败:

> setnx name 1
(integer) 0

这时加锁成功的客户端就去操作数据,操作成功之后,需要释放锁给后面的客户端操作,这里使用 DEL 命令删除这个 key就可以。

> del name
(integer) 1

但是这个实现方式会有个问题,一旦服务获取锁之后,因某种原因挂掉,则锁一直无法自动释放。从而导致死锁。

那么怎么解决这个问题呢?

方式 2、SETNX + EXPIRE

服务某种原因挂掉,导致无法释放锁,这时候我们能想到的就是给这把锁加个时间,在 Redis 中,给 key 设置一个过期时间。

> setnx name 1
(integer) 1
> expire name 5
(integer) 1

这样的话,无论是否异常,我们设置的这个锁都会在 5 秒之后自动释放锁,其他客户端还是可以获取到锁的。

此方式解决了方式 1 死锁的问题,但同时引入了新的死锁问题,因为我们设置过期时间是经过 2 条命令来执行的,可能发生以下的情况:

SETNX 成功以后,因为各种原因(网络、Redis异常、宕机崩溃),都会导致陷入死锁,两条命令不能保证原子操作,就会导致过期时间设置失败的问题。依然会发生死锁。

那么怎么解决这个问题呢?

方式 3、SET EX NX

> set name 1 ex 10 nx
OK

这个方式通过 set 的 EX/NX 选项,将加锁、设置超时两个步骤合并为一个原子操作,从而解决方式 1、2 的问题,但是此方式还是会出现问题,什么问题呢?

如果锁被错误的释放(如超时),或被错误的抢占,或因 Redis 问题等导致锁丢失,无法很快的感知到。

比如 客户端 A 去加锁成功去操作资源,超过锁的过期时间自动释放锁,这时候客户端 B 加锁成功去操作资源,这时候客户端 A 操作资源完成,释放锁,可能释放的是客户端 B 的锁。

如何解决这个问题呢?

SET name uuid EX 10 NX

客户端在加锁时,设置一个只有自己知道的唯一标识进去。在释放锁时,要先判断这把锁是否自己持有的。

if redis.get("lock") == $uuid:
    redis.del("lock")

但是这里释放锁,使用的是 GET + DEL 两条命令,又回出现我们前面所讲的的原子性问题,为保证原子性,需要通过 lua 脚本实现。

if redis.call("GET",KEYS[1]) == ARGV[1]
then
    return redis.call("DEL",KEYS[1])
else
    return 0
end

此方案更严谨,即使因为某些异常导致锁被错误的抢占,也能部分保证锁的正确释放。并且在释放锁时能检测到锁是否被错误抢占、错误释放,从而进行特殊处理。

项目我们总结一下,基于 Redis 实现的分布式锁,严谨的的流程如下所示:

  • 加锁:SET name uuid EX time NX
  • 操作共享资源
  • 释放锁:Lua 脚本,先 GET 判断锁是否自己持有的,再 DEL 释放锁
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值