
你的微服务网关是不是也遇到过这种场面大促一开闸几十万的请求像洪水一样涌进来下游订单服务瞬间被打瘫队列积压用户刷屏投诉系统繁忙限流不是要不要做的事而是早晚要出事故的事。本文带你用 Spring Cloud Gateway 的限流过滤器配合 Redis 做令牌桶限流再用熔断把故障隔离在网关这一层让下游服务永远站在安全线之后。纯 Java 实战复制就能跑。一、这个问题到底是什么网关Gateway是微服务系统的大门所有外部请求先进这道门才被路由到后端的各个服务。你可以把它想象成小区门口的保安亭所有访客进门都得经过保安登记和分流。问题来了如果某天有大量请求同时涌进这扇门比如秒杀、抢购、凌晨批量任务触发、甚至恶意刷接口门后的服务扛不住就会出现三种典型事故——第一服务崩溃。下单服务每秒只能处理 1000 个请求结果来了 1 万个线程池被打满服务直接宕机连正常用户都进不去。第二雪崩效应。A 服务挂了调用 A 的 B 服务还在傻等 A 的响应把 B 自己的线程也占满了B 跟着挂然后 C 也挂……一个服务出事整条链路全瘫。这就是雪崩比单个服务挂掉可怕得多。第三超时堆积。请求响应越来越慢用户的每个操作都在转圈体验崩坏最终用户流失。限流要解决的核心问题就一句话在请求进入后端之前按我定好的速度把请求放行超出的请求要么排队、要么拒绝绝不能让后端一次性吃下超出能力的流量。它守护的是系统的吞吐上限。网关做限流有个天然优势它处于流量的最前沿所有请求必经此地。在这里限流等于在洪水入口就装好闸门而不是等洪水淹到下游每个服务再各自救。二、底层原理到底怎么回事Spring Cloud Gateway 的限流是靠一个叫过滤器Filter的机制实现的。简单说过滤器就是请求在网关里过路时要做的一连串检查动作先看用户有没有权限再查这个接口允许每秒放行多少最后才把请求路由到后端。限流过滤器里最关键的是桶这个概念衍生出几种算法固定窗口计数把时间切成一段段固定长度的小窗口比如 1 秒一个窗口每个窗口里维护一个计数器请求进来就加 1超过上限就拒绝窗口一过计数器清零。它实现最省事但有个明显漏洞假设每秒限 100 个某个窗口最后 0.5 秒来了 100 个下一个窗口开头 0.5 秒又来了 100 个那么在这挨着的 1 秒时间里实际放行了 200 个远超限速。这就是临界突刺问题。滑动窗口计数把窗口切成很多更小的子窗口取最近一段完整时间比如最近 1 秒内所有子窗口计数之和作为当前速率超过就限流。它解决了临界突刺但需要维护多个子窗口状态内存和计算成本更高。令牌桶算法Token Bucket这是最经典也最常用来做允许突发流量的算法。想象一个桶桶里装的是令牌令牌按固定速率往桶里注入比如每秒注入 10 个。每个请求要进网关必须先拿走一个令牌拿到才能放行拿不到就拒绝或排队。桶有容量上限满了就不再注入所以允许短时间内的突发流量一口气把桶里攒的令牌用完但又不会无限突发。Gateway 默认用的RequestRateLimiter过滤器底层就是令牌桶算法的实现配合 Redis 存储令牌桶的状态。为什么用 Redis因为网关在线上通常是多实例部署的有好几个网关进程水平扩展如果每个实例各用一个内存里的桶那限流就失效了——用户绕着一个实例把桶打空换到另一个实例又是满桶。用 Redis 存令牌桶所有网关实例共享同一个桶限流才是全局的、准确的。RequestRateLimiter过滤器有两个核心配置项redis-rate-limiter.replenishRate令牌桶每秒注入的令牌数也就是正常情况下每秒允许通过的请求数基准速率。redis-rate-limiter.burstCapacity桶的最大容量也就是允许的突发流量上限。它必须大于 replenishRate否则突发能力为 0。它实现的是流控中平滑突发的效果平时请求少时桶里令牌攒着突然来一波流量可以先用掉攒的令牌顶上但长期平均速率始终被卡在 replenishRate 附近不会让后端持续超载。熔断则和限流是两码事但经常搭配使用。限流是主动防水在流量进来前就控制熔断是被动止损在监控到下游开始出错后主动切断调用。熔断有点类似家里电路的保险丝电流过大时保险丝熔断切断电路保护电器。在 Spring Cloud 里熔断通常用一个叫 Resilience4j 的组件实现——当下游服务连续失败超过阈值熔断器就打开一段时间内直接拒绝调用该服务快速失败并返回降级结果让下游有时间恢复恢复后再半开试探成功就把熔断器关闭。用一张表说清限流和熔断的区别维度限流RequestRateLimiter熔断Resilience4j目的控制进入后端的流量速度隔离故障、保护已出问题的下游触发依据请求速率是否超阈值下游失败率/失败次数是否超标作用位置请求进来之前检测到下游故障之后比喻进门闸门按速度放行保险丝坏了就切断限流在前熔断在后一前一后把网关打造成微服务的安全阀。三、实战手把手写代码整个项目由三个完整文件组成一个 pom.xml、一个 application.yml、一个网关启动类。复制下来放进同一个工程就能跑。第一步pom.xml完整的 Maven 依赖下面是在纯 Spring Boot 项目里引入 Spring Cloud Gateway 和 Redis 的完整依赖。注意 Spring Boot 用 4.0.7、Spring Cloud 用 2025.1.3两者是匹配的组合Spring Cloud 2025.1.3 只兼容 Spring Boot 4.0.x不能拉到 4.1.x这是版本硬约束。?xml version1.0 encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion4.0.7/versionrelativePath//parentgroupIdcom.dunfan/groupIdartifactIdgateway-demo/artifactIdversion1.0.0/versionnamegateway-demo/namedescriptionSpring Cloud Gateway 限流 熔断示例/descriptionpropertiesjava.version21/java.versionspring-cloud.version2025.1.3/spring-cloud.version/propertiesdependencyManagementdependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversion${spring-cloud.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependencies!-- 网关核心包含路由、过滤器、限流能力 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-gateway/artifactId/dependency!-- 限流要用 Redis 存令牌桶这里引入响应式 Redis 客户端 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis-reactive/artifactId/dependency!-- 熔断降级使用 Resilience4j --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-circuitbreaker-resilience4j/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project这段配置在干什么spring-boot-starter-parent是 Boot 的家长统一管理一大堆依赖版本省得你一个个写版本号。spring-cloud-dependencies通过 dependencyManagement 导入专门管理 Spring Cloud 各组件版本Gateway 目前由它管理到 5.0.3。spring-cloud-starter-gateway是网关本体spring-cloud-starter-circuitbreaker-resilience4j提供熔断能力。版本 21 是因为 Java 已经是 21 的时代别用旧的 17 了。第二步application.yml路由 限流 熔断配置配置文件是限流真正生效的地方。下面这段给一个/api/order/**的路由加上了限流过滤器并且配置了一个熔断 fallback。server:port:8080spring:application:name:gateway-demoredis:host:localhostport:6379password:cloud:gateway:routes:-id:order-serviceuri:lb://order-servicepredicates:-Path/api/order/**filters:-name:RequestRateLimiterargs:redis-rate-limiter.replenishRate:10redis-rate-limiter.burstCapacity:20key-resolver:#{userKeyResolver}-name:CircuitBreakerargs:name:orderCircuitBreakerfallbackUri:forward:/fallback/orderBusy# 上面是常规写法下面两行是等价的简化写法二选一即可# filters:# - RequestRateLimiter10, 20, #{userKeyResolver}logging:level:org.springframework.cloud.gateway:INFO逐行解释replenishRate: 10表示正常情况下每秒最多放行 10 个请求burstCapacity: 20表示最多允许突发 20 个相当于桶里最多攒 20 个令牌。key-resolver: #{userKeyResolver}指定按什么来区分限流对象——这里是按用户身份下面定义这样是每个用户各自限 10 个/秒而不是所有人共用一个桶。CircuitBreaker过滤器名字里配置了一个叫orderCircuitBreaker的熔断器当它检测到调用 order-service 失败超过阈值时会走fallbackUri指向的降级接口返回系统忙的友好提示而不是让用户看到一堆报错堆栈。第三步网关启动类 限流键解析器两个完整 Java 文件先看主启动类。Gateway 项目不写常规的 Controller它本身就是一个转发器所以启动类就是标准 Spring Boot 启动类packagecom.dunfan.gateway;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplicationpublicclassGatewayApplication{publicstaticvoidmain(String[]args){SpringApplication.run(GatewayApplication.class,args);}}这段代码就是个入口SpringBootApplication告诉 Spring “这是应用的启动中心”main方法里的run负责把整个网关拉起来。它和普通后端应用的启动类没有区别因为网关本身也是一个 Spring Boot 应用。再看限流键解析器。这个类的职责是告诉 Gateway限流按谁算——我们选择按请求里的用户 ID 来区分packagecom.dunfan.gateway.config;importorg.springframework.cloud.gateway.filter.ratelimit.KeyResolver;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importreactor.core.publisher.Mono;ConfigurationpublicclassRateLimitConfig{BeanpublicKeyResolveruserKeyResolver(){returnexchange-{// 从请求头里取出 userId作为限流的分组键StringuserIdexchange.getRequest().getHeaders().getFirst(userId);if(userIdnull||userId.trim().isEmpty()){userIdanonymous;}returnMono.just(userId);};}}这里的关键逻辑KeyResolver返回一个MonoString这个字符串就是限流键。比如取到userId1001那么所有带userId1001的请求共用一个令牌桶他们合起来每秒最多 10 个userId2002用的是另一个桶互不影响。这样不同用户不会因为别人刷接口而被连带限流比较公平。第四步降级 fallback 接口熔断触发后要走fallbackUri所以需要一个能响应这个降级路径的接口。Gateway 里可以用 WebFlux 的方式写一个简单的降级处理packagecom.dunfan.gateway.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;importreactor.core.publisher.Mono;RestControllerpublicclassFallbackController{GetMapping(/fallback/orderBusy)publicMonoStringorderBusy(){returnMono.just(订单服务暂时繁忙请稍后再试);}}当orderCircuitBreaker熔断打开请求会被转到/fallback/orderBusy返回这句友好提示用户看到的是请稍后再试而不是 500 报错。第五步怎么验证跑起来把上述文件放好本地启动 Redis默认端口 6379然后运行GatewayApplication。再用下面命令模拟并发请求比如同时发 30 个请求超过了 burstCapacity 20foriin$(seq130);docurl-s-o/dev/null-w%{http_code}\n-HuserId: 1001http://localhost:8080/api/order/1done;wait你会看到一部分请求返回正常走路由转发得到后端响应超出 20 的请求被限流拦截。如果你没起真正的 order-service返回的可能是网关的 503 或熔断降级响应——这正演示了下游不可用被熔断兜住的效果。把replenishRate调大调小就能直观感受每秒放行多少个的差别。四、踩坑经验和最佳实践限流看起来配置几分钟就完事但真实生产里到处是坑下面几条都是我实际趟过的。坑一burstCapacity 必须大于 replenishRate否则限流卡死。这两个值是很多新手栽跟头的地方。如果你把 burstCapacity 设得小于等于 replenishRate桶里永远装不满能突发的量限流行为会变得很怪甚至出现该放行的请求也被拦。经验上 burstCapacity 设为 replenishRate 的 2 到 3 倍比较合理太大会失去限流意义太小又没突发能力。坑二key-resolver 不分键等于全局一个桶一个用户把全站堵死。如果限流键写死成一个固定字符串那么所有用户共用一个令牌桶某个用户恶意狂刷瞬间把桶打完正常用户全被连带限流。生产必须按用户、按 IP、或按接口维度合理分键。分键粒度越细越能精准限流但也越考验你对业务的理解。坑三Redis 挂了限流直接瘫痪甚至更糟。RequestRateLimiter 依赖 Redis 存令牌桶如果 Redis 不可用限流过滤器可能抛异常导致整个请求链崩掉。生产一定要给 Redis 做高可用集群或哨兵并且给限流过滤器配好容错——比如 Redis 异常时是放行还是拒绝这个策略必须在压测时定下来别到事故现场才手忙脚乱。坑四GateWay 多实例必须共享同一个 Redis否则限流失效。这是最容易被忽视的。很多团队本地一个实例调试好好的一上生产开了 4 个网关实例才发现限流完全失效——因为每个实例自己的内存桶各算各的。必须让所有实例连同一个 Redis令牌桶状态集中存储限流才能全局生效。坑五熔断的 fallback 别只给一句话要给可用的降级数据。很多项目熔断降级就反回一句系统繁忙对用户毫无价值。好的降级是返回缓存里的旧数据、返回本地默认值、或返回更慢的可接受响应。把降级做有内容用户感知会好很多。最佳实践小结限流阈值要基于压测数据定不是拍脑袋。先测出后端服务的真实最大吞吐比如 800 QPS再留 20% 余量设限流值。先限流、后熔断顺序别反。限流挡在前面防超载熔断跟在后面防故障扩散。把限流、熔断、降级的触发次数和原因打日志、接监控告警出事能第一时间定位是谁在拦、为什么拦。配置用配置中心比如 Nacos管理改限流阈值不用重启网关线上调参才来得及。五、性能对比和技术选型网关层面的限流方案不止 Spring Cloud Gateway 一种各有适用场景方案优点缺点适用场景Spring Cloud Gateway Redis 限流与微服务生态无缝集成配置即用支持动态调参每个请求多一次 Redis 网络往返多实例下是瓶颈点Spring Cloud 生态内的标准网关网关层 本地内存令牌桶如 Guava无额外网络开销性能最高多实例各自为政限流不全局单实例、对实时性要求极高的场景独立限流组件如 Sentinel 独立部署限流规则可视化、动态推送功能全引入额外组件部署和运维成本高大规模、限流规则复杂多变的中大型系统Nginx Lua 限流在流量最前端性能极好需要 Lua 功底规则下发不如微服务组件灵活偏基础设施层、统一入口的防护性能层面纯内存令牌桶Guava单机性能最高因为不依赖外部存储加 Redis 后每个限流请求多一次网络往返在高并发下 Redis 就成了必须专项优化的点比如用 Redis 集群分散压力。Gateway 的 RequestRateLimiter 更适合Spring Cloud 一体、方便动态配置的场景它的正确性带来的稳定性收益通常大于那一点点额外网络开销。选型建议如果你的系统已经是 Spring Cloud 微服务架构优先用 Gateway 自带限流 Resilience4j 熔断成本最低、集成最顺如果限流规则极其复杂、需要可视化运维和流控大盘再引入 Sentinel 这样的独立限流组件。六、总结限流和熔断是微服务网关保障稳定性的两件核心武器它们解决的是两个不同的问题限流在请求进来前主动防水控制进入后端的流量速率熔断在检测到下游故障后被动止损隔离故障防止雪崩。两者一前一后配合网关就成了微服务的安全阀。Spring Cloud Gateway 用一个RequestRateLimiter过滤器就实现了令牌桶限流配合 Redis 让多实例共享同一个桶、实现全局准确的限流再用CircuitBreaker过滤器接上 Resilience4j 做熔断降级把故障兜在网关这一层用户只看到友好的请稍后再试。实践中最容易栽的坑就几条burstCapacity 与 replenishRate 的关系、限流键要合理分维度、Redis 要保证可用性、多实例必须共享同一套状态、降级要给有内容的数据。把这几点记牢你就能在生产上放心地把安全阀交给网关。记住限流阈值靠压测数据说话多实例限流靠共享存储兜底降级要做得有内容。这三条守住你的微服务再大促也不会轻易趴窝。