Kong限流插件深度解析:local与redis策略选型与实战
2026/9/24 18:36:26 网站建设 项目流程

最近一次接手公司 API 网关的限流改造,让我真正把 Kong 的 rate-limiting 插件从头到尾研究了一遍。之前一直用的是默认策略,网关是单节点部署,感觉一切正常。后来业务量上来了,网关横向扩到三台,结果限流数字完全对不上——明明配置每分钟 100 次,三台节点加起来能放到 300 次,上游服务直接被冲垮。排查到最后,问题出在 rate-limiting 插件的策略选型上。local 和 redis 这两个策略,如果不搞清楚它们的底层机制,生产环境早晚要踩坑。

这篇文章我不打算只讲配置命令,而是把 rate-limiting 插件在 local 和 redis 两种策略下的实现原理、适用场景、性能差异、配置参数、常见坑一次性说透,既有原理分析,也有可以直接抄作业的实操步骤。适合正在使用 Kong 做 API 网关的开发者、运维同学,以及对限流方案选型有困惑的朋友。

1. rate-limiting 插件到底在限什么

1.1 插件的核心能力与执行时机

Kong 的 rate-limiting 插件,本质上是一个运行在网关接入层的流量控制组件。它负责统计每一个请求是否落在允许的配额范围内,如果超出配额,直接返回 429 Too Many Requests,请求根本不会到达上游服务。这个拦截动作发生在 Kong 的 access 阶段,也就是说,请求刚进入网关、还没转发出去的时候,限流判断已经完成了。

插件的限流维度非常丰富,既可以针对 Consumer 限流,也可以按 Credential、IP、Service、Route 甚至自定义 Header 来限流。实际项目中,最常见的组合是config.limit_by = consumer配合config.header_name = X-Consumer-Username,或者直接config.limit_by = ip做全局限流。这些维度本身不难理解,难的是它们背后的计数存储方式——也就是这里的核心问题——计数器到底存在哪里,怎么保证准确。

1.2 local 和 redis 策略的本质区别

Kong 的 rate-limiting 插件支持多种策略,其中用得最多、也最常被混淆的就是 local 和 redis 两类。很多人以为它们只是“一个不用 Redis、一个用 Redis”的区别,实际上远没有这么简单。

local 策略下,计数器的读写完全发生在当前 Kong 节点的内存中。也就是说,每一个节点独立维护一份“自己的”计数器。单节点部署时,这种策略既快又简单,完全没有网络开销;但多节点部署时,限流统计是分散的,各节点之间互不感知。

redis 策略则是把计数器统一存放在外部 Redis 服务中,所有 Kong 节点共享同一份计数。它解决了分布式场景下“限流总量不准确”的问题,但引入了网络依赖和一致性方面的考量。

简单说来:local 是“各管各的账本”,redis 是“所有人共用一本总账”。下面我分别展开讲。

2. local 策略:单节点与轻量场景的优选

2.1 local 策略的实现原理

local 策略的实现,用的是典型的固定窗口计数法。Kong 节点内部维护一个以时间窗口为键的计数器,例如minute级别的限制,就会存在一个时间粒度为一分钟的计数桶。每个请求进来,对应的桶数值加一;当桶内计数达到上限时,后续请求直接拒绝。

听起来很简单,但有一个关键点很多文章没有讲透:local 策略是纯内存操作,它不依赖任何外部存储,因此它的延迟极低。相对于 Redis 策略每一次计数都要走一次网络请求(哪怕 Redis 就在本机,也有 Unix socket 或 TCP loopback 的开销),local 的策略几乎是零成本。对于压测要求极高、且可以接受网关单节点部署的场景,local 策略是不错的选择。

那它的缺点也很明显。多节点部署时,假设两个 Kong 节点同时收到流量,各自内存中的计数器独立累加。理想情况下,总限流值应该是每个节点配额的总和。如果你配置的限流上限是每分钟 100 次,两台节点各放掉 100 次,加起来就是 200 次,远超预期。如果遇到节点数不固定(比如 HPA 伸缩),限流总量会随节点数量变化,这是最危险的一种情况。

2.2 local 策略的配置示例

local 策略的配置方式很简单,通过 Admin API 给某个 Service 或 Route 添加插件即可:

curl -X POST http://localhost:8001/services/example-service/plugins \ --data name=rate-limiting \ --data config.minute=100 \ --data config.policy=local \ --data config.limit_by=ip \ --data config.fault_tolerant=true

