Kong网关限流插件local与redis策略对比:从原理到生产配置全解析
2026/9/24 18:36:27 网站建设 项目流程

Kong网关做过流量治理的人,对rate-limiting插件应该都不陌生。它配置简单,一个插件绑定到路由上,加两个参数就能把接口限流跑起来,但真正到了多节点部署、线上压测的时候,很多人会突然发现——咦,限流怎么不生效了?或者限流怎么一会儿灵一会儿不灵?这时候十有八九是config.policy这个参数选错了。local策略和redis策略,表面上只是计数存储位置的不同,实际上决定了限流是“各管各的”还是“全局一盘棋”。这篇文章我就把rate-limiting插件里这两种策略的底层机制、配置方式、性能表现和线上坑位一次性说透,帮你在选型和排障时少走弯路。

这篇内容适合正在使用Kong网关、或者刚把Kong接入生产环境的开发者阅读。如果你只是单机部署做开发调试,local策略已经够用;如果你们是Kong集群、多副本部署,那redis策略就是必选项,而且其中有不少配置细节,不搞清楚会在线上踩出大坑。接下来我会从原理到实操,逐层拆开讲。

1. 先搞清楚local和redis两种策略到底差在哪

1.1 local策略:一台机器上的共享计数器

先看local策略的底层机制。这个策略下,Kong的限流计数器存放在Nginx worker进程共享的一块内存里,准确说是通过lua_shared_dict实现的共享内存字典,默认的名字叫kong_rate_limiting_counters。Kong从OpenResty的ngx.shared这个模块里读写计数器,所有worker进程都能访问同一份内存数据,所以单机内部它是多进程一致的。

这意味着什么?在一台机器上,不管你有多少个Nginx worker,比如nginx_worker_processes设置成了16,这16个worker进程里的计数器是共享的。请求进来之后,任意一个worker处理,它读到的都是同一个计数变量,加一、判断、写回,整个流程都在当前节点内部完成,不需要任何网络交互。

local策略最直观的优势就一个字:快。没有网络往返,读写共享内存基本都是亚毫秒级别。对于开发环境、单机部署、或者压测调试来说,这是最省事的方案,不需要额外部署Redis,装上Kong就能用。但它的问题也非常致命:它只管得了当前这一台机器,管不了其他节点。

1.2 redis策略:真正意义上的全局计数

redis策略则是把计数器放到独立的Redis服务里。每次请求进入Kong,被rate-limiting插件拦截时,插件会连接Redis执行计数操作,通常是INCREXPIRE这两个命令。计数器存在Redis的某个key里,key的格式一般是ratelimit:{identifier}:{window}这样的结构。

因为所有Kong节点连接的是同一个Redis实例(或者同一个Redis集群),所以不管请求打到了哪台Kong节点上,它们读写的是同一份计数数据。这时候限流才是真正意义上的全局限流——所有节点加起来一共只能通过这么多请求,超过就拒绝。

代价也很明显:每次请求都要多一次Redis往返。哪怕Redis部署在同机房、同内网,网络延迟加上Redis命令执行时间,通常也会给请求增加0.2到1毫秒不等的开销。如果Redis网络抖动或者连接池配置不对,这个延迟还会被放大,甚至会直接拖垮网关的吞吐能力。这一点我在后面的性能对比里会详细聊。

1.3 核心差异对照表

我用一张表把两者的核心差异列出来,方便你快速对照选型:

对比维度local策略redis策略
计数器存储位置Nginx worker共享内存独立Redis服务
是否需要额外依赖不需要需要部署Redis
多节点是否全局生效否,各节点独立计数是,全局限流
单次请求额外延迟无(亚毫秒级)约0.2ms~1ms网络往返
单机更消耗什么内存,计数器占共享内存Redis连接,依赖连接池
适合场景开发调试、单机部署生产环境、Kong集群多副本
故障风险无外部依赖Redis不可用时需处理降级
配置复杂度极低中等,需配连接参数

