REDIS 并发实验 · 乐观锁

WATCH 盯住的不是值,
而是“有没有人动过”

两个请求同时领取最后几张优惠券。连接 A 已经读到库存,但连接 B 抢先修改了同一个 key——看看 A 的事务为什么会整个放弃,以及一次正确重试究竟要重做哪些步骤。

正在竞争的 key stock:coupon:42
当前库存8
成功领取0
WATCH 冲突0
放弃 / 饥饿0
连接边界:WATCH 状态属于执行它的那条 Redis 连接。A 从 WATCH 到 EXEC 必须留在同一条连接上;如果连接池中途换连接,新的连接既没有监视状态,也不能替 A 完成这次校验。
① WATCHWATCH stock:coupon:42
② 重新读取GET stock:coupon:42
③ 入队命令MULTI
DECR stock:coupon:42
④ 冲突窗口连接 B 可能写入同一 key
⑤ EXEC成功返回结果;冲突返回空

等待开始。可以自动运行,也可以单步推进到冲突窗口后手动让 B 插队。

尝试记录

尝试A 读取库存监视版本EXEC退避
尚无记录

冲突后不能只重发 EXEC

A 的读取依据已经过时,上一轮排队的命令也被 Redis 丢弃。下一次尝试必须从 WATCH 开始,再次 GET 最新库存,然后重新计算并入队。

重试上限是在保护系统

热点 key 持续被写时,某个请求可能反复冲突。退避能减少同时抢跑,最大重试次数则避免请求无限占用线程;达到上限后应返回“稍后再试”,并记录饥饿指标。