几个关键参数说明一下:

  • config.minute=100表示每分钟允许 100 次请求,Kong 还支持 second、hour、day 等时间单位,也可以同时配置多个单位,比如minutehour同时设定。
  • config.policy=local是本策略的核心,告诉插件计数器存内存。
  • config.limit_by=ip按客户端 IP 维度计数。
  • config.fault_tolerant=true表示限流计数失败时是否放行请求。对 local 策略来说,内存计数几乎不会失败,这个参数影响不大,但配置上建议统一设为 true,保持后续切换策略时行为一致。

配置完成后,可以通过实际请求验证返回头:

curl -I http://localhost:8000/example

如果插件生效,响应头中会看到X-RateLimit-Remaining-MinuteX-RateLimit-Limit-Minute这样的字段,显示当前剩余配额和总配额。

注意:local 策略下,响应头中的剩余配额只代表当前 Kong 节点的计数器状态,并不反映整个网关集群的总配额。

2.3 local 策略什么时候选、什么时候千万别选

我个人的判断标准是这样的:只要你的网关是单节点部署(比如开发环境、内网小规模服务、边缘节点独立部署),或者限流的目标是“保护本节点自身不被打垮”,local 策略是一个很好的选择。

但如果你有五台甚至更多的网关节点在做负载均衡,而业务方希望无论请求打到哪台节点,全局限流总量都不超过某个阈值,那么千万不要选 local。这一点属于我明确踩过的坑:之前部署了三节点网关,限流按 local 配置每分钟 100 次,业务方压测时发现上游收到的流量高达每分钟 300 次,完全失去了限流作用。所以要记住:local 策略只能保证单节点的限额,多节点集群需要的是下一章要讲的 redis 策略。

3. redis 策略:分布式场景下的正确打开方式

3.1 redis 策略为什么能解决多节点问题

redis 策略的核心思路,是把计数器从节点内存中挪到一个所有节点共享的中间存储里。每个请求进来,Kong 节点不是在自己本地做加法,而是请求 Redis 执行一次原子自增操作。

这里必须强调“原子”这个特性。Redis 的 INCR 指令是原子操作,并发请求下不会出现两个节点同时读到相同旧值、各加一次导致计数丢失的情况。这保证了全局限流总量的准确性。

3.2 redis 策略的底层计数机制

Kong 的 redis 策略实现中,计数器使用 Redis 的 key-value 存储,key 通常由限流维度、时间窗口和路由标识拼接而成。大致结构类似这样:

rate-limit:consumer:{consumer_id}:minute:202504011500

每次请求到来,执行一次 INCR,然后对 key 设置过期时间,过期时间一般等于窗口周期的两倍左右。这样做的目的,是避免遗留的计数 key 长期占内存空间。比如minute级别的限制,key 的 TTL 会设置成 120 秒,保证本窗口结束后旧 key 自动清理。

有意思的是,Kong 在实现 redis 策略时并不只使用了 INCR,还利用了 Redis 的事务管道特性来减少网络往返。不过这个细节在参数上体现为事务开关,通常不建议关闭。实际操作中,如果你发现计数器偶尔偏高,排查一下是否关闭了事务或管道选项。

补充一点,Kong 的 redis 策略支持 Redis 单机、Sentinel 高可用和 Redis Cluster 集群。如果生产环境 Redis 是主从或集群架构,配置上要特别注意连接参数。

3.3 redis 策略的配置与关键参数

配置 redis 策略的示例:

curl -X POST http://localhost:8001/services/example-service/plugins \ --data name=rate-limiting \ --data config.minute=100 \ --data config.policy=redis \ --data config.redis_host=192.168.1.10 \ --data config.redis_port=6379 \ --data config.redis_database=0 \ --data config.redis_password=yourpassword \ --data config.redis_timeout=2000 \ --data config.redis_ssl=false \ --data config.fault_tolerant=true

其中重点参数包括:

  • config.redis_hostconfig.redis_port:Redis 服务地址和端口,生产环境建议使用内网地址。
  • config.redis_database:使用的 Redis 库编号,默认 0。如果有多个服务共用 Redis,建议分配给 Kong 独立的数据库,方便管理。
  • config.redis_password:Redis 认证密码,强烈建议必填。
  • config.redis_timeout:每次 Redis 操作的超时时间,单位毫秒。默认 2000ms,在高并发场景下可以适当调低,比如 500ms,避免 Redis 故障时网关请求大量堆积。
  • config.redis_ssl:是否启用 TLS 连接。跨机房或公网场景下建议开启,内网可以关闭以节省加密开销。
  • config.fault_tolerant:这个参数必须单独拎出来讲,后面常见问题部分会详细展开。

提示:如果是 Redis 集群部署,还需要配置config.redis_cluster_nodes参数并置config.redis_cluster_mode为 true,避免连接池报错。