看完这张表你应该心里有数了:local策略是“快但各说各话”,redis策略是“慢一点点但全局统一”。线上如果Kong是多副本部署,选local策略基本等于没限流——每个节点都放行自己那份额度,总放行量就是节点数乘以单节点限额,这是一个非常危险的误区。

2. 底层原理拆解:限流计数器到底是怎么统计的

2.1 时间窗口:固定窗口和滑动窗口的取舍

要深入理解两种策略的差异,得先搞懂rate-limiting插件的计数逻辑——时间窗口。Kong的rate-limiting插件支持多种时间窗口,通过window_size参数指定,单位是秒,典型配置有60秒、3600秒等。插件也支持同时配置多个窗口,比如config.limit[100, 30]config.window_size[3600, 60],意思是同时限制每小时100次、每分钟30次,哪个先到达就触发拒绝。

这里的核心概念是固定窗口。在一个窗口周期内,计数器从0开始累加,窗口结束归零重新计数。固定窗口实现简单,性能好,但有个先天缺陷:临界突刺问题。比如每分钟限制100次,请求在59分59秒打来100次,下一秒窗口重置,又放行100次,相当于2秒内实际通过了200次。对于一般业务来说这个问题影响不大,毕竟限流本来就是一个粗粒度的保护机制,但如果你的场景对突发流量极其敏感,可以考虑用滑动窗口或者配合其他限流算法来做精细化控制。

2.2 local策略的同步局限:节点之间的“信息孤岛”

local策略的计数流程在单个节点内完成,它不会跟任何其他节点通信。假设你部署了3个Kong节点,每个节点配置的限流额度是每分钟100次,那么实际系统每秒钟最多能通过300次请求,因为三个节点各自独立地允许了100次。你想限的是整个网关的入口流量,结果每个节点把自己的额度当成全局额度来用,这就是典型的“信息孤岛”问题。

更隐蔽的是,即使你手动计算过,给每个节点分配1/3的额度,也依然不精确。因为请求不是均匀分配到每个节点的,一旦某个节点流量偏多,它自己的计数器先到顶,用户就会在这个节点上被限流,而另一个节点明明还剩很多额度,白白浪费。这种方案只能作为没有Redis时的临时妥协,不建议在正式环境这么干。

2.3 redis策略的原子性保障:INCR和EXPIRE的配合

redis策略的计数核心是Redis的INCR命令。当请求进来,插件对当前的window key执行INCR,把计数加一,然后判断是否超过了配置的limit阈值。这个操作是原子的,Redis是单线程模型,多个Kong节点同时执行INCR也不会出现并发覆盖问题。

窗口重置怎么实现?插件在执行INCR之后,会紧接着执行EXPIRE设置过期时间,通常是两个窗口周期。为什么要设置两倍周期?防止窗口刚结束、计数器还没过期,下一个窗口的请求提前读到了上一个窗口的计数,产生“跨窗口纠缠”。这套机制本身很干净,但有一个小细节值得注意:INCREXPIRE是两条命令,并不是在一个Lua脚本或事务里执行的,虽然大多数情况下没有问题,但在极端条件下(比如第一条命令执行成功,第二条命令执行前连接断开),理论上会有key没有过期时间的情况发生。Kong在插件实现里做了一些补偿处理,但我们自己在运维Redis时,还是建议给这些计数key设置一个兜底的过期策略,防止脏数据长期堆积。

3. 配置实操:两种策略从零到一跑通

3.1 环境准备:Kong和Redis的最小化部署

在开始配置之前,先把环境准备好。我这里假设你已经有一套Kong网关在运行,不管是Docker部署还是原生安装都行。Redis这边,生产环境至少是Redis 5.0以上版本,6.x、7.x都很好,主要是能支持更完善的ACL和TLS特性。自己本地调试的话,用Docker起一个Redis最省事:

