首页/数据/数据库速查/分布式锁落地的六个高频坑
Redis 运维与场景124 条

分布式锁落地的六个高频坑

锁粒度、重入、误删、续期、集群槽位与 fencing 是自研 Redis 锁最常翻车的地方;加锁前先想清楚「锁谁、锁多久、失败怎么办」。

数据库速查数据更新于 2026-10-13

自研 Redis 锁容易翻车的地方

用 SET NX EX 加锁看起来只有一行代码,真正上线后暴露的问题却集中在几个固定位置:锁的粒度太粗、释放时误删别人的锁、TTL 与业务耗时对不上、集群下键落在不同槽、以及把锁当成强一致保证。加锁之前先把「锁谁、锁多久、失败怎么办」想清楚,比事后加班排查划算得多。

基本信息

适用平台Redis 2.6.12+(SET 支持 NX EX 后)
加锁写法SET lock:res value NX EX ttl
释放写法Lua 脚本比对 value 后删除
续期手段看门狗线程定期延长 TTL
集群注意锁键与业务键需要落在同一个槽

六个高频坑

  • 锁粒度:锁到资源 ID(如订单号),不要一把全局锁把所有请求串行化
  • 重入:同一执行流需要重复加锁时要计数,否则自己把自己锁死
  • 误删:释放必须校验持有者标识,不能直接 DEL
  • 续期:TTL 要覆盖 P99 耗时,长任务配合看门狗续期
  • 集群槽位:锁键与业务键用同一 hash tag,避免 CROSSSLOT
  • 兜底:锁只降低并发概率,关键写入仍需唯一索引或版本号等强约束

安全释放与代际校验

安全释放与代际校验
# 加锁:value 用唯一标识(请求 ID + 线程 ID)
SET lock:order:42 req-8f2k NX EX 15
# 释放:Lua 脚本保证比对与删除原子执行
# if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end
# 代际编号:让下游拒绝过期持有者的写入
INCR lock:job:fence            # 得到 7
# 携编号写入,存储层只接受更大的编号
UPDATE jobs SET data = '...', fence = 7 WHERE id = 42 AND fence < 7

锁参数的取舍

参数取小取大
TTL频繁出现双持有者持锁方崩溃后等待时间长
续期间隔额外开销与网络压力大续期不及时导致锁提前失效
重试策略请求容易直接失败高并发下重试放大压力
锁粒度键数量多、管理复杂串行化严重、吞吐下降
常见折中是 TTL 十到三十秒配合看门狗续期:既避免崩溃后长时间无法恢复,又能覆盖较大范围的任务耗时。
Redis 锁不是强一致机制:在时钟漂移、进程长时间暂停、主从切换等场景下双持有者窗口始终存在,涉及资金或库存的写入必须有独立的最终校验。

易错点

  • 不设 TTL:持有者崩溃后锁永不过期,只能人工介入删除才能恢复服务
  • 释放时直接 DEL:可能删掉别人刚加上的锁,造成并发进入临界区
  • 把 Redis 锁当强一致保证:双持有者窗口下的重复写入会破坏业务约束
  • 集群中锁键与业务键落在不同槽:事务或 Lua 脚本直接报 CROSSSLOT 失败

数据来源与参考

  • Redis 官方文档: 分布式锁
  • 分布式锁实践总结

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