3.4 redis 策略的适用边界与潜在代价

redis 策略不是银弹。它解决了一致性问题,但引入了额外的网络依赖和延迟。每命中一次限流计数,Kong 节点都需要和 Redis 通信一次。假设 Redis 平均响应时间是 0.5ms,那么每秒处理 1000 个限流请求,就意味着 Redis 至少要承受 500ms 的总等待时间,这还不包括网络排队。因此在极高性能场景下,需要认真评估限流本身带来的开销。

另一个容易被忽略的问题是 Redis 的可用性。如果 Redis 实例挂掉,而fault_tolerant配置为 false,那么所有带限流插件的请求都会被直接拒绝;如果配置为 true,限流会临时失效,请求全部放行。这涉及一个经典的运维取舍:宁可让系统被流量冲垮,还是宁可误杀正常请求。一般建议在核心链路上配置 fault_tolerant=true,防止 Redis 故障影响整体可用性,同时做好 Redis 本身的监控告警。

4. 从 local 切换到 redis 的实操过程

4.1 环境准备与 Redis 快速部署

如果你还没有可用的 Redis 环境,最快的方式是使用 Docker 起一个单机实例:

docker run -d --name kong-redis \ -p 6379:6379 \ -e REDIS_PASSWORD=yourstrongpassword \ redis:7-alpine \ redis-server --requirepass yourstrongpassword

生产环境建议不要直接用容器单机模式,而是优先考虑由云厂商提供的托管 Redis,或者内部自建的主从哨兵集群。这一步不需要太复杂的调优,只要保证网络延迟低、可用性高即可。

4.2 保留旧插件、创建新插件的平滑切换法

local 和 redis 策略同时存在时,两个插件会在 Kong 内部按优先级进行判定,如果配置的维度和限制值相同,会出现双重计数的现象。所以从 local 切换到 redis,不能简单地把两个插件都挂在同一个 Service 上。

比较稳妥的切换方式是这样:

第一步,先通过 Admin API 查询已有的 local 限流插件 ID:

curl http://localhost:8001/services/example-service/plugins | jq .

第二步,直接更新该插件的 policy 参数,把 local 改为 redis:

curl -X PATCH \ http://localhost:8001/services/example-service/plugins/{plugin_id} \ --data config.policy=redis \ --data config.redis_host=127.0.0.1 \ --data config.redis_port=6379 \ --data config.redis_database=0

第三步,压测验证。这一步很重要,不要直接切全量流量,先跑一轮小规模压测,确认响应头中的X-RateLimit-Remaining-Minute数值在多节点下递减一致。

4.3 压测对比:local 与 redis 的实际表现

我在内部环境做了一个简单压测,网关是 2 核 4G 的虚拟机,Redis 单独部署在另一台机器上,网络是内网千兆。限流维度都设为按 IP,每分钟 1000 次,使用 wrk 发起请求,结果如下:

指标local 策略redis 策略
单节点最大 QPS(限流判定耗时)约 18000 req/s约 8500 req/s
限流计数准确度单节点准确,多节点超发多节点准确
平均额外延迟接近 0ms约 0.4ms
Redis 故障时行为不受影响受 fault_tolerant 控制
依赖资源节点内存外部 Redis 实例

这个数据说明两点:第一,local 在处理性能上确实有压倒性优势;第二,redis 策略的额外延迟和性能损耗完全可以接受,而且是多节点场景下必须支付的一致性成本。

4.4 切换之后必须做的检查项

切换完成后,至少要做以下几项检查,避免事后踩坑:

  • 检查所有网关节点是否都应用到了新的插件配置,Kong 的配置同步有轻微延迟,批量部署时尤其明显。
  • 检查 Redis 中的 key 是否能正常过期,长时间运行后如果 key 越堆越多,说明 TTL 设置有误。
  • 检查X-RateLimit-Remaining-*响应头是否符合预期,多节点下剩余配额应该是集群级别的全局递减。

5. 常见问题与排查技巧实录

5.1 限流数字不准,是所有网关集群的经典故障

限流不准的最常见原因有三个:一是还在使用 local 策略且网关多节点部署;二是config.hide_client_headers=false导致客户端能看到内部计数,从而绕过了部分限制逻辑(其实这个不会直接造成失效,但会带来安全隐患);三是不同节点的系统时钟不同步,导致时间窗口的边界在每个节点上不一致。

时钟同步这个问题很多人忽略。redis 策略虽然共享计数,但时间窗口的计算在每台 Kong 节点上独立完成。如果节点 A 的时钟比节点 B 快了 5 秒,那么 A 已经进入新的时间窗口,而 B 还在旧窗口,两个窗口的计数被同时累加,总量会短暂超过配置值。解决办法很简单,在所有网关节点上配置 NTP 时间同步服务。

