Redis Lua 脚本执行时不被穿插
Redis Lua 脚本可以先读取数据、判断条件,再更新数据,而不让另一个客户端的命令插进这些步骤之间。但这不意味着每个语句都会成功,也不意味着后面发生错误时会撤销前面的写入,更不意味着执行结果自动具备持久性。原子执行与失败回滚,是必须拆开理解的两个问题。 把判断放在数据旁边 例如,仅当令牌仍属于当前持有者时才删除它。客户端先 GET、稍后再 DEL,中间可能已经被新持有者替换了值;随后删除就误伤了别人。脚本可以在同一次执行中比较当前值,匹配才删除。因此旧持有者即使带着之前读到的令牌来操作,也不会直接删除新持有者的值。 脚本应通过 redis.call 访问 Redis 数据,这正是脚本接口的用途。沙箱不提供普通文件系统或网络访问,不能把邮件、支付或文件读写搬进脚本。所有会访问的键都应通过 KEYS 传入,普通值用 ARGV,不要根据数据库里读出的内容临时拼接键名。Redis Cluster 中的常规多键使用还要求相关键位于同一哈希槽。 如果原生原子命令已经表达了条件,应优先使用它。Redis 8.4 新增的 DELEX 配合 IFEQ 就能按字符串值比较后删除。下方 Lua 版本用于理解分支与响应,不代表需要用自定义代码替换原生命令。删除所有权令牌本身也不是完整的分布式锁协议,无法单独阻止过期工作者继续修改外部资源。 坏掉的样子 没有被捕获的 redis.call 错误会让脚本停止。例如先 SET 一个标记,再对字符串键执行 LPUSH,后者失败时,前面的 SET 仍然保留,而失败语句之后的代码不会继续运行。这又与 MULTI/EXEC 有区别:事务中单条命令发生运行期错误,通常不会阻止后面的排队命令继续执行。两者都不提供先前成功写入的自动回滚。 redis.pcall 会把 Redis 命令错误作为包含 err 字段的表交回脚本,由脚本显式判断并决定是否继续。捕获错误既没有撤销旧写入,也不能把失败的业务操作自动变成成功。应尽可能在第一条写入之前校验参数和预期类型,并明确剩余失败路径如何收场,而不是依赖脚本名义上的原子性掩盖部分结果。 ...