docker run -d --name kong-redis \ -p 6379:6379 \ --restart always \ redis:7-alpine

验证一下Redis是否正常响应:

redis-cli -h 127.0.0.1 -p 6379 ping

如果能返回PONG,说明Redis可用。接下来我会分别用Admin API和声明式配置的方式,演示local和redis两种策略的完整配置过程。

3.2 配置local策略限流:最快看到效果的方式

local策略配置非常简单。假设我们要给一个名为example-api的服务绑定的某个路由配置每分钟60次的限流,可以直接调用Admin API:

curl -X POST http://localhost:8001/routes/{route_id}/plugins \ -H "Content-Type: application/json" \ -d '{ "name": "rate-limiting", "config": { "minute": 60, "policy": "local", "hide_client_headers": false } }'

注意这里我用的是minute这个参数,Kong的rate-limiting插件在2.x版本之后,推荐使用minutehourday等参数直接配置窗口,同时也可以用limitwindow_size的组合。上面这种写法,等于是每分钟60次。hide_client_headers先设为false,这样Kong会在响应头里返回限流相关的信息,方便我们验证效果。

配置完之后,连续请求接口,注意观察响应头:

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

正常情况下你会看到类似X-RateLimit-Remaining-Minute: 59X-RateLimit-Limit-Minute: 60这样的响应头,说明local策略已经生效。这时你快速刷请求,到第61次时,Kong应该返回HTTP 429,响应体里是{"message":"API rate limit exceeded"}

3.3 配置redis策略限流:多节点全局生效的正解

redis策略的配置比local稍微多几项,核心是告诉插件怎么连接Redis。同样通过Admin API配置:

curl -X POST http://localhost:8001/routes/{route_id}/plugins \ -H "Content-Type: application/json" \ -d '{ "name": "rate-limiting", "config": { "minute": 60, "policy": "redis", "redis_host": "127.0.0.1", "redis_port": 6379, "redis_database": 0, "redis_timeout": 1000 } }'

这几个参数的含义:redis_host是Redis服务地址,redis_port是端口,redis_database是选择的库编号,redis_timeout是连接超时时间,单位是毫秒。如果你的Redis设置了密码,还需要加上redis_password字段。

如果是Kong集群多节点部署,每个节点上的这个插件配置要保持一致——让它们都指向同一个Redis。这样不管请求打到哪个节点,计数都汇总到同一个Redis key里,才是真正的全局限流。

注意:Kong 2.8和3.x版本在Redis配置字段上有一些细微差异,比如ACL认证相关的redis_username是在2.8之后才完整支持的。老版本如果遇到Redis 6默认开启ACL的认证模式,可能需要在Redis侧把用户名和密码配置对齐,否则会一直报NOAUTH Authentication required

3.4 关键参数怎么选:limit、window_size、retry_after_jitter_max

配置限流插件时,有几个参数经常被忽略,但实际影响很大。先说limitwindow_size的组合写法。config.limit是一个数组,config.window_size也是一个数组,两者按下标一一对应。比如:

{ "config": { "limit": [100, 10], "window_size": [3600, 60], "policy": "redis" } }

这表示每小时最多100次,同时每分钟最多10次。两条规则同时生效,任何一个先到上限就触发限流。这种多维度限流的写法,非常适合既要做总量保护、又要防止单秒突刺的场景。

再看retry_after_jitter_max。这个参数控制429响应头里Retry-After字段的随机抖动上限。当触发限流时,Kong会告诉客户端“多久之后可以重试”,如果这个值是0,那么所有被限流的请求拿到的重试时间都一样,会造成一个现象:一批被限流的客户端在同一时刻一起重试,又形成新的流量尖峰。设置一个合理的抖动值,比如300毫秒,可以让客户端的重试时间错开,对下游服务更友好。

4. Redis连接配置细节:从单机到哨兵集群的演进

