EVAL 是什么
EVAL 把一段 Lua 脚本发送到服务端执行,整个脚本作为一个命令运行,中间的读、判断、写不会被其他命令插入,因此不需要 WATCH 重试。库存扣减、限流计数、锁释放这些「读完再判断再写」的逻辑用它一次完成最稳;代价是脚本运行期间会占住单线程实例,脚本必须短小。
基本信息
| 适用版本 | EVAL 需 Redis 2.6+;EVAL_RO 需 7.0+ |
|---|---|
| 参数传递 | 键放 KEYS、值放 ARGV,不要拼进脚本文本 |
| 执行方式 | EVAL 直接传脚本;EVALSHA 配合 SCRIPT LOAD 省带宽 |
| 耗时上限 | lua-time-limit 默认 5000 毫秒,超时只记录不快停 |
| 集群限制 | 脚本涉及的所有 key 必须落在同一槽,可用 hash tag 保证 |
典型使用场景
- 秒杀扣库存:判断与 DECR 在一段脚本里完成,杜绝超卖
- 接口限流:INCR 与首次设过期原子完成,避免计数无过期时间
- 分布式锁释放:GET 比对 token 与 DEL 合成一步
- 批量条件写入:按当前状态决定是否更新,避免多轮往返
- 多键一致性读:一次脚本内读取多个键,避免读到中间状态
- 降低网络往返:高频小操作合并成一次调用,减少 RTT
示例
示例
-- KEYS[1]=库存 key;返回剩余库存,-1 表示无货
local stock = tonumber(redis.call('GET', KEYS[1]) or '-1')
if stock <= 0 then return -1 end
redis.call('DECR', KEYS[1])
return stock - 1
EVAL "local s = tonumber(redis.call('GET', KEYS[1]) or '-1'); if s <= 0 then return -1 end; redis.call('DECR', KEYS[1]); return s - 1" 1 stock:1001
SCRIPT LOAD "return redis.call('INCR', KEYS[1])" # 返回 sha1,之后用 EVALSHA 复用
EVALSHA 3c9c1b6e8f0a1d2c3b4a5e6f7a8b9c0d1e2f3a4b 1 counter:api返回值映射
| Lua 返回 | 客户端收到 | 用途 |
|---|---|---|
| number | 整数 | 库存余量、计数结果 |
| string | 批量字符串 | 状态描述或拼接结果 |
| table | 数组 | 一批键值结果 |
| true / 1 | 整数 1 | 操作成功标记 |
| false / nil | nil | 未命中等空语义 |
脚本运行期间阻塞整个实例:只写 O(1) 或 O(logN) 级逻辑;大集合遍历、全量 SCAN 千万别放进来。
脚本长时间运行会导致集群内所有请求排队:发现脚本卡住时无法直接 KILL,只能等服务端触发超时行为。上线前用 SCRIPT LOAD 加真实数据量做验证。
Redis 7.0 起提供 EVAL_RO 与 EVALSHA_RO,脚本内只允许只读命令,统计类场景用它可以避免误写;只读脚本也能在只读副本上执行,写入脚本则不行。