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

Redis 8.8 INCREX 怎么做窗口限流:计数、上限和过期一次核对

来源:17golang原创

时间:2026-07-21 11:42:03 123浏览 收藏

做登录接口每分钟100次的限流时,常规的老写法一般是先给Redis做计数累加,再单独给键设置过期时间。这两个操作中间一旦出现异常断开,或者刚好插进来并发请求,限流窗口就可能丢失TTL,后续的请求根本没法确认自己有没有正确拿到配额。Redis 8.8 的 INCREX 直接把计数校验、配额上限判断和过期时间设置整合成一个原子操作,刚好补上了这段很容易出bug的空隙。

要点速览
  • INCREX 从 Redis 开源版 8.8.0 开始正式提供,时间复杂度为 O(1)。
  • UBOUND 100 遇到第101次超限请求时会返回实际增量0,默认不会让计数突破预先设置的上限。
  • EX 60 ENX 只会在当前新窗口没有TTL的时候设置60秒过期,后续进来的请求不会反复重置续期时间。
  • 旧版本的Redis不识别这条命令,要提前保留Lua脚本或者“计数+过期原子封装”的兼容兜底路径。

Redis 8.8 为什么新增 INCREX

传统的固定窗口限流逻辑至少要走 INCREXPIRE 两个步骤。第一次请求创建计数键,第二步再设置窗口过期;如果客户端两步之间直接断开,或者多个客户端同时判断“这是个新键”,就会出现键没有过期时间、限流窗口被无限制拉长等问题。把这段逻辑塞进Lua脚本可以保证原子性,但每个项目都得单独维护脚本、参数传递规则和返回值约定,额外的维护成本不低。

INCREX 是Redis 8.8新增的原生命令,除了常规的累加增量之外,还支持直接设置 EXPXEXAT 或者 PXAT,在同一次请求操作里就能给计数加上上下边界限制。它会返回两个值:更新后的新计数、实际生效的增量,返回结果可以直接拿来判断“放行还是拦截”,不用再多做额外判断。

Redis 8.8 INCREX 窗口限流的请求、计数、UBOUND 检查与放行链路

60秒窗口限流的最小写法

下面的示例把用户ID拼进限流键名,实现每个用户60秒内最多放行100次的规则。先清理测试用的旧键,再分别观察首次请求、计数满额之后的返回结果。

redis-cli DEL ratelimit:user-42
redis-cli INCREX ratelimit:user-42 BYINT 1 UBOUND 100 EX 60 ENX
redis-cli TTL ratelimit:user-42
redis-cli INCREX ratelimit:user-42 BYINT 1 UBOUND 100 EX 60 ENX

第一次调用会创建全新的计数键,典型返回是 11;键的剩余TTL会非常接近60秒。同一个窗口内再次调用,计数会正常累加但TTL不会被重置回60秒。等到计数涨到100之后,下一次调用会直接返回当前计数值和 0,应用层拿到这个结果就可以直接返回HTTP 429限流响应。

1) (integer) 1
2) (integer) 1
1) (integer) 60
2) (integer) 0

这里的 ENX 参数非常关键:它要求只有在键当前不存在过期时间的时候才写入TTL。要是漏掉这个参数,每次请求都可能重新覆盖写入窗口过期时间,原本的固定窗口就会变成“最后一次请求结束后再等60秒”的类滑动窗口效果,完全不符合固定限流的预期。

UBOUND、SATURATE 和实际增量怎么选

不带 SATURATE 参数的时候,请求越过 UBOUND 上限的操作会被直接拒绝,键的计数值和原有TTL都保持不变,返回的实际增量是0。这是限流场景最常用的行为,上层调用方只需要判断第二个返回值是不是大于0就能知道有没有拿到配额。

如果业务侧希望计数值最多停在设定的上限,不要把越界的操作直接当成失败,就可以加上 SATURATE

