分布式锁避坑指南:从 Redis SETNX 到 Redisson 看锁续期与并发安全

举报
陈陈迹 发表于 2026/10/11 12:17:19 2026/10/11
【摘要】 在分布式与高并发架构中,为了防止多节点同时修改同一共享资源(如商品超卖、重复计费、状态机乱序),分布式锁是必不可少的基础设施。很多团队在早期往往基于 Redis 简单封装一套分布式锁,但往往因忽略网络抖动、GC 停顿等边界场景,引发线上数据脏写。本文从最基础的命令演进讲起,剖析分布式锁的经典陷阱及工业级解决方案。 1. 基础演进:为什么单靠 SETNX 远远不够? 1.1 缺陷阶段一:加锁与...

在分布式与高并发架构中,为了防止多节点同时修改同一共享资源(如商品超卖、重复计费、状态机乱序),分布式锁是必不可少的基础设施。很多团队在早期往往基于 Redis 简单封装一套分布式锁,但往往因忽略网络抖动、GC 停顿等边界场景,引发线上数据脏写。

本文从最基础的命令演进讲起,剖析分布式锁的经典陷阱及工业级解决方案。


1. 基础演进:为什么单靠 SETNX 远远不够?

1.1 缺陷阶段一:加锁与设置超时非原子操作

早期常见的伪代码写法:

SETNX lock_key 1
EXPIRE lock_key 30
  • 风险:如果在执行 SETNX 成功之后、EXPIRE 之前服务崩溃或重启,该锁将永远不会过期,导致死锁。

1.2 阶段二:原子性加锁

Redis 2.6.12 之后,官方支持在 SET 命令中直接携带条件和过期时间:

SET lock_key unique_value NX PX 30000
  • NX:仅在 Key 不存在时设置成功。
  • PX 30000:设置毫秒级过期时间(30 秒)。
  • unique_value:随机生成的唯一客户端标识(如 UUID + 线程 ID),严禁使用固定常量。

2. 释放锁陷阱:误删他人锁与 Lua 脚本方案

2.1 误删场景复现

  1. 客户端 A 成功获取锁,超时时间为 10 秒。
  2. 客户端 A 遭遇网络阻塞或耗时 Full GC,执行时间超过 10 秒。
  3. 锁自然过期失效,客户端 B 成功竞争到该锁。
  4. 客户端 A 恢复执行完毕,直接调用 DEL lock_key。
  5. 后果:客户端 A 删除了属于客户端 B 的有效锁,此时客户端 C 又能获取锁,造成严重的数据并发混乱。

2.2 正确姿势:Lua 脚本校验并释放

释放锁必须保证“判断 value 属于自己”与“执行删除”两个动作原子生效:

-- 比较传入的 ARG[1] 是否与 key 的值相等,相等才执行 DEL
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

调用执行:

EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 lock_key unique_value

3. 锁续期机制:如何应对耗时超预期?

即便解决了释放锁原子性,业务未跑完但锁提前失效的问题依然存在。工业界成熟的解法是引入看门狗(Watchdog)自动续期机制(以 Redisson 为代表):

  1. 后台守护线程:当客户端成功持有锁且未显式指定锁超时时,启动后台定时任务。
  2. 定时续期:默认锁租期为 30 秒,看门狗在达到租期三分之一(10 秒)时触发,执行 Lua 脚本将 Key 的过期时间重置回 30 秒。
  3. 安全释放:业务执行完毕显式释放锁,或者应用实例崩溃后守护线程自然消亡,锁将在剩余租期结束后自动失效,既保证不会永久死锁,又防止业务提前“裸奔”。

4. 主从切换与 CAP 取舍

在典型的 Redis 主从 / 哨兵 / 集群架构中:

  • 客户端在 Master 节点加锁成功。
  • Master 尚未将该 Key 同步给 Slave 时突然宕机。
  • 哨兵或集群将 Slave 提升为新 Master。
  • 新 Master 上并不存在该锁,其他客户端可重复加锁。

工程决策权衡:

  • 一般业务场景:Redis 单节点 / 主从方案已能覆盖 99% 的防重需求,轻微并发穿透可通过数据库唯一索引(Unique Constraint)或乐观锁(Version 字段)作为最终兜底防线。
  • 强一致金融级场景:若对一致性要求极高且不可容忍任何锁丢失,应考虑基于 Raft / Paxos 强一致协议的组件(如 ZooKeeper、etcd),或者结合业务状态机进行二次校验。

5. 核心实践 CheckList

  • [ ] 原子性加锁:是否使用了 SET ... NX PX 保证加锁原子性?
  • [ ] 唯一身份标识:Value 是否绑定了唯一的请求/线程标识,而非固定值?
  • [ ] 原子性解锁:是否使用 Lua 脚本先校验所属权再删除?
  • [ ] 续期兜底:长耗时任务是否配置了锁续期或合理预估了超时阈值?
  • [ ] 底层数据库兜底:下游存储是否建立了唯一索引,避免锁失效时发生静默脏写?
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。