redis 6.0+ 允许 numkeys=0 是因集群模式下引入了对无状态 lua 脚本的显式支持,前提是脚本不调用任何访问 key 的 redis 命令;早期版本(如 2.6–5.0)因集群键槽路由机制未完善,强制要求至少一个 key 以确保命令可被正确路由到对应节点。

Redis 6.0 允许 EVAL 和 EVALSHA 的 numkeys 参数为 0,即不传任何 KEYS,这并非“放开限制”,而是明确支持无状态、只读或纯计算类 Lua 脚本——前提是脚本里**不调用任何会访问 key 的 Redis 命令**(如 redis.call("GET", ...))。
为什么 numkeys=0 在 6.0+ 才被正式允许?
早期 Redis(KEYS,哪怕 numkeys=0 也放行。
- 脚本中若误调用了
redis.call("SET", "x", "y")但没声明 key,运行时直接报错:(error) ERR Error running script (call to f_...): @user_script:3: @user_script: 3: Write commands not allowed after non deterministic commands or from scripts executed in replication context - 集群模式下,
numkeys=0的脚本会被路由到任意 master 节点(无 slot 冲突),但要注意:如果脚本里硬编码了 key 名并调用redis.call,仍可能因跨 slot 报错 - Redis 7.0 的
FUNCTION LOAD默认也接受numkeys=0,且函数注册后可被所有节点识别,比EVAL更适合长期复用的无状态逻辑
哪些场景真正需要 numkeys=0?
不是“能不用 key 就不用”,而是业务逻辑天然不依赖 Redis 数据存储。典型例子:
- 生成唯一 ID:
return math.random(100000, 999999) .. os.time() % 1000(注意:生产环境别真这么写,只是示意无状态) - 时间格式转换:
return os.date("%Y-%m-%d", tonumber(ARGV[1])),输入时间戳,输出字符串 - JWT payload 解析(仅验证签名合法性,不查 Redis):
local header, payload = string.match(ARGV[1], "(.-)%.(.-)%."),配合redis.sha1hex做 HMAC 校验 - 批量参数预处理:客户端传入 10 个原始值,脚本统一转小写、去空格、截断,再返回处理后的数组——全程不碰任何 key
redis.call 和 redis.pcall 在无 key 场景下的行为差异
即使不操作 key,脚本里仍可能触发 Redis 内部命令(比如 redis.sha1hex、redis.log、redis.error_reply)。这时两者的区别就暴露出来了:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
redis.call("sha1hex", "hello"):正常返回"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d" -
redis.call("time"):合法,返回当前服务端时间数组,numkeys=0下可用 -
redis.call("get", "missing"):运行时报错,因为"get"是 key 相关命令,且你没声明它为KEYS -
redis.pcall("get", "missing"):同样报错,pcall只捕获错误类型,不绕过 key 访问检查
关键点:是否允许调用某个 Redis 命令,取决于该命令是否属于「key-space 操作」,和用 call 还是 pcall 无关。
容易忽略的坑:AOF / RDB 和复制安全
无 key 脚本看似安全,但仍有隐性风险:
- 如果脚本里调用了
redis.log,日志内容会进入 AOF(除非配置lua-log-level关闭) - Redis 7.0 的
FUNCTION LOAD自动同步到从节点和 RDB,但EVAL不同步——同一段numkeys=0脚本,在主从上执行结果可能不一致(比如依赖本地时间或随机数) - 脚本中用
math.random()默认是伪随机,且未调用math.randomseed(os.time())时,每次执行结果固定;但如果在集群多节点上并发执行,又没 seed,可能意外产生重复值 - 调试时用
redis.debug输出,上线前必须删掉,否则可能因输出超长触发lua-time-limit中断
最常被跳过的一步:无状态脚本也要考虑幂等性。比如用脚本生成订单号,若客户端重试导致多次调用,而脚本本身不校验已存在,就会产生脏数据——这不是 Redis 的问题,但容易归咎于“numkeys=0 不安全”。










