redis lua脚本的原子化能力在于单次请求中完成带逻辑判断、状态校验、条件分支及多键协同的完整业务闭环,全程无竞态、无中间态暴露。

Redis Lua 脚本的原子化能力,不止于“把几个命令包在一起执行”。它真正价值在于:在单次请求中完成带逻辑判断、状态校验、条件分支甚至多键协同的完整业务闭环,且全程无竞态、无中间态暴露。
库存扣减 + 预占 + 超时释放的一体化实现
单纯 DECR 不足以支撑秒杀场景——用户下单后可能放弃支付,需预留库存并自动释放。Lua 脚本能在一个脚本里完成三件事:检查可用库存、写入预占记录(含过期时间)、扣减主库存。
- 用 redis.call('SET', 'lock:order:'..itemId, clientId, 'NX', 'EX', 60) 实现带租约的轻量锁,避免重复预占
- 用 redis.call('GET', 'stock:'..itemId) 和 redis.call('GET', 'reserved:'..itemId) 同步读取主库存与已预占数
- 仅当 current_stock - reserved >= need 时,才执行 redis.call('INCRBY', 'reserved:'..itemId, need) 和 redis.call('DECRBY', 'stock:'..itemId, need)
- 返回结果包含预占成功标识、剩余可售数、过期时间戳,便于客户端决策
分布式 Token 校验与幂等状态写入原子合并
防重 Token 不能只删不记——删完瞬间若服务宕机,同一请求重试就会重复处理。高级做法是:校验 Token 存在性的同时,立即写入“已处理”状态,并设置相同 TTL。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 脚本内用 local tokenVal = redis.call('GET', KEYS[1]) 获取原始 Token 值(如订单ID)
- 接着调用 redis.call('SET', 'processed:'..tokenVal, '1', 'EX', ARGV[1]) 写入幂等标记
- 最后 redis.call('DEL', KEYS[1]) 删除 Token,三步不可分割
- 若 Token 已不存在,直接返回 0;否则返回 1 并确保后续同 Token 请求必命中 processed key
多维度计数器联动更新
比如“用户今日发帖数 + 总发帖数 + 活跃度积分”需同步变更,且总发帖数达阈值时额外加奖励积分。普通命令组合极易出现部分更新成功、部分失败的情况。
- 脚本中先 redis.call('INCR', 'post:today:'..uid) 和 redis.call('INCR', 'post:total:'..uid)
- 再 local total = redis.call('GET', 'post:total:'..uid),判断是否为 100/500/1000 等里程碑
- 满足则 redis.call('INCRBY', 'score:'..uid, 100),并 redis.call('LPUSH', 'milestone:log', uid..' '..total) 记录事件
- 所有操作要么全成功,要么全不发生——没有“今日+1但总帖没+1”的中间态
带乐观锁语义的字段级更新
对 Hash 中某个 field 做条件更新(如 only if version == X),Redis 原生不支持,但 Lua 可模拟。
- 读取整个 Hash:local data = redis.call('HGETALL', KEYS[1])
- 解析 version 字段:local curVer = data['version'] and tonumber(data['version']) or 0
- 比对传入版本:if curVer == tonumber(ARGV[1]) then ... end
- 更新时同时递增 version:redis.call('HSET', KEYS[1], 'version', curVer + 1, 'value', ARGV[2])
- 返回旧 version 或新 version,供上层判断是否冲突
不复杂但容易忽略:脚本执行时间必须控制在毫秒级,否则会阻塞整个 Redis 实例;所有 key 必须显式通过 KEYS 传入,不能硬编码;涉及多个 key 时,注意客户端路由一致性(集群模式下 key 需在同一 slot)。