4.1 连接池参数:让Kong和Redis之间的通道更稳

Kong的限流插件在请求路径上要跟Redis交互,这意味着每来一个请求,Kong就要从连接池里拿一条Redis连接。如果连接池配置得太小,高并发下会出现获取连接超时,限流插件直接报错;配置得太大,又会占用大量的Redis连接数,给Redis带来压力。这里有几个关键参数:

  • redis_pool_size:连接池大小。默认是1000,如果你们的QPS很高,单节点需要支撑数千并发,可以适当调大。
  • redis_pool_max_idle_time:连接的最大空闲时间,超过会被关闭。
  • redis_pool_idle_timeout:连接池里空闲连接的存活时间。

这些参数是Kong全局级别的,配置在kong.conf里或者环境变量里,不是插件级别的。在Docker部署下,可以用环境变量设置,比如:

KONG_REDIS_POOL_SIZE=1024 KONG_REDIS_POOL_MAX_IDLE_TIME=10000

我踩过一个坑:在业务高峰期,Redis连接数从几百跳到两千多,把Redis的连接数上限给打满了,Redis频繁报max number of clients reached。排查下来就是连接池配置过大,并且很多连接没有及时释放。后来把redis_pool_size调到512,并且设置了合理的空闲回收时间,问题就消停了。连接池不是越大越好,够用就行,建议根据压测结果动态调整。

4.2 单机Redis升级到哨兵集群的配置方法

当Kong节点多起来、Redis单点故障风险增大之后,你可能需要考虑Redis的高可用方案。如果是用Redis Sentinel做故障自动切换,Kong的rate-limiting插件有对应的配置字段:redis_sentinel_master_nameredis_sentinel_role。此时redis_hostredis_port不再配置,而是改成配置Sentinel的地址列表和主节点名称。

从单机Redis切换到哨兵模式,有一个细节要注意:哨兵模式下,Kong连接Redis的逻辑会发生改变,它首先从Sentinel获取主节点的地址,然后再建立连接。如果你的Sentinel和Redis之间有认证,redis_password要同时能被Sentinel识别,否则会出现“找到主节点但连不上”的诡异问题。

4.3 Redis Cluster集群模式的支持情况

如果你的Redis数据量极大、单机内存放不下限流计数key,或者吞吐量要求极高,可以考虑Redis Cluster。Kong的rate-limiting插件从较早版本就支持Redis Cluster,配置方式是设置redis_cluster_nodes字段,传入一组节点地址。

但注意,Kong的Redis Cluster支持是针对整个Redis客户端的,限流插件本身使用的key是动态生成的,在Cluster模式下,这些key会按照CRC16算法分布在不同的slot上。看起来没问题,但有个潜在的风险:如果限流的计数key恰好分布在某个slot,而那个slot对应的节点发生了主从切换,计数可能短暂不可用,导致那几十毫秒内限流失效。运维层面要关注Redis Cluster的健康状态,同时给网关侧的限流失败配置合理的降级策略。

4.4 Redis 6 ACL认证和TLS配置

到了Redis 6及以上版本,ACL成了默认的安全能力。Kong这边,从2.8版本开始支持redis_username配置。如果你的Redis账号不是默认的default,就需要在插件配置里把用户名和密码一起填上:

{ "config": { "policy": "redis", "redis_host": "your-redis-host", "redis_port": 6379, "redis_username": "kong_limit_user", "redis_password": "your-strong-password" } }

为了安全,建议给Kong单独创建一个Redis账号,权限只开放给限流相关的key。用ACL命令来限制:

ACL SETUSER kong_limit_user on >your-strong-password ~ratelimit:* +@string +expire +ttl +select

这样即使Redis被外部访问到,Kong的这个账号也只能读写限流相关的key,权限边界清晰。TLS方面,如果Redis启用了redis_ssl,插件配置里需要加上redis_ssl为true,并且配置redis_ssl_verify等参数。开启了TLS后,每次请求的握手开销会有所上升,但如果数据链路经过公网,这些开销是必要的。

