直接用INCR或HINCRBY不够用,因其无法原子执行“读-判-写-删”整套逻辑,高并发下易因竞态导致计数错乱;Lua脚本能在一个原子操作中完成条件判断、多键联动与TTL设置,确保限流准确。

为什么直接用 INCR 或 HINCRBY 不够用?
实时统计常要满足原子性+条件判断+多键联动,比如“每分钟只允许用户提交 3 次表单,超限则返回错误”。单纯靠客户端先 GET 再 INCR 会因并发导致计数错乱;而 INCR 本身不支持条件跳过或带 TTL 的复合逻辑。Lua 脚本能在一个 Redis 原子操作里完成读、算、写、删整套流程,避免竞态。
- 条件判断必须在服务端完成,不能依赖客户端逻辑
- 多个 key(如计数器 + 时间戳 + 黑名单)需同步更新,否则状态不一致
- 避免网络往返开销:一次请求完成全部逻辑,尤其高频场景下延迟敏感
怎么写一个带时间窗口和限流的 Lua 脚本?
核心是利用 Redis 的 TIME 和 EXPIRE 配合哈希结构,让每个窗口独立计数。以下脚本实现“按用户 ID + 分钟级时间窗口限流”:
local user_key = KEYS[1]
local window_key = KEYS[2]
local max_count = tonumber(ARGV[1])
local current_ts = tonumber(ARGV[2])
local window_sec = 60
<p>-- 获取当前窗口起始时间(向下取整到分钟)
local window_start = current_ts - (current_ts % window_sec)
local window_end = window_start + window_sec</p><p>-- 用哈希保存 {timestamp: count},key 是窗口起始秒数
local count = tonumber(redis.call("HGET", user_key, window_start) or "0")</p><p>if count >= max_count then
return 0 -- 超限
end</p><p>redis.call("HINCRBY", user_key, window_start, 1)
redis.call("EXPIRE", user_key, window_sec * 2) -- 留一点缓冲,防窗口漂移</p><p>-- 清理过期窗口(可选,避免哈希无限膨胀)
for _, k in ipairs(redis.call("HKEYS", user_key)) do
local ts = tonumber(k)
if ts < window_start then
redis.call("HDEL", user_key, k)
end
end</p><p>return 1-
KEYS[1]是用户维度哈希 key(如"rate:user:123") -
KEYS[2]实际未使用,但预留扩展位(比如用于全局黑名单 key) -
ARGV[1]是最大次数,ARGV[2]是客户端传入的 Unix 秒级时间戳(务必用服务端TIME更可靠) -
HDEL清理逻辑放在最后,避免影响主路径性能;高频场景建议改用定时任务异步清理
调用时怎么避免常见坑?
Redis 执行 Lua 脚本要求所有涉及的 key 必须显式声明在 KEYS 数组里,否则集群模式下会报错 (error) CROSSSLOT Keys in request don't hash to the same slot。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 所有读/写的 key 都得进
KEYS,哪怕只读一次;ARGV只能传参数,不能当 key 用 - 脚本里不能调用带随机性的命令(如
RANDOMKEY),也不能用SELECT切库 - 返回值类型必须是 number/string/boolean/table(且 table 不能嵌套太深),否则客户端解析失败
- 单次脚本执行超时默认是 5 秒,复杂循环或大集合遍历容易触发
BUSY错误,宁可拆成多个小脚本
聚合类统计(如 TOP N)为什么慎用 Lua?
想用 Lua 遍历全量 SCAN 结果再排序?别这么做。Redis 是单线程,Lua 脚本会阻塞所有请求,一旦数据量上去(比如百万级 key),整个实例就卡死。
-
SCAN本身不保证原子性,Lua 里调用它无法规避中间状态变更 - 排序、分页、去重等操作更适合交给客户端或专用聚合服务(如 RedisTimeSeries + TS.MRANGE)
- 真要 TOP N,优先考虑
ZSET+ZREVRANGE,用有序集合天然支持插入即排序
实际中,Lua 最适合“单次决策型”逻辑:计数、开关、简单条件更新。真正的汇总分析,该走离线批处理就走批处理,该上 TSDB 就上 TSDB。

















