登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis 8.8 INCREX 怎么替代简单的 Lua 限流计数

来源:17golang原创

时间:2026-09-09 03:48:17 131浏览 收藏

如果旧的 Redis 限流脚本只是“给某个用户的计数器加 1、限制上限、设置固定窗口过期时间”,Redis 8.8 可以优先考虑 INCREX。它把计数、上限、过期和“只在新窗口设置 TTL”放进一个原子命令,返回值还会告诉你本次实际增加了多少。达到上限时默认返回实际增量 0,应用可以直接拒绝请求,不必再额外读取一次。

迁移边界很明确:单键固定窗口适合 INCREX;滑动窗口、多键联动、复杂条件和需要写入业务结果的脚本,仍然保留 Lua 或应用层编排。

要点速览
  • INCREX key BYINT 1 UBOUND 100 EX 60 ENX 可表达“60 秒最多 100 次,窗口内不续期”。
  • 返回数组的第一个元素是新值,第二个元素是实际增量;实际增量为 0 时表示这次没有拿到额度。
  • 默认越过上限会跳过更新;只有明确需要截断到边界时才使用 SATURATE

先看旧 Lua 限流到底解决了什么

经典固定窗口通常把计数和过期拆成两条命令:先对 rate:user:42 执行 INCR,第一次创建时再执行 EXPIRE,最后读取计数判断是否超过上限。问题在于,多个客户端可能在这几步之间交错,计数、TTL 和放行判断不一定处在同一个原子边界里,所以常见做法是把它们包进 Lua。

INCREX 的价值不是“所有限流都不用脚本”,而是把这个最小单键场景直接下沉到 Redis 命令。官方文档将它标为 O(1),并说明不存在的 key 会先按 0 参与计算。

用一个 INCREX 表达固定窗口限流

下面的命令表示:用户 42 在一个固定的 60 秒窗口内最多消耗 100 个整数额度;窗口第一次建立时设置 TTL,后续请求只增加计数,不把窗口重新延长。

# 每次请求消耗 1 个额度;只有新窗口才设置 60 秒 TTL
redis-cli INCREX rate:user:42 BYINT 1 UBOUND 100 EX 60 ENX

# 查看窗口剩余寿命,确认后续请求没有反复续期
redis-cli TTL rate:user:42

这里的 UBOUND 100 是上限,EX 60 是窗口长度,ENX 表示只有 key 当前没有 TTL 时才应用这次过期参数。不要把 EXENX 混为“每次都刷新 60 秒”:没有 ENX 时,每次命令都可能重设过期时间,窗口会变成滑动的“最后一次请求后 60 秒”。

Redis INCREX 将请求计数器、BYINT 增量、UBOUND 上限与 EX ENX 窗口时间域关联的静态技术框图
图1:INCREX 把额度计数、上限和窗口生命周期放在同一个原子命令边界中。

根据实际增量决定放行还是拒绝

INCREX 返回两个元素:第一个是更新后的值,第二个是实际应用的增量。比如当前值为 99,再申请 1 次,返回可以理解为 [100, 1];继续申请 1 次,默认越过上限,返回类似 [100, 0],key 和 TTL 都不变。

客户端应优先读取第二个元素,而不是仅看第一个元素。第一个元素只能说明窗口中的当前计数,第二个元素才直接回答“本次请求有没有成功占用额度”。伪代码可以写成:

# 第二个返回值代表本次真正增加的额度
new_value, actual_increment = redis_client.execute_command(
    "INCREX", "rate:user:42", "BYINT", 1,
    "UBOUND", 100, "EX", 60, "ENX"
)

# 实际增量为 0,说明已经触及上限
if actual_increment == 0:
    return reject_request("rate limit exceeded")
return allow_request(new_value)

如果业务需要一次消耗多个额度,把 BYINT 1 换成相应的整数即可;如果使用 BYFLOAT,返回值会按浮点数处理。计数 key 必须保持数值字符串,否则会收到类型或数值解析错误。

默认拒绝和 SATURATE 不是一回事

写法越过 UBOUND 时适合场景
默认模式跳过更新,返回实际增量 0,TTL 保持不变请求限流、额度扣减失败即拒绝
SATURATE把结果截到上限,返回被截断后的实际增量允许“最多累计到上限”的计量或展示值

固定窗口限流通常选择默认模式,因为“拒绝本次请求”比“把计数写到上限后再猜是否放行”更容易表达。SATURATE 只是改变越界处理,不会把命令变成滑动窗口,也不能替代业务幂等。

哪些限流场景仍然应该保留 Lua

如果一次判断只涉及一个 key、一个固定窗口和一个上限,迁移到 INCREX 通常能减少脚本维护与参数拼接。但以下情况不要为了追求“少一段 Lua”而硬迁移:

  • 多键联动:同时检查用户、IP、接口和租户四个计数器,并要求全部成功后再一起扣减。
  • 滑动窗口:需要按时间戳集合统计最近 N 秒,而不是一个固定 TTL 的计数器。
  • 业务副作用:放行后还要写入队列、记录审计字段或更新另一类 Redis 数据结构。
  • 复杂回滚:一组条件中部分成功时需要恢复多个 key 的原值。
Redis INCREX 单键固定窗口适用区与多键限额滑动窗口和业务副作用 Lua 保留区的边界分析图
图2:单键固定窗口适合 INCREX,多键规则、滑动窗口和业务副作用仍位于 Lua 或应用编排边界。

迁移前的检查清单

  1. 确认服务端实际运行 Redis 8.8 或更高版本,并在目标部署形态中检查 INCREX 是否可用。
  2. 确认原 Lua 脚本确实只有单 key 固定窗口计数,不要把多 key 业务规则误判成简单限流。
  3. 固定 key 命名、窗口长度、上限和每次消耗量,先在灰度流量下比较拒绝率与 TTL 行为。
  4. 客户端按数组解析返回值,特别测试首次创建、达到上限、窗口过期和错误类型四个分支。

相关问题

INCREX 能完全替代 Redis Lua 限流吗?

不能。它主要替代单 key 固定窗口计数这一类脚本;多 key、滑动窗口、复杂副作用仍需要 Lua 或应用层方案。

为什么要用 ENX?

ENX 让 TTL 只在没有过期时间时设置,避免每次请求都刷新窗口。固定窗口限流通常需要这个语义。

达到 UBOUND 后还会延长 TTL 吗?

默认越界会跳过操作,key 和 TTL 都保持不变;窗口自然过期后,下一次请求才会开始新窗口。

小结:把旧脚本先拆成“单 key、固定窗口、一次计数、一次上限判断”四个条件。四项都满足时,用 INCREX + UBOUND + EX + ENX 表达最直接;只要出现多键协同或时间序列统计,就保留原有脚本边界。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>