redis-cli SET quota:team-a 99
redis-cli INCREX quota:team-a BYINT 5 UBOUND 100 SATURATE
redis-cli GET quota:team-a

执行结果会直接把值封顶到100,返回的实际增量是1。这个选项更适合“累计消耗不超过总预算”这类场景,不能直接拿来判断请求有没有拿到本次的限流名额;常规限流场景还是建议保留默认的越界拒绝语义更稳妥。

选项用途边界行为
UBOUND设置计数值上限越过边界默认不改写键值,实际增量返回0
LBOUND设置计数值下限适合带负增量调整的余额或者配额扣减场景
SATURATE对数值执行封顶或封底返回截断之后实际生效的增量
ENX只给新建窗口设置TTL键已经有TTL的时候不会做续期操作

从 INCR 加 EXPIRE 迁移时要留意什么

第一个要过的门槛是服务端版本校验。INCREX 从Redis开源版8.8.0开始提供,正式升级之前可以先执行下面的命令确认目标实例能不能正常识别这条命令:

redis-cli COMMAND INFO INCREX
redis-cli INFO server | grep redis_version

生产环境如果还有7.x或者更早版本的实例,不要直接把新命令全量下发到所有节点。可以先在客户端侧按实例版本做分支选择:8.8版本走 INCREX ... UBOUND ... EX ... ENX 新命令,旧版本继续用之前已经跑稳了的Lua原子脚本。灰度观察阶段重点关注限流命中率、429响应数量、限流键的TTL分布,以及有没有出现“命令不存在”的报错。

第二个要注意的门槛是客户端返回值解析。很多官方客户端暂时没有给这条新命令封装专属方法,需要用通用自定义命令的接口调用,再把返回的数组拆成两个单独的数值字段。不要只取返回的第一个计数字段做判断,不然计数满100之后很容易把“当前值是100”误判成“本次请求成功拿到配额”。

Redis INCR 加 EXPIRE 与 Redis 8.8 INCREX 的原子窗口限流对照

上线前的四个检查点

  • 键名要带上稳定的业务限流维度,比如 ratelimit:user-42,不要把所有用户的请求都统计到同一个公共键里。
  • 窗口时长要和业务对外公示的规则对齐,EX 60 是从第一次请求进入时开始计时,不是按自然整分钟的起点对齐。
  • 逻辑里要明确判断只有第二个返回值大于0的时候才放行,返回0的时候直接映射成明确的限流响应。
  • Redis集群版本和所用客户端都要确认支持8.8新命令,仍在运行旧版本的节点要提前准备好可回退的兼容路径。

常见问题

INCREX 和 INCRBY、EXPIRE 有什么区别?

INCRBY 只负责修改计数值,EXPIRE 单独负责操作TTL,两个命令组合使用需要额外做原子性封装;INCREX 在一次原生命令里就能完成计数累加、边界判断和过期设置三个动作。

为什么窗口限流要加 ENX?

没有加 ENX 参数的话,每次请求都可能重新写入过期时间,导致限流窗口不断向后移动。ENX 可以保证TTL只有在窗口第一次创建的时候才会被写入。

返回的第二个数字为 0 代表什么?

一般代表请求越过了预先设置的上下边界,计数值没有被实际修改。固定窗口限流的场景下,这个返回值就是“本次没有拿到可用配额”的直接信号。

Redis 7 能使用 INCREX 吗?

不能直接使用。Redis 7 要继续用已经验证过的兼容实现,在客户端侧提前根据服务端版本或者能力探测的结果自动选择对应执行路径即可。

最后的落地判断

如果线上服务已经在运行Redis 8.8,做固定窗口限流可以优先用 INCREX + UBOUND + EX + ENX 这组最小参数组合:单命令原子操作、O(1)复杂度、直接返回实际增量,代码里的放行判断逻辑也会更清晰简洁。要是环境还跑在旧版本上,先保留之前已经验证稳定的原子脚本,等灰度确认好客户端返回解析逻辑和核心监控指标之后再逐步切换就行。

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