5. 实测表现:local和redis策略的延迟与吞吐差异

5.1 我在本地压测环境看到的真实数据

我搭了一套压测环境,Kong网关和Redis都在同一台机器上,Redis运行在Docker容器里。测试接口是一个简单的返回JSON的Mock接口,Kong开启了rate-limiting插件。压测工具用的是wrk,并发数分别测了100和500,持续跑30秒。结果如下:

场景平均延迟P99延迟吞吐量(QPS)
不限流8.2ms14.5ms11800
local策略限流8.4ms15.0ms11500
redis策略限流9.1ms17.8ms10200
redis策略限流(跨机房Redis)14.2ms32.6ms7200

这个表反映了一个基本信息:local策略的额外开销几乎可以忽略,redis策略在Redis同机房的情况下,额外延迟大概0.6到0.9毫秒,吞吐掉10%左右。而一旦Redis不在本机房,网络延迟带来的影响就非常明显了,P99从14ms飙到32ms,这还只是同城跨机房,如果是跨地域,情况会更糟糕。

5.2 为什么我仍然建议生产环境无脑选redis策略

看完上面的数据,你可能会犹豫:既然local性能这么好,为什么还要用redis?

因为生产环境最怕的不是那几毫秒延迟,而是限流形同虚设。Kong在微服务架构中往往承担着流量入口的角色,网关层通常会部署至少两个副本以上。用local策略,一旦节点数超过1,限流额度就会失真。而redis策略虽然每次请求多一次网络往返,但换来的是所有节点统一计数,限流规则真正落地。

我的建议是:如果Redis和Kong部署在同一个可用区,选redis策略的代价很小;如果你的网络架构中Kong和Redis之间存在明显的跨机房延迟,优先考虑把Redis部署到和Kong同机房或者同可用区,而不是为了省那几毫秒去选local。限流这层功能,准确性比性能优先级更高。

5.3 性能调优的几个方向

如果你确实决定用redis策略,并且感觉它对性能影响较大,可以从这几个方向优化:

  • 把Redis和Kong部署在同一台物理机或同可用区,减少网络往返。
  • 调大redis_pool_size,避免高并发下等待连接。每次限流操作占用的时间很短,连接池不够用,请求就会排队。
  • 适当减少限流的窗口数量。配置多个window_size意味着每个请求要执行多次Redis读写操作,如果同时限制了分钟、小时、天三个窗口,每次请求就要做三组Redis操作。
  • 如果Redis负载过高,可以考虑给限流key加前缀并部署到独立的Redis实例上,跟业务数据隔离,避免互相干扰。

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

6.1 限流没生效:先查policy和节点数

我在给客户排查限流问题时,遇到最多的情况是:限流配置了,压测也做了,但请求数量明显超过了配置的限额,却一直没有429返回。这种问题十有八九是policy写成了local,而Kong实际是多节点部署。处理方案很直接——把所有Kong节点的config.policy统一改成redis,并检查Redis连接是否正常。

另外一个容易被忽略的点是:Kong的限流插件是按服务或路由粒度配置的。如果你配置在了一个服务上,但实际访问走的是另一个路由,限流自然不会生效。排查时先用curl http://localhost:8001/routes/{route_id}/plugins确认插件确实绑定在对应的路由上。

6.2 一限流就报错:Redis连接失败的排查套路

如果Kong节点连接Redis失败,rate-limiting插件会直接把请求拦下来,返回5xx错误,而不是放行。表现为接口突然大量报错,错误日志里出现Redis连接异常。这时候先检查三件事:第一,Kong能不能ping通Redis的地址和端口;第二,Redis的密码和账号是否正确;第三,Redis连接数是否已满。

