十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Kong网关限流插件local与redis策略对比:从原理到生产配置全解析

Kong网关限流插件local与redis策略对比:从原理到生产配置全解析 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执行计数操作通常是INCR和EXPIRE这两个命令。计数器存在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设置过期时间通常是两个窗口周期。为什么要设置两倍周期防止窗口刚结束、计数器还没过期下一个窗口的请求提前读到了上一个窗口的计数产生“跨窗口纠缠”。这套机制本身很干净但有一个小细节值得注意INCR和EXPIRE是两条命令并不是在一个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 APIcurl -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版本之后推荐使用minute、hour、day等参数直接配置窗口同时也可以用limit和window_size的组合。上面这种写法等于是每分钟60次。hide_client_headers先设为false这样Kong会在响应头里返回限流相关的信息方便我们验证效果。配置完之后连续请求接口注意观察响应头curl -I http://localhost:8000/example-api/test正常情况下你会看到类似X-RateLimit-Remaining-Minute: 59、X-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配置限流插件时有几个参数经常被忽略但实际影响很大。先说limit和window_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_SIZE1024 KONG_REDIS_POOL_MAX_IDLE_TIME10000我踩过一个坑在业务高峰期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_name和redis_sentinel_role。此时redis_host和redis_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.5ms11800local策略限流8.4ms15.0ms11500redis策略限流9.1ms17.8ms10200redis策略限流跨机房Redis14.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如果还是默认的yesRedis会拒绝外部连接。把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_headers、fault_tolerant、retry_after_jitter_max这几个参数一并确认掉后面能省出大把排障时间。最后再分享一个小技巧在Kong的日志里限流相关的错误往往被埋在其他请求日志中间不容易发现。排查限流问题时可以先把访问日志的格式加上X-Kong-Proxy-Latency和X-Kong-Upstream-Latency这两个指标一旦发现代理耗时异常升高再结合Redis的慢查询日志去判断是不是限流插件拖慢了请求。这套排查路径我在多个项目里验证过非常实用。
返回列表