秒杀扣库存需要连续读写多个 Redis Key,你会选择事务、Lua 还是 Pipeline?
先说结论:Pipeline 在客户端批量发送命令,主要减少网络往返,不保证其他客户端命令不会穿插;MULTI/EXEC 把命令排队后连续执行,可配合 WATCH 做乐观并发控制;Lua 脚本在服务端原子执行,适合带条件的多步读写。
我先给结论,再说明它在项目里解决什么问题。比较批量发送、命令排队、乐观锁和服务端原子脚本的语义。Pipeline 在客户端批量发送命令,主要减少网络往返,不保证其他客户端命令不会穿插;MULTI/EXEC 把命令排队后连续执行,可配合 WATCH 做乐观并发控制;Lua 脚本在服务端原子执行,适合带条件的多步读写。 Redis 事务执行中某条命令发生运行时错误,其他已排队命令仍可能继续执行,不提供关系数据库那样的自动回滚。
核心机制我会按一次真实执行过程来讲。沿着「客户端组织多条命令 → 选择批量/事务/脚本 → 服务端顺序执行 → 处理错误与返回值 → 重试并保证业务幂等」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 客户端组织多条命令 Pipeline 只批量发送和读取响应,减少 RTT,不提供隔离与原子性。 选择批量/事务/脚本 MULTI/EXEC 把命令排队后连续执行,但不支持自动回滚,运行期错误不会撤销已成功命令。
实现细节只抓关键入口,不会整段背源码。src/multi.c:MULTI/EXEC/WATCH 执行。 src/scriptlua.c:脚本原子执行与超时。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
放到生产使用时,我会关注参数和验证数据。在响应丢失后重发 Pipeline、事务和幂等 Lua,比较余额/库存最终值;制造 WATCH 高冲突。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「脚本 P99」为主基线,记录值应满足「<1ms示例」;同时保存 批次大小与响应字节、脚本执行时长,使后续变化能够回到同一时间轴比较。
最后补充常见误区和使用边界。客户端未收到响应就重发整批命令,第一次其实已执行成功,余额被再次扣减。Pipeline 不能提供恰好一次;改用带请求 ID 的 Lua,在同一脚本中检查去重记录并更新余额。 把 Pipeline 当事务:批次大小与响应字节:需要原子的读改写用短 Lua。 Lua 遍历大集合阻塞实例:脚本执行时长:Pipeline 重试带业务幂等键。 方案:更适合的场景:主要收益:代价与边界。 Pipeline:大量独立命令批量执行:减少网络 RTT、吞吐高:不原子,错误逐条处理。 MULTI/EXEC + WATCH:短事务与乐观冲突:Redis 原生、无需脚本:冲突重试且无回滚。 Lua/Function:有条件的原子读改写:一次往返、语义集中:脚本阻塞风险与版本治理。