跳到正文
Elaine Blog
返回

分布式锁的正确边界

Redis 与缓存工程

Redis 锁适合降低重复工作和短时互斥,但不能单独证明关键资源绝不会被并发修改。只写 SETNXDEL 不够:锁要有过期时间、唯一所有者、原子释放,并让下游用单调 Fence Token 拒绝旧持有者。

Table of contents

Open Table of contents

最小获取与释放协议

获取使用一条命令同时设置“仅不存在时写入”和租约:

SET lock:invoice:7 <owner-id> NX PX 5000

owner-id 必须每次获取都唯一。释放时不能先 GETDEL,因为锁可能在两条命令之间过期并被别人获得。使用 Lua 比较所有者后删除:

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

完整实现见 FencedRedisLock.java下载

租约过期仍会并发

客户端 A 获得 5 秒锁后发生 10 秒 GC 暂停;锁过期,客户端 B 获得锁并写资源;A 恢复后若继续写,就与 B 并发。自动续期只能降低概率,无法让暂停中的客户端及时知道自己已经失去锁。

Fence Token 是每次成功租约对应的单调递增编号。下游资源保存已接受的最大编号,只接受更大的写入。旧客户端即使恢复,也会因为编号过小被拒绝。实验用 INCR 生成编号,但生产设计还要证明编号生成与锁故障模型满足需求。

什么时候不要用 Redis 锁

库存扣减可用数据库条件更新,状态转换可用乐观锁版本号,消息重复可用幂等键。它们通常比跨系统租约更容易证明。涉及资金、所有权或不可逆外部设备时,应把正确性放在能验证 Fence Token 或事务约束的最终资源上。

运行锁集成测试:

mvn -Dredis.uri=redis://:lab-password@localhost:6380 \
  -Dtest=RedisIntegrationTest#lockUsesOwnerCheckAndFencingToken test

下一步

继续阅读07-07 Cache-Aside、穿透、击穿与雪崩,从互斥控制转向缓存失效时的数据库保护。


分享这篇文章:

上一篇
主从、Sentinel 与 Cluster
下一篇
Cache-Aside、穿透、击穿与雪崩