网关限流实战:从算法选型到分布式落地的核心要点

发布时间:2026/10/22 16:36:33
网关限流实战:从算法选型到分布式落地的核心要点 1. 网关限流的核心思路与方案选型1.1 为什么限流要放在网关层做但凡做过线上服务的人迟早都会遇到一个绕不开的问题某个接口突然被大量请求打满数据库连接池告急整个服务雪崩连带其他正常业务一起挂掉。这种场景在电商大促、秒杀活动、或者某个接口被外部恶意刷量的时候特别常见。解决这类问题最直接的手段就是限流。那限流放在哪里做最合适我的经验是能放在网关层就不要下沉到业务服务里。原因很简单网关是所有外部流量的统一入口在这里做限流等于在小区大门口设了保安而不是每栋楼各自雇人看守。好处有三个第一拦截成本低无效流量根本进不了内网省下大量线程和连接资源第二规则统一管理不用每个微服务都写一套限流逻辑改规则也不用挨个发版第三对业务代码零侵入业务开发只管写业务限流这种横切关注点交给基础设施层。当然网关层限流也不是万能的。它更适合做粗粒度的全局防护比如按IP、按路由、按用户维度限制QPS。至于更细粒度的业务限流比如“同一个用户对同一个商品每秒最多下单一次”那还是得在业务层配合做。两者是互补关系不是替代关系。1.2 常见限流算法怎么选限流算法这块网上讲得很多但真正落地的时候选哪个是有讲究的。我把几个主流算法拉出来对比一下方便你根据场景做决策。算法核心思想优点缺点适用场景计数器固定时间窗口内计数超阈值拒绝实现最简单存在临界突刺问题对精度要求不高的粗限流滑动窗口把时间窗口切成小格滚动统计精度较高平滑突刺实现稍复杂占内存通用限流场景漏桶请求先入桶匀速流出输出速率绝对平滑无法应对突发流量需要严格匀速的下游保护令牌桶匀速生成令牌有令牌才放行允许一定突发灵活参数调优需要经验大多数网关限流场景实际在网关里令牌桶是使用最广的。原因在于它既能限制平均速率又能容忍一定程度的突发流量这跟真实业务的流量特征最匹配。比如一个接口平时QPS是100偶尔来个瞬时200的峰值如果用漏桶多出来的100个请求全被拒用令牌桶只要桶里攒了足够的令牌这波突发就能扛过去。提示令牌桶的两个核心参数是“令牌生成速率”和“桶容量”。速率决定长期平均QPS上限桶容量决定能容忍多大的瞬时突发。桶容量设得越大突发容忍度越高但对下游的冲击风险也越大需要结合下游实际承载能力来定。1.3 单机限流与分布式限流的取舍这是很多人容易踩坑的地方。网关通常是多实例部署的如果你在每个网关实例上做单机限流比如每个实例限制100 QPS部署了5个实例那实际总限流就是500 QPS。问题来了负载均衡不一定均匀可能某个实例分到150另一个只分到50结果就是有的实例已经在拒绝请求了有的还很空闲整体限流效果大打折扣。所以生产环境一般要做分布式限流把计数或令牌集中存储所有网关实例共享。常见的存储选型是Redis因为它的原子操作和高性能天然适合做这个。用Redis做分布式限流核心就是利用Lua脚本保证“读取-判断-扣减”这一系列操作的原子性避免并发情况下的超发问题。不过分布式限流也有代价每次限流判断都要走一次网络IO到Redis会引入额外延迟。如果对延迟极其敏感可以考虑本地限流加分布式限流的两级配合——本地先挡掉大部分分布式做兜底精确控制。这个后面实操部分我会详细讲。2. 网关限流的核心细节与实操要点2.1 限流维度的设计限流不是简单设个数字就完事限流维度的设计直接决定了限流效果好不好用。常见的维度有这么几种按IP限流最粗粒度适合防刷。但要注意NAT场景很多用户共用一个出口IP按IP限流容易误伤。按用户ID限流需要先做身份识别适合已登录场景精度高。按接口/路由限流针对特定接口做保护比如下单接口单独限流。按应用/租户限流多租户系统里给每个租户分配配额。按全局限流整个网关的总入口限流作为最后一道防线。实际项目里通常是多维度组合使用。比如全局设一个总上限防止整体被打垮然后对核心接口单独设更严格的限制再对异常IP做黑名单级别的封禁。这种分层防御的思路比单一维度可靠得多。2.2 限流阈值的确定方法阈值定多少这是最考验经验的地方。定高了没效果定低了正常用户被误伤。我的做法是分三步走第一步压测摸底。在测试环境对目标接口做压力测试找到服务开始出现明显延迟上升或错误率上升的拐点这个拐点对应的QPS就是理论上限。第二步打折留余量。理论上限不能直接用要打个折。一般我会取理论值的60%到70%作为限流阈值。为什么因为压测环境是理想状态线上还有各种其他流量、慢查询、GC停顿等干扰因素留出余量才能保证稳定性。第三步灰度观察再调整。先按估算值上线开启监控观察一段时间看拒绝率、响应时间、错误率这些指标。如果拒绝率长期为0说明阈值可能偏松如果正常用户频繁被拒说明偏紧。根据实际数据微调一般迭代两三轮就能找到合适的值。注意阈值不是一劳永逸的。业务增长、代码优化、硬件扩容都会改变服务承载能力建议每个季度或者每次大版本上线后重新评估一次限流阈值。2.3 被限流后的响应设计请求被限流之后返回什么给客户端这个细节很多人不重视但其实很影响用户体验和排查效率。首先HTTP状态码要用对。限流拒绝应该返回429Too Many Requests而不是笼统的500或者403。429是专门表示请求过多的语义客户端和监控系统能据此做针对性处理。其次响应体里要带上有用信息。至少包含错误码、错误描述、建议的重试等待时间。比如返回一个Retry-After头告诉客户端多少秒后可以重试。这样客户端就能做退避重试而不是傻乎乎地一直猛冲。最后限流日志要打全。每次触发限流都要记录是被哪个规则、哪个维度、哪个阈值触发的。这些日志在排查“为什么用户被限流了”这类问题时是救命稻草。我见过有的团队限流日志只打一句“rate limited”出了问题完全没法定位只能靠猜。2.4 限流与熔断、降级的配合限流不是孤立存在的它要和熔断、降级一起构成完整的流量治理体系。三者的分工是这样的限流控制入口流量不让过多请求进来。熔断当依赖的下游服务出现故障时快速切断调用避免被拖死。降级在资源不足时主动放弃非核心功能保核心功能。举个实际场景大促期间网关限流把总入口控制在系统能承受的范围内如果某个下游服务响应变慢熔断器触发快速失败不再等待同时降级开关打开把商品推荐这类非核心功能关掉把资源让给下单支付。这三者联动才能扛住极端流量。在网关配置上限流规则和熔断规则通常是分开配置的但要注意它们的执行顺序。一般建议限流在前熔断在后。因为限流是主动控制入口成本低熔断是被动响应故障成本高。先限流把流量控制在合理范围能减少熔断被触发的概率。3. 网关限流的完整实操过程3.1 环境与组件准备这里我以目前主流的网关组件为例来演示具体名字就不点了思路是通用的。你需要准备的东西包括一个网关服务支持自定义过滤器或插件一个Redis实例用于分布式限流计数一套配置中心用于动态下发限流规则可选但强烈建议监控系统用于观察限流指标Redis这块要特别注意不要用和业务共用的Redis实例。限流是高频操作QPS可能很高跟业务混用容易互相影响。建议单独部署一个Redis或者至少用独立的DB和连接池。如果限流QPS特别高可以考虑Redis集群但要注意Lua脚本在集群模式下对key的要求——所有key必须在同一个slot否则会报错。3.2 基于令牌桶的分布式限流实现下面我用Lua脚本的方式展示一个分布式令牌桶的核心实现。这个脚本在Redis里执行保证原子性。-- 令牌桶限流脚本 -- KEYS[1]: 限流key -- ARGV[1]: 令牌生成速率每秒 -- ARGV[2]: 桶容量 -- ARGV[3]: 当前时间戳秒 -- ARGV[4]: 本次请求需要的令牌数 local key KEYS[1] local rate tonumber(ARGV[1]) local capacity tonumber(ARGV[2]) local now tonumber(ARGV[3]) local requested tonumber(ARGV[4]) -- 获取桶的当前状态 local bucket redis.call(hmget, key, tokens, last_refill) local tokens tonumber(bucket[1]) local last_refill tonumber(bucket[2]) -- 初始化桶 if tokens nil then tokens capacity last_refill now end -- 计算这段时间内应该补充的令牌数 local delta math.max(0, now - last_refill) local refill delta * rate tokens math.min(capacity, tokens refill) -- 判断令牌是否足够 local allowed 0 if tokens requested then tokens tokens - requested allowed 1 end -- 写回桶状态 redis.call(hmset, key, tokens, tokens, last_refill, now) -- 设置过期时间避免冷key长期占用内存 redis.call(expire, key, math.ceil(capacity / rate) * 2) return allowed这个脚本的逻辑很清晰先算出从上次补充到现在应该攒了多少令牌加上去但不能超过桶容量然后判断够不够扣够就扣并放行不够就拒绝。最后设置一个合理的过期时间避免那些不再使用的key一直占着内存。调用的时候网关过滤器里大致是这样的流程// 伪代码示意 String limitKey rate_limit: routeId : clientIp; long now System.currentTimeMillis() / 1000; ListString keys Collections.singletonList(limitKey); ListString args Arrays.asList( String.valueOf(rate), // 每秒生成令牌数 String.valueOf(capacity), // 桶容量 String.valueOf(now), 1 // 每次请求消耗1个令牌 ); Long allowed redisTemplate.execute(redisScript, keys, args.toArray()); if (allowed null || allowed 0) { // 触发限流返回429 response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); response.getHeaders().add(Retry-After, 1); return response.setComplete(); } // 放行继续后续过滤链 return chain.filter(exchange);3.3 参数计算与调优实例光有代码不够参数怎么定才是关键。我拿一个实际调优的例子来说明。假设某个查询接口压测下来单实例能稳定支撑800 QPS网关部署了4个实例那么理论上总承载是3200 QPS。按照前面说的打七折原则限流阈值定在2200 QPS左右比较稳妥。现在要把它换算成令牌桶参数。因为是分布式限流所有实例共享一个桶所以令牌生成速率 2200 / 1 2200 个/秒桶容量这个决定了突发容忍度。我一般设为速率的1到2倍。这里取1.5倍即3300个。这样配置的效果是长期平均QPS被限制在2200但如果某一秒突然来了3000个请求只要桶里攒够了令牌这波突发能扛过去。当然桶容量不能无限大否则就失去了限流的意义下游还是会被打垮。调优的时候要盯几个指标拒绝率被限流的请求占比、P99响应时间、下游错误率。如果拒绝率持续高于5%可能是阈值偏紧如果P99明显上升但拒绝率为0说明阈值偏松下游已经在承压了。3.4 动态规则下发限流规则写死在配置文件里是很痛苦的每次调整都要重启网关。生产环境一定要做动态规则下发。做法是把限流规则存在配置中心或者数据库里网关监听规则变化实时更新本地的限流配置。规则的数据结构大概长这样{ ruleId: rule_001, resource: /api/order/create, dimension: ip, rate: 100, capacity: 150, enabled: true, priority: 1 }网关启动时拉取全量规则之后通过长连接或者定时轮询感知变化。规则更新后新的请求立即按新规则执行不需要重启。这个能力在应对突发流量时特别重要——比如发现某个接口被刷可以立刻在配置中心把它的限流阈值调低几秒钟就生效。提示动态规则下发要考虑规则冲突的情况。比如同时存在全局规则和接口级规则要定义好优先级。一般原则是粒度越细的规则优先级越高接口级高于应用级应用级高于全局。4. 常见问题与排查技巧实录4.1 限流不生效的排查思路限流配了但没起作用这是最常见的问题。排查的时候按这个顺序走第一确认规则是否加载成功。看网关日志里有没有规则加载的记录或者通过管理接口查询当前生效的规则列表。很多时候是规则格式写错了解析失败被静默忽略了。第二确认限流key的生成逻辑。如果key里包含了某些每次都变的因素比如时间戳精确到毫秒那每次请求的key都不一样限流自然不生效。key的粒度要跟限流维度匹配。第三确认Redis连接正常。如果Redis连不上有些实现会默认放行fail-open这时候限流就形同虚设。要检查Redis的健康状态和连接池配置。第四确认过滤器执行顺序。如果限流过滤器排在路由转发之后那请求已经出去了才限流当然没用。限流过滤器必须在路由之前执行。4.2 限流误伤的定位与解决正常用户被限流这个投诉起来很要命。定位这类问题关键是把限流日志和访问日志关联起来。每次限流触发时记录完整的上下文限流key、当前令牌数、请求来源IP、用户ID、请求路径。然后拿被误伤用户的特征去日志里搜看是被哪条规则拦的。常见的误伤原因有几种NAT出口IP共享多个用户共用一个IP按IP限流时被一起限了。解决办法是结合用户ID做更细粒度的限流或者对已知的NAT IP段放宽阈值。爬虫或监控探针某些健康检查或监控系统的高频请求触发了限流。解决办法是把这些已知来源加入白名单。阈值确实偏低业务增长了但阈值没更新。解决办法是重新评估阈值。误伤现象可能原因排查方法解决措施某地区用户集中被限NAT IP共享看被限IP的请求来源分布结合用户ID限流或放宽IP阈值固定周期被限监控探针高频请求看请求时间规律和User-Agent加入白名单高峰期大量被限阈值偏低对比历史QPS和阈值重新压测评估阈值随机用户被限key生成有误检查key生成逻辑修正key粒度4.3 Redis热点key与性能问题分布式限流把压力都集中到了Redis上如果某个key的访问特别频繁就会形成热点key可能导致Redis单节点CPU打满。这个问题在限流场景下尤其突出因为限流key往往是按接口或全局维度生成的访问量巨大。解决热点key有几个思路。一是本地缓存加批量上报网关本地先做一层计数攒一批再同步到Redis减少Redis访问次数。二是key分片把一个热点key拆成多个子key请求分散到不同子key上最后汇总判断。三是本地限流兜底每个网关实例先做单机限流只有单机没超限的才去请求Redis做分布式判断这样能挡掉大部分流量。我个人的经验是本地限流加分布式限流的两级方案性价比最高。本地限流用令牌桶阈值设为分布式阈值的1.2倍左右先把明显超量的请求挡在本地只有本地放行的才去走Redis。这样Redis的压力能降一个数量级而且本地限流没有网络开销响应更快。4.4 限流与业务逻辑的边界问题有个容易被忽略的问题限流应该在业务逻辑执行之前还是之后答案显然是之前。但有些实现里限流过滤器虽然排在前面却因为异步执行的原因实际判断时业务已经开始处理了。这个要特别注意过滤器的同步/异步模型。另外限流不应该影响幂等性。比如一个支付请求被限流了客户端重试这时候要保证不会重复扣款。限流拒绝的请求应该是完全没有副作用的客户端重试是安全的。如果限流判断本身有副作用比如扣了令牌但业务没执行那就要考虑令牌的归还机制。还有一个实践中的细节限流和鉴权的顺序。一般建议鉴权在前限流在后。因为限流往往需要用户身份信息来做维度区分没鉴权就不知道是谁只能按IP限精度差。而且先鉴权可以把非法请求挡掉减少限流的无效计算。4.5 压测验证限流效果限流配好了怎么验证它真的有效必须做压测。压测的时候要注意几点第一压测流量要能触发限流。如果压测QPS还没到阈值那测了个寂寞。要逐步加压直到观察到限流触发。第二观察限流触发后的系统表现。重点看被限流的请求是否快速返回不能拖慢整体响应、下游服务的负载是否下降、系统整体是否稳定。第三验证限流恢复。压测停止后观察限流是否自动恢复令牌桶是否能正常补充令牌。有些实现里桶状态没设过期时间压测完桶一直是空的正常请求也被拒这就是bug。第四验证多实例一致性。如果是分布式限流要确认所有实例共享同一个限流状态。可以同时压多个实例看总QPS是否被控制在阈值内。注意压测环境要和线上环境尽量一致包括网关实例数、Redis配置、下游服务能力。否则压测出来的阈值参考价值有限。5. 限流体系的进阶优化方向5.1 自适应限流固定阈值的限流有个天然缺陷阈值是静态的但系统承载能力是动态的。比如系统负载高的时候实际能处理的QPS会下降但限流阈值还是按空闲时的标准设的结果就是系统已经快扛不住了限流还没触发。自适应限流的思路是让阈值跟着系统状态走。具体做法是采集系统的关键指标CPU使用率、负载、响应时间、队列深度等通过算法动态计算当前应该限制的QPS。比如用滑动窗口统计最近一段时间的平均响应时间当响应时间上升时自动调低限流阈值。这种方案实现复杂度高但效果确实好。适合那些流量波动大、对稳定性要求极高的核心系统。不过要注意自适应算法本身要足够稳定不能因为指标抖动导致阈值频繁大幅波动那样反而会引起系统震荡。5.2 分级限流与优先级所有请求一视同仁地限流有时候并不合理。比如同样是访问商品接口普通用户的浏览请求和VIP用户的下单请求重要性完全不同。分级限流就是给不同优先级的请求分配不同的配额。实现上可以给每个请求打上优先级标签限流时按优先级从高到低分配令牌。高优先级请求先用令牌剩下的才给低优先级。这样在流量高峰时能保证核心业务不受影响非核心业务被限流。优先级的设计要跟业务方一起定不能拍脑袋。一般原则是写请求高于读请求付费相关高于免费相关老用户高于新用户或者反过来看业务策略。定好之后要固化到规则里避免随意变更。5.3 限流数据的可视化与告警限流跑起来之后如果没有可视化等于闭着眼睛开车。至少要监控这几个指标限流触发次数按规则、按维度统计看哪些规则在起作用。拒绝率被限流请求占总请求的比例反映限流松紧程度。限流key的分布看是哪些IP、哪些用户在频繁触发限流。Redis性能指标限流依赖RedisRedis的延迟和QPS要重点监控。告警方面我建议设这么几条拒绝率突然飙升可能是被攻击或阈值配错、Redis延迟超过阈值限流可能失效、限流规则加载失败规则没生效。这些告警能帮你在问题扩大之前就介入处理。可视化用Grafana之类的工具做几个面板把限流相关的指标都放上去值班的时候一眼就能看出有没有异常。这个投入很值得比出了问题再翻日志高效得多。5.4 限流规则的版本管理限流规则也是代码的一部分应该纳入版本管理。每次修改规则都要记录改了什么、为什么改、谁改的、什么时候改的。这样出了问题可以回溯也能避免多人同时改规则导致的冲突。实践中我建议把限流规则存在Git仓库里通过CI/CD流程发布到配置中心。规则变更走代码评审重要规则的调整要有压测数据支撑。这样虽然麻烦一点但能避免很多低级错误。我见过因为手抖把某个接口的限流阈值少写了一个零结果正常流量全被拒排查了半天才发现是配置问题。规则的回滚也要方便。发现新规则有问题要能一键回滚到上一个版本。这个能力在紧急情况下特别重要能大大缩短故障恢复时间。6. 个人实操心得与避坑建议6.1 几个容易踩的坑坑一限流key没有设过期时间。令牌桶的状态存在Redis里如果不设过期时间那些不再使用的key会一直占着内存时间长了Redis内存被撑爆。一定要根据业务情况设合理的TTL一般设为桶容量除以速率的两倍就够了。坑二Lua脚本里用了浮点数。Redis的Lua环境对浮点数支持有限令牌计算如果涉及小数可能出现精度问题。建议把速率和容量都设成整数或者用毫秒为单位放大计算避免浮点运算。坑三限流阈值按单机设但实际是分布式。这个前面提过但真的太常见了。部署了N个实例每个实例按单机能力设阈值结果总流量是N倍直接把下游打垮。一定要明确是单机限流还是分布式限流参数别搞混。坑四忽略限流的性能开销。限流本身也要消耗资源尤其是分布式限流每次请求都要走Redis。如果限流逻辑写得低效比如每次都new一个Redis连接或者Lua脚本特别复杂那限流本身就成了瓶颈。要压测限流逻辑的性能确保它不会成为新的问题。坑五限流规则没有兜底。如果配置中心挂了网关拉不到规则这时候是放行还是拒绝放行的话限流失效拒绝的话正常业务也挂了。我的建议是本地缓存一份规则配置中心不可用时用本地缓存同时告警。这样既保证限流基本可用又不会因为配置中心故障导致全站不可用。6.2 上线前的检查清单每次上线新的限流规则我都会过一遍这个清单规则格式是否正确能否被正常解析限流key的生成逻辑是否合理粒度是否匹配维度阈值是否经过压测验证是否留了余量被限流的响应是否符合规范状态码、响应体、重试头限流日志是否完整能否支撑问题排查监控指标是否配置告警是否设置回滚方案是否准备好出问题能否快速恢复是否在测试环境验证过多实例一致性这个清单看起来繁琐但能避免绝大多数低级问题。我宁愿上线前多花半小时检查也不愿意半夜被叫起来处理故障。6.3 关于限流粒度的一点思考最后聊一个偏理念的东西。限流的粒度不是越细越好。粒度太细规则数量爆炸维护成本高而且每个规则都要走一次限流判断性能开销大。粒度太粗又起不到精准保护的作用。我的经验是抓住关键路径做细粒度其他做粗粒度。比如一个电商系统下单、支付这两个接口做细粒度的用户级限流其他浏览类接口做粗粒度的全局限流就够了。把有限的精力放在最核心的地方性价比最高。另外限流规则的数量要控制。我见过一个系统配了几百条限流规则结果规则匹配本身就耗了不少CPU而且规则之间的优先级和冲突很难理清。规则数量最好控制在几十条以内超过这个量级就要考虑是不是设计有问题能不能合并简化。限流这件事说到底是在保护系统和服务用户之间找平衡。限得太松系统扛不住限得太紧用户用不了。这个平衡点没有标准答案只能靠对业务的深入理解和持续的观察调优来逼近。我自己的做法是宁可初期稍微松一点观察一段时间再收紧也不要一上来就限死那样容易误伤真实用户影响业务。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询