原有 Lua 实验中,旧 owner-a 请求删除时保留当前 owner-b;owner-b 匹配时才删除,键不存在则返回零。未捕获的 redis.call 错误保留此前写入但停止后续语句;明确的 NOSCRIPT 表示代码缺失。

Redis Lua 脚本执行时不被穿插

Redis Lua 脚本可以先读取数据、判断条件,再更新数据,而不让另一个客户端的命令插进这些步骤之间。但这不意味着每个语句都会成功,也不意味着后面发生错误时会撤销前面的写入,更不意味着执行结果自动具备持久性。原子执行与失败回滚,是必须拆开理解的两个问题。 原有 Lua 实验中,旧 owner-a 请求删除时保留当前 owner-b;owner-b 匹配时才删除,键不存在则返回零。未捕获的 redis.call 错误保留此前写入但停止后续语句;明确的 NOSCRIPT 表示代码缺失。 仅为原有实验示意,本轮未重跑。 把判断放在数据旁边 例如,仅当令牌仍属于当前持有者时才删除它。客户端先 GET、稍后再 DEL,中间可能已经被新持有者替换了值;随后删除就误伤了别人。脚本可以在同一次执行中比较当前值,匹配才删除。因此旧持有者即使带着之前读到的令牌来操作,也不会直接删除新持有者的值。 脚本应通过 redis.call 访问 Redis 数据,这正是脚本接口的用途。沙箱不提供普通文件系统或网络访问,不能把邮件、支付或文件读写搬进脚本。所有会访问的键都应通过 KEYS 传入,普通值用 ARGV,不要根据数据库里读出的内容临时拼接键名。Redis Cluster 中的常规多键使用还要求相关键位于同一哈希槽。 ...

一月 11, 2028 · 3 分钟 · XB Field Notes
原有库存实验中,竞争方把库存从 2 改成 1,监视冲突使旧队列被拒绝,标记仍不存在。重新监视并读到 1 后,第二次尝试一起提交 0 与标记。仅靠 MULTI/EXEC 无法修复此前的旧读取。

WATCH 保护 Redis 中依赖读取的更新决定

MULTI/EXEC 能阻止别的客户端命令插进队列的执行过程,却不会自动保护应用此前已经读到的值。如果两个购买方先读到相同库存,再各自写回计算结果,即使每次 SET 都放进事务,也仍然可能丢失一次更新。问题出在读取与提交之间的空隙,而不是 SET 执行得是否足够连续。 原有库存实验中,竞争方把库存从 2 改成 1,监视冲突使旧队列被拒绝,标记仍不存在。重新监视并读到 1 后,第二次尝试一起提交 0 与标记。仅靠 MULTI/EXEC 无法修复此前的旧读取。 仅为原有实验示意,本轮未重跑。 先监视,再读取依赖 WATCH 让 EXEC 变成有条件的执行:被监视的键从建立监视之后不能发生修改。因此,必须先 WATCH,再读取参与决策的数据。若键在提交前改变,Redis 拒绝整个队列并返回空响应;这只是不执行自己的队列,不会撤销竞争方已经完成的修改。 监视与事务状态都属于同一条连接。redis-py 流水线在 watch 之后保留连接,multi 之前的 GET 会立即发出,应用可以读取结果并做判断;multi 之后才转为缓冲命令,等待 execute。用上下文管理器包住一次尝试,可以在结束时重置流水线状态并释放专用连接。 ...

七月 22, 2027 · 3 分钟 · XB Field Notes