最近一次接手公司 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 namerate-limiting \ --data config.minute100 \ --data config.policylocal \ --data config.limit_byip \ --data config.fault_toleranttrue几个关键参数说明一下config.minute100表示每分钟允许 100 次请求Kong 还支持 second、hour、day 等时间单位也可以同时配置多个单位比如minute和hour同时设定。config.policylocal是本策略的核心告诉插件计数器存内存。config.limit_byip按客户端 IP 维度计数。config.fault_toleranttrue表示限流计数失败时是否放行请求。对 local 策略来说内存计数几乎不会失败这个参数影响不大但配置上建议统一设为 true保持后续切换策略时行为一致。配置完成后可以通过实际请求验证返回头curl -I http://localhost:8000/example如果插件生效响应头中会看到X-RateLimit-Remaining-Minute和X-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 namerate-limiting \ --data config.minute100 \ --data config.policyredis \ --data config.redis_host192.168.1.10 \ --data config.redis_port6379 \ --data config.redis_database0 \ --data config.redis_passwordyourpassword \ --data config.redis_timeout2000 \ --data config.redis_sslfalse \ --data config.fault_toleranttrue其中重点参数包括config.redis_host和config.redis_portRedis 服务地址和端口生产环境建议使用内网地址。config.redis_database使用的 Redis 库编号默认 0。如果有多个服务共用 Redis建议分配给 Kong 独立的数据库方便管理。config.redis_passwordRedis 认证密码强烈建议必填。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_toleranttrue防止 Redis 故障影响整体可用性同时做好 Redis 本身的监控告警。4. 从 local 切换到 redis 的实操过程4.1 环境准备与 Redis 快速部署如果你还没有可用的 Redis 环境最快的方式是使用 Docker 起一个单机实例docker run -d --name kong-redis \ -p 6379:6379 \ -e REDIS_PASSWORDyourstrongpassword \ redis:7-alpine \ redis-server --requirepass yourstrongpassword生产环境建议不要直接用容器单机模式而是优先考虑由云厂商提供的托管 Redis或者内部自建的主从哨兵集群。这一步不需要太复杂的调优只要保证网络延迟低、可用性高即可。4.2 保留旧插件、创建新插件的平滑切换法local 和 redis 策略同时存在时两个插件会在 Kong 内部按优先级进行判定如果配置的维度和限制值相同会出现双重计数的现象。所以从 local 切换到 redis不能简单地把两个插件都挂在同一个 Service 上。比较稳妥的切换方式是这样第一步先通过 Admin API 查询已有的 local 限流插件 IDcurl http://localhost:8001/services/example-service/plugins | jq .第二步直接更新该插件的 policy 参数把 local 改为 rediscurl -X PATCH \ http://localhost:8001/services/example-service/plugins/{plugin_id} \ --data config.policyredis \ --data config.redis_host127.0.0.1 \ --data config.redis_port6379 \ --data config.redis_database0第三步压测验证。这一步很重要不要直接切全量流量先跑一轮小规模压测确认响应头中的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.4msRedis 故障时行为不受影响受 fault_tolerant 控制依赖资源节点内存外部 Redis 实例这个数据说明两点第一local 在处理性能上确实有压倒性优势第二redis 策略的额外延迟和性能损耗完全可以接受而且是多节点场景下必须支付的一致性成本。4.4 切换之后必须做的检查项切换完成后至少要做以下几项检查避免事后踩坑检查所有网关节点是否都应用到了新的插件配置Kong 的配置同步有轻微延迟批量部署时尤其明显。检查 Redis 中的 key 是否能正常过期长时间运行后如果 key 越堆越多说明 TTL 设置有误。检查X-RateLimit-Remaining-*响应头是否符合预期多节点下剩余配额应该是集群级别的全局递减。5. 常见问题与排查技巧实录5.1 限流数字不准是所有网关集群的经典故障限流不准的最常见原因有三个一是还在使用 local 策略且网关多节点部署二是config.hide_client_headersfalse导致客户端能看到内部计数从而绕过了部分限制逻辑其实这个不会直接造成失效但会带来安全隐患三是不同节点的系统时钟不同步导致时间窗口的边界在每个节点上不一致。时钟同步这个问题很多人忽略。redis 策略虽然共享计数但时间窗口的计算在每台 Kong 节点上独立完成。如果节点 A 的时钟比节点 B 快了 5 秒那么 A 已经进入新的时间窗口而 B 还在旧窗口两个窗口的计数被同时累加总量会短暂超过配置值。解决办法很简单在所有网关节点上配置 NTP 时间同步服务。5.2 Redis 连接失败时请求为什么全被干掉了这是我在生产环境遇到过的另一个严重事故。当时 Redis 实例因为内存碎片化触发 OOMKong 节点连接 Redis 失败而插件配置的config.fault_tolerantfalse结果所有请求直接返回了 503。因为限流服务不可用网关选择拒绝请求整个业务入口瞬间不可用。处理思路是将fault_tolerant设置为 true让限流计数失败时请求默认放行。同时配合 Redis 监控告警一旦限流依赖的 Redis 出现异常立即告警运维介入不要寄希望于网关自身过硬。还有一种比较隐蔽的情况Redis 并没有挂掉但因为连接池被打满导致超时。Kong 有config.redis_pool_size参数控制连接池大小默认值较小的时候高并发下会出现连接等待超时表现为请求延迟飙升。遇到这类问题适当调大连接池同时调小config.redis_timeout。5.3 常见问题速查表现象可能原因解决方案多节点下限流总量超过配置值仍在使用 local 策略改为 redis 策略限流计数偶发偏高网关节点时钟不同步配置 NTP 时间同步Redis 故障后所有请求异常fault_tolerantfalse改为 true同时完善监控告警高并发下请求延迟飙升redis 连接池不足或超时设置过大调大 redis_pool_size调小 redis_timeoutRedis key 越堆越多过期时间设置不合理检查并设置 TTL 为窗口周期两倍响应头中限流数字异常巨大多个限流插件作用在同一服务检查是否同时存在 local 和 redis 策略5.4 限流维度选择的几个避坑经验最后分享几个和策略无关、但和限流效果密切相关的经验。第一limit_byip在网关前面有负载均衡器或 CDN 时可能失效因为客户端 IP 变成了负载均衡节点的 IP。这种情况下需要配合config.forwarded_header参数让插件读取 X-Forwarded-For 中的真实客户端 IP。第二limit_byconsumer适合在给不同调用方分配差异配额时使用但它依赖 Consumer 认证完成。如果有些接口是匿名访问的consumer 维度的限流就覆盖不到需要同时配置一个按 IP 的兜底限流插件。第三多个时间单位同时存在时比如同时配置minute100和hour1000Kong 会分别维护两个独立的计数器。判断是否限流时任何一个维度超限都会拒绝请求。这意味着如果分钟窗口和小时窗口的边界不在同一时刻会出现短时间内的“超预期限制”但其实这只是两个窗口叠加的必然结果不是策略的 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 挂掉、时钟偏移、连接池耗尽这些才是生产环境里最容易翻车的地方。希望这篇文章能帮你把这些坑提前避掉让限流真正成为保护系统的那道闸门而不是新的故障源。