5.2 Redis 连接失败时,请求为什么全被干掉了

这是我在生产环境遇到过的另一个严重事故。当时 Redis 实例因为内存碎片化触发 OOM,Kong 节点连接 Redis 失败,而插件配置的config.fault_tolerant=false,结果所有请求直接返回了 503。因为限流服务不可用,网关选择拒绝请求,整个业务入口瞬间不可用。

处理思路是:将fault_tolerant设置为 true,让限流计数失败时请求默认放行。同时配合 Redis 监控告警,一旦限流依赖的 Redis 出现异常,立即告警运维介入,不要寄希望于网关自身过硬。

还有一种比较隐蔽的情况:Redis 并没有挂掉,但因为连接池被打满导致超时。Kong 有config.redis_pool_size参数控制连接池大小,默认值较小的时候,高并发下会出现连接等待超时,表现为请求延迟飙升。遇到这类问题,适当调大连接池,同时调小config.redis_timeout

5.3 常见问题速查表

现象可能原因解决方案
多节点下限流总量超过配置值仍在使用 local 策略改为 redis 策略
限流计数偶发偏高网关节点时钟不同步配置 NTP 时间同步
Redis 故障后所有请求异常fault_tolerant=false改为 true,同时完善监控告警
高并发下请求延迟飙升redis 连接池不足或超时设置过大调大 redis_pool_size,调小 redis_timeout
Redis key 越堆越多过期时间设置不合理检查并设置 TTL 为窗口周期两倍
响应头中限流数字异常巨大多个限流插件作用在同一服务检查是否同时存在 local 和 redis 策略

5.4 限流维度选择的几个避坑经验

最后分享几个和策略无关、但和限流效果密切相关的经验。

第一,limit_by=ip在网关前面有负载均衡器或 CDN 时可能失效,因为客户端 IP 变成了负载均衡节点的 IP。这种情况下需要配合config.forwarded_header参数,让插件读取 X-Forwarded-For 中的真实客户端 IP。

第二,limit_by=consumer适合在给不同调用方分配差异配额时使用,但它依赖 Consumer 认证完成。如果有些接口是匿名访问的,consumer 维度的限流就覆盖不到,需要同时配置一个按 IP 的兜底限流插件。

第三,多个时间单位同时存在时,比如同时配置minute=100hour=1000,Kong 会分别维护两个独立的计数器。判断是否限流时,任何一个维度超限都会拒绝请求。这意味着如果分钟窗口和小时窗口的边界不在同一时刻,会出现短时间内的“超预期限制”,但其实这只是两个窗口叠加的必然结果,不是策略的 bug。

6. 规模化部署时的进一步思考

6.1 限流与弹性伸缩的协作方式

当网关集群使用 K8s HPA 自动伸缩时,local 策略会让限流阈值随实例数漂移,这几乎不可控。而 redis 策略能保证无论扩到多少节点,限流总量始终稳定。但要注意,流量突增时,Redis 本身也可能成为瓶颈,建议给 Kong 使用独立的 Redis 实例,不要和业务缓存混用。

6.2 多环境隔离与 Redis 实例规划

如果你有 dev、staging、prod 多套环境,最好的做法是每个环境一个 Redis 实例或 DB 编号。之前就遇到过一次比较尴尬的情况:开发环境的网关配置错误地指向了生产环境的 Redis,导致开发流量把生产的限流额度全部耗尽,影响到真实用户。这种故障很隐蔽,排查起来也非常头疼。把环境隔离做好,这个坑就从根本上避开了。

6.3 是否值得结合滑动窗口算法做更精细的限流

Kong 自带的 rate-limiting 插件基于固定窗口,存在窗口边界“突刺”问题:在窗口的最后 1 秒内涌入大量请求,只要总量不超过窗口上限,就会被全部放行。对于要求更平滑限流的业务,可以考虑结合 Redis 通过自定义插件实现滑动窗口或令牌桶算法。不过对于绝大多数业务场景来说,固定窗口的成本和复杂度已经足够,不必为了追求完美而过度设计。

我个人在实际项目中的体会是,限流方案没有绝对的对错,关键是在明确限制维度和部署结构之后,选择对应匹配的策略。local 适合单机边界防护,redis 适合集群全局限流。而真正考验实现质量的,往往不是配置本身,而是对故障场景的预判——比如 Redis 挂掉、时钟偏移、连接池耗尽,这些才是生产环境里最容易翻车的地方。希望这篇文章能帮你把这些坑提前避掉,让限流真正成为保护系统的那道闸门,而不是新的故障源。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询