不能直接用多个incr命令做多key计数,因为redis多命令非原子,易导致并发覆盖;lua脚本在服务端原子执行,是唯一轻量可靠方案,但需满足keys同slot、避免超时与过度复杂等约束。

为什么不能直接用多个 INCR 命令做多 Key 计数
因为 Redis 的多个命令不是原子的——哪怕只差几微秒,两个客户端就可能读到同一份旧值、各自加 1、再写回去,最终只 +1 而非 +2。分布式环境下,靠客户端重试或加锁(比如 SETNX)成本高、易死锁、还可能丢数据。Lua 脚本在 Redis 服务端原子执行,是唯一轻量又可靠的选择。
- Redis 执行 Lua 脚本时会阻塞当前连接,但整个脚本作为一个不可分割的操作完成,不会被其他命令插入
-
EVAL和EVALSHA都支持传入多个 key,脚本里通过KEYS表访问,完全规避客户端并发竞争 - 注意:
KEYS只能用于真正要操作的 Redis key;如果只是读配置或传参,必须走ARGV,否则会触发集群模式下的CROSSSLOT错误
怎样写一个安全的多 Key 批量自增 Lua 脚本
核心是把所有要操作的 key 放进 KEYS 数组,用 redis.call("INCR", KEYS[i]) 逐个调用,并统一返回结果。不要在脚本里做条件判断后只更新部分 key——那会破坏原子性边界。
-- multi_incr.lua
local results = {}
for i, key in ipairs(KEYS) do
table.insert(results, redis.call("INCR", key))
end
return results
- 调用时必须显式列出所有 key:
EVAL "..." 3 counter:a counter:b counter:c,其中3是 key 的数量 - 如果某个 key 不存在,
INCR自动初始化为 0 再 +1,行为符合预期 - 避免在脚本中使用
redis.call("GET", ...)后再INCR——这等于手动拆解原子操作,失去意义 - 脚本长度别超过 4KB(Redis 默认限制),否则
EVAL会报错ERR Error running script (call to f_...): @user_script: N: user_script: too long
在 Redis Cluster 中执行多 Key Lua 脚本的硬约束
Cluster 模式下,KEYS 中的所有 key 必须落在同一个 slot,否则直接报错 CROSSSLOT Keys in request don't hash to the same slot。这不是脚本能绕过的限制,而是架构决定的。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 解决办法只有两个:用哈希标签强制路由(例如
counter:{user123}:a和counter:{user123}:b),让大括号内内容决定 slot;或者改用单 key 结构,把多个计数存进一个 Hash,用HINCRBY - 千万别用
KEYS *或模糊匹配——Cluster 下不支持,且性能极差 - 如果业务上确实需要跨 slot 计数(比如全局排行榜 + 用户维度计数),那就不能靠单个 Lua 脚本原子完成,得接受最终一致性,用消息队列或定期对账补偿
如何避免 Lua 脚本超时导致连接卡死
Redis 对 Lua 脚本有默认 5 秒超时(lua-time-limit 配置),超时后连接会被断开,脚本中止,但已执行的部分无法回滚——比如前 3 个 key 加了,第 4 个没来得及,就会产生数据倾斜。
- 务必在脚本开头加简单校验,比如
if #KEYS > 100 then return {err="too many keys"} end,防止误传海量 key - 避免在脚本里调用
redis.call("KEYS", "...")或遍历大集合,这些操作在生产环境极易超时 - 上线前用
redis-benchmark -r 10000 -n 10000 --eval multi_incr.lua , key1 key2 key3做压测,观察 P99 延迟是否稳定在毫秒级 - 监控
latency doctor和INFO commandstats中cmdstat_eval的 failed 和 duration 字段,及时发现异常脚本
实际最难的不是写脚本,是厘清哪些场景真的需要多 key 原子性——很多时候用 Hash 或客户端预聚合更简单,也更可控。Lua 是利器,但别把它当万能胶。