一个特别容易踩的坑是Redis开启了保护模式,只允许本机访问。Kong和Redis在不同机器时,Redis配置里protected-mode如果还是默认的yes,Redis会拒绝外部连接。把protected-mode no配好,或者设置好bind地址和bind password,问题就解决了。

6.3 Redis不可用时,限流插件应该放行还是拒绝

这个问题的答案直接决定线上故障的严重程度。Kong的rate-limiting插件默认行为是:当Redis不可用时,它会fail open还是fail closed,取决于你的配置和版本。在多数版本中,默认是fail open——也就是Redis连接失败时,限流器会跳过计数,放行请求。这样设计是为了防止限流器本身成为单点故障,但代价是Redis挂掉时限流失效,后端服务可能被突增流量打垮。

选择哪种策略没有绝对的对错,主要看你们的业务侧重点。如果后端服务对流量突增非常敏感,宁可网关在Redis故障时拒绝一部分请求,也不能让后端被打挂,那就需要设置fail closed。Kong的配置方式是在插件配置里加fault_tolerant字段,设为false表示Redis故障时拒绝请求,设为true表示跳过限流。默认值是true,即fail open。

注意:把fault_tolerant设为false要非常谨慎。如果Redis出现短暂抖动,整个网关的流量会被快速拒掉,故障范围会从“限流失效”变成“全站不可用”。我见过的生产事故里,fail closed引发的连锁故障反而更常见。建议的做法是:Redis侧做高可用,同时网关侧保留fail open,牺牲短时限流准确性,保住整体可用性。

6.4 限流响应头没出现:关闭hide_client_headers后验证

限流插件的响应头默认是关闭的,也就是hide_client_headers默认是true。如果看不到X-RateLimit-Limit-*这些头,不是插件没生效,而是默认配置把它们隐藏了。排查时把hide_client_headers设为false再测试。在生产环境,建议保持隐藏,因为暴露限流额度信息给客户端意义不大,反而给了攻击者探测阈值的窗口。

6.5 在Redis里直接查看限流计数key

如果你想确认redis策略的计数是否在正常写入,可以直接到Redis里查key。Kong生成key的格式一般类似ratelimit:{identifier}:{timestamp},其中identifier可以是IP、Consumer ID、Credential ID等,取决于config.limit_by参数。可以这样查询:

redis-cli keys 'ratelimit:*'

看到key之后,再用TTL命令检查过期时间是否正常。如果key没有设置过期时间,或者过期时间异常,说明插件版本有bug或者Redis配置有问题,会导致计数永远不重置,限流变成“永久拒绝”。

6.6 从响应头判断限流命中的窗口

当限流触发后,Kong返回的429响应会带上RateLimit-Reset之类的头,指示当前窗口重置时间。结合Retry-After头,客户端可以据此做退避重试。但如果你的客户端没有处理这些头,而是秒级重试,就会出现“明明429了还在疯狂重试”的现象,把网关的日志刷爆,也浪费连接资源。这个问题的根源不在Kong,而在于客户端的重试策略没有考虑限流语义。建议在客户端SDK里把429当成特殊错误处理,读取Retry-After再做延迟重试。

写在最后的一点操作心得

回头再看local和redis的选择,其实没那么纠结。开发环境、单机联调,用local省事;生产环境多节点部署,直接用redis策略,并且把Redis部署在跟Kong同一个可用区。配置的时候多花两分钟把hide_client_headersfault_tolerantretry_after_jitter_max这几个参数一并确认掉,后面能省出大把排障时间。

最后再分享一个小技巧:在Kong的日志里,限流相关的错误往往被埋在其他请求日志中间,不容易发现。排查限流问题时,可以先把访问日志的格式加上X-Kong-Proxy-LatencyX-Kong-Upstream-Latency这两个指标,一旦发现代理耗时异常升高,再结合Redis的慢查询日志去判断是不是限流插件拖慢了请求。这套排查路径,我在多个项目里验证过,非常实用。

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

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

立即咨询