ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Cloud Gateway限流配置实战:原理、参数与避坑指南

Spring Cloud Gateway限流配置实战:原理、参数与避坑指南

网关这个东西,平时不吭不响地在流量入口处躺平,一旦出事,背锅比谁都惨。我印象很深的一次,是在模拟项目X上线前一晚做压测,网关后面接的订单服务直接被打到CPU 100%,接口延迟从50毫秒飙到8秒,紧接着就是雪崩、超时、重试、再把数据库拖垮。当时所有人都在查业务代码,唯独没人怀疑过网关。事后复盘才反应过来:网关层什么都没配,所有流量都是裸奔状态,那一刻我意识到gateway网关限流配置不是锦上添花,而是微服务架构里实打实的保命设施。

后来我花了两天时间,把Spring Cloud Gateway自带的限流能力从原理到配置完完整整捋了一遍,这篇就把实战经验写出来。整体围绕这样几个问题展开:网关限流和业务限流到底怎么分工、RequestRateLimiter底层是怎么限流的、YAML那几行配置每个参数该怎么调、KeyResolver怎么写才能按IP或按用户维度限流,最后再聊聊多实例部署和线上踩过的坑。不管你是刚接触微服务网关的开发者,还是已经在维护生产环境的架构师,这篇文章都应该能给出可直接抄作业的配置思路。

1. 为什么网关层要做限流

刚接微服务那会儿,我也觉得限流嘛,写个接口拦截器,在业务代码里用计数器挡一下不就行了?后来被现实教育了几次之后,才真正理解网关限流的位置感:它和业务限流解决的问题、部署的位置、防护的目标都不一样,两者根本互相替代不了。

1.1 没有限流的网关有多脆弱

先复盘一下那次压测的问题。订单服务本身扛得住日常峰值,但大促流量一上来,入口流量翻了几倍。此时网关没有限流,所有请求不管下游能不能消化,一律透传过去。服务端的线程池被占满之后,后面的请求排队、超时、重试,重试又增加新的流量,形成正反馈恶性循环。最终整个订单链路瘫痪,连健康检查都过不了。

这里的核心问题是:后端服务的容量是确定的,比如数据库连接池30个、线程池200个、下游RPC服务QPS上限5000,但入口流量是不确定的。网关作为所有流量的必经之路,是唯一一个能在流量到达业务代码之前就做出“拒绝”决策的位置。如果这里不做限制,就等于把系统的生死完全交给调用方,这在大流量的生产环境里就是一场豪赌。

所以网关限流的价值不是“限死业务”,而是在流量异常或者超出容量时,先挡住一部分请求,给下游留出喘息空间。

1.2 网关限流和业务限流的定位差异

我之前做过一个对比,整理下来大概是这样的:

维度网关限流业务限流
位置流量入口,所有请求必经业务代码内部,特定接口或服务
粒度粗粒度,按路由、IP、用户等细粒度,按业务场景、SKU、操作类型
目标保护整个下游服务集群保护单个核心场景或资源
实现方式网关中间件配置,零侵入业务代码、注解、组件
典型场景大促防刷、流量洪峰保护秒杀、下单、支付等单点控制

实际项目中,两层限流是配合关系。举个例子,秒杀场景里,网关层按IP限流,拦住同一个IP疯狂刷接口;业务层按用户账号和商品维度的令牌数再做一次精准控制,防止超卖。网关限流解决的是“整体流量能不能进来”,业务限流解决的是“进来的流量怎么分配”。

因此就有了一个很关键的架构原则:网关限流必须要配置,且要优先于业务限流。

2. Spring Cloud Gateway限流的核心机制

要配置好gateway网关限流,不能只停留在“照着配几行YAML能用”的层面,因为参数怎么调、出问题了怎么排查,全得靠对底层机制的理解。Spring Cloud Gateway自带了一个叫RequestRateLimiter的过滤器,它默认结合Redis实现了基于令牌桶算法的限流。

2.1 RequestRateLimiter过滤器工作原理

RequestRateLimiter是Gateway提供的内置GatewayFilter之一。它做事情很简单:每个请求过来,先向Redis发起一个请求,按当前路由的key从令牌桶里取令牌,取到了就放行,取不到就直接返回HTTP 429 Too Many Requests。

这个过滤器在路由配置中通过filters段挂载,YAML长这样:

spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/api/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 redis-rate-limiter.burstCapacity: 20 redis-rate-limiter.requestedTokens: 1

这里有几个点要弄清楚。第一,RequestRateLimiter只是一个壳,真正干活的bean是RedisRateLimiter。第二,默认情况下,Gateway是按路由ID来隔离限流计数器的,也就是说你配置了几个路由,就有几个独立的令牌桶。第三,限流状态存储在Redis里,所以天然支持分布式,后面单独讲多实例的时候再展开。

另外有一点特别容易忽略:RequestRateLimiter从Redis取令牌走的是Reactive方式,因此网关项目必须引入响应式的Redis依赖,如果用成普通spring-boot-starter-data-redis,启动时会直接报错。

2.2 令牌桶算法是怎么工作的

限流算法有很多种,固定窗口、滑动窗口、漏桶、令牌桶,Spring Cloud Gateway选的是令牌桶。它理解起来很像景区入口的闸机:景区规定每秒钟放行一定数量的游客进入,但门口有一个容量有限的缓冲区,游客可以先在这个缓冲区里等着。

令牌桶的具体逻辑是这样的:系统以固定的速率向桶里补充令牌,比如每秒钟补充10个令牌。每个请求进来,先尝试从桶里拿走一个令牌,拿得到就放行,拿不到就拒绝。桶有一个最大容量,超过容量的令牌会被丢弃,不会无限累积。

用生活化的例子解释:桶就像你家小区门口的临时停车位,物业每10分钟清理出一个空位,但整个临时停车区最多能停20辆车。如果这个时刻恰好有20辆车排队等着,放行一波;如果来车太猛,超过20辆上限,后面的就只能等下一轮。

令牌桶相比固定窗口的优势在于:它可以允许一定的突发流量。固定窗口算法只要当前窗口内请求数超过阈值就一律拒绝,而令牌桶只要桶里还有积攒的令牌,短时间内是可以接受一拨高于平均速率的突发请求的,这对于电商大促时流量短时间飙升的场景很重要。

RedisRateLimiter在实现令牌桶时,是用一段Lua脚本在Redis服务端执行的,整个取令牌逻辑是原子操作,多个网关实例同时操作同一个key不会出现并发竞争问题。这比你自己在业务代码里用AtomicInteger做计数器靠谱得多。

2.3 三个核心参数到底怎么调

RequestRateLimiter的配置中,有三个最核心的参数,关于它们的含义,网上很多文章讲得并不透彻,我用自己的话重新拆一遍。

参数含义类比默认值
replenishRate每秒向桶中补充的令牌数,也就是平均允许的持续请求速率每秒放行N个人1
burstCapacity桶的最大容量,允许的突发请求总量上限缓冲区最多容纳人数1
requestedTokens每个请求消耗的令牌数,用于让某些请求更“贵”每人进门消耗的票数1

默认情况下三者都是1,意味着每秒最多放行1个请求,突发量也只有1,这显然不能满足实际需要。一般来说,replenishRate应该设置成下游服务能够稳定支撑的QPS值,burstCapacity则设置成replenishRate的2到3倍。

举个例子:下游订单服务经过压测,稳定支撑的QPS是1000,那么yaml里可以配:

redis-rate-limiter.replenishRate: 1000 redis-rate-limiter.burstCapacity: 2500

为什么要给2到3倍的突发空间?因为线上流量从来都不是均匀的,往往在秒级内有波峰波谷。比如一个接口平均QPS是500,但某几秒内冲到1500,如果burstCapacity只等于replenishRate,那几秒钟内大部分请求会被误伤。但burstCapacity也不能毫无节制地调大,否则令牌桶会积攒下大量令牌,一旦流量突然集中涌入,后端会直接被打穿。

还有一个容易被忽略的参数requestedTokens。它默认是1,但如果你希望某些高成本接口(比如批量导出、复杂报表查询)消耗更多令牌,可以在路由维度上单独调大。不过实际项目中用得不多,我遇到过一个场景是给VIP用户配了requestedTokens=0.5,等价于给这部分请求double的配额,但这个用法需要重写RedisRateLimiter,不建议新手一开始就折腾。

3. 从零配置gateway网关限流

理论部分讲完,下面直接进入实操。我假设你已经有一个可以正常运行的Spring Cloud Gateway项目,下面每一步都按生产标准来。

3.1 引入正确的依赖

限流必须依赖Redis,而且必须是响应式Redis客户端。在pom.xml中,核心依赖是这样:

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>

这里要特别强调,一定要使用spring-boot-starter-data-redis-reactive,而不是spring-boot-starter-data-redis。因为Gateway是基于WebFlux构建的,RequestRateLimiter底层的ReactiveRedisTemplate要求非阻塞的Reactive连接。我只引入普通Redis依赖时,项目启动就会报类似“No qualifying bean of type ReactiveRedisTemplate”的错误。如果你是从老项目改造,注意同时移除或忽略掉原先的普通Redis依赖,两个一起引入会引发Bean冲突。

3.2 配置Redis连接

既然限流状态要存Redis,自然得配置连接信息。在application.yml中,和普通Redis配置一样:

spring: data: redis: host: 10.10.10.20 port: 6379 password: yourpassword timeout: 2000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0

对于超时时间和连接池配置,多提一嘴。限流过滤器是同步阻塞在请求链路上的,如果Redis出现缓慢或超时,网关的性能也会被拖下水,因此timeout不要设置太长,一般1到2秒,同时连接池的max-active要结合网关实例数和并发量来估算。我见过有人把max-active设成100,生产环境一共就4个网关实例,每个实例Redis连接数轻轻松松破400,直接把Redis的连接数打满,这是典型的过度配置。

3.3 在路由上挂载限流过滤器

配置限流最常见的方式是在路由的filters段中添加RequestRateLimiter。我来写一个完整的例子。

假设网关需要转发所有以/api/order开头的请求到订单服务,并做限流保护:

spring: cloud: gateway: routes: - id: order-route uri: lb://order-service predicates: - Path=/api/order/** filters: - StripPrefix=1 - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 200 redis-rate-limiter.burstCapacity: 400 redis-rate-limiter.requestedTokens: 1 key-resolver: "#{@ipKeyResolver}"

这里的几个配置要逐一解释。

  • redis-rate-limiter.replenishRate=200:允许的稳定速率是每秒200个请求。
  • redis-rate-limiter.burstCapacity=400:桶里最多积攒400个令牌,允许瞬间涌入400个请求。
  • redis-rate-limiter.requestedTokens=1:每个普通请求消耗1个令牌。
  • key-resolver:指定使用哪个KeyResolver,这里用的是SpEL表达式,指向一个名为ipKeyResolver的Bean,这个Bean必须提前定义好。

需要注意的是,key-resolver在较新的Spring Cloud版本中可以放在args里,也可以在全局配置spring.cloud.gateway.filter.request-rate-limiter.key-resolver下面指定。如果项目里只有一个KeyResolver Bean,其实不需要显式指定;如果有多个,就必须要指定,否则启动会报错。

3.4 自定义KeyResolver

KeyResolver是限流配置里比较容易迷糊的点。它的作用是决定“当前请求按什么维度来做限流”。默认要做分布式部署时,单靠presenter的RemoteAddress是不可靠的,大概率拿到的网关前一层负载均衡的IP,所以自定义KeyResolver是刚需。

最常用的两个维度:按IP限流、按用户限流。先看按IP的写法:

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.net.InetAddress; import java.net.InetSocketAddress; @Configuration public class GatewayRateLimitConfig { @Bean public KeyResolver ipKeyResolver() { return exchange -> { String ip = getClientIp(exchange); return Mono.just(ip); }; } private String getClientIp(ServerWebExchange exchange) { // 优先取X-Forwarded-For里的真实IP,避免被前置负载均衡隐藏 String xff = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For"); if (xff != null && !xff.isEmpty()) { return xff.split(",")[0].trim(); } InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); if (remoteAddress != null) { InetAddress address = remoteAddress.getAddress(); return address != null ? address.getHostAddress() : "unknown"; } return "unknown"; } }

这里有一个线上环境的重要细节:如果网关前面悬挂了云负载均衡器或者Nginx,exchange.getRequest().getRemoteAddress()拿到的往往是负载均衡器的IP,而不是用户真实IP。此时限流按IP就变成了按负载均衡器IP,相当于所有用户共用一个限额,配置瞬间失效。所以生产环境里,按IP限流必须解析X-Forwarded-For头。

再来看按用户ID限流的写法。一般网关在鉴权之后,会把用户信息写入请求头,或者直接放到ServerWebExchange的attribute里:

@Bean public KeyResolver userKeyResolver() { return exchange -> { String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id"); if (userId == null || userId.isEmpty()) { return Mono.just("anonymous"); } return Mono.just(userId); }; }

还有一个常见需求是按请求路径限流。比如/api/order/search这个接口被爬虫打,但其它接口正常,这时按路径维度隔离就很方便:

@Bean public KeyResolver pathKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getPath().value()); }

KeyResolver写好后,注册成Spring Bean即可。如果项目里存在多个KeyResolver类型Bean,并且未通过key-resolver明确指定,Gateway启动时就会报“Multiple KeyResolver beans found”之类的错误。

3.5 验证限流是否生效

配置完以后,怎么证明限流真的工作?我觉得最快的方式是用shell脚本发大量并发请求。

先不启动限流,看看接口正常响应。然后加上限流配置,用curl或者ab工具直接打。以ab为例:

ab -n 1000 -c 50 http://localhost:9000/api/order/list

如果replenishRate=200、burstCapacity=400、qps=1000,那么响应码中应该会有大量HTTP 429。用curl单次请求看响应头:

curl -i http://localhost:9000/api/order/list

能正常返回时,可以在响应头里看到Gateway加入的限流信息。RedisRateLimiter默认会在响应中附加X-RateLimit-Remaining、X-RateLimit-Burst-Capacity等header。这些响应头可以直观反映当前桶里还剩多少令牌。如果请求过猛被限流,返回的body默认是空的,status是429。

为了更直观,还可以直接观察Redis里的限流key:

redis-cli keys "*request_rate_limiter*" redis-cli mget request_rate_limiter.order-route.tokens request_rate_limiter.order-route.timestamp

tokens字段就是桶里当前的剩余令牌数。通过对比它随请求数量的变化,基本可以确定限流是否在正常工作。

4. 高级玩法:从能用走向好用

把基础的限流配好之后,只能说达到了“能用”的标准。在生产环境里,我们通常还需要处理路由隔离、限流响应体验、多实例部署一致性问题,以及部分场景下是否需要引入更专业的限流组件。

4.1 不同路由配置不同的限流策略

不同后端服务的容量不一样,全用同一套QPS阈值会误伤。比如订单服务容量大,可以扛1000QPS,但优惠券服务只能扛100QPS,这就需要在路由的filters里分别配置。

订单路由限流:

- id: order-route uri: lb://order-service predicates: - Path=/api/order/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 1000 redis-rate-limiter.burstCapacity: 2000

优惠券路由限流:

- id: coupon-route uri: lb://coupon-service predicates: - Path=/api/coupon/** filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 100 redis-rate-limiter.burstCapacity: 200

这里要理解一个底层细节:RequestRateLimiter的RedisKey默认是以路由ID为分隔的。Redis里存储的key格式是request_rate_limiter.{路由ID}.tokens。所以两个路由的令牌桶互相独立,互不影响。这个机制给了我们很好的路由隔离能力,但也要注意,一个服务如果拆成多个路由配置,比如按版本拆了v1、v2两条路由,它们各自限流,最终打到后端的总流量是叠加的,别把两条路由的replenishRate分别设成1000,结果后端被打2000的QPS。

4.2 自定义限流返回结果

默认情况下,网关限流后返回的HTTP 429响应body是空的。这对浏览器用户来说体验尚可,但对API调用方来说,往往需要明确知道“我被限流了,多久之后可以重试”。所以生产环境里,我会自定义一下返回结构。

Spring Cloud Gateway提供了DefaultErrorWebExceptionHandler来处理Gateway的异常。我们可以自定义ErrorAttributes,把TooManyRequestsException包装成JSON返回。核心代码如下:

import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import java.util.LinkedHashMap; import java.util.Map; @Component public class GatewayErrorAttributes extends DefaultErrorAttributes { @Override public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); Throwable error = getError(request); HttpStatus status = determineHttpStatus(error, request); errorAttributes.put("code", status.value()); errorAttributes.put("message", determineMessage(error, status)); errorAttributes.put("timestamp", System.currentTimeMillis()); return errorAttributes; } private HttpStatus determineHttpStatus(Throwable error, ServerRequest request) { return error instanceof org.springframework.cloud.gateway.support.TooManyRequestsException ? HttpStatus.TOO_MANY_REQUESTS : HttpStatus.INTERNAL_SERVER_ERROR; } private String determineMessage(Throwable error, HttpStatus status) { if (error instanceof org.springframework.cloud.gateway.support.TooManyRequestsException) { return "请求过于频繁,请稍后重试"; } return status.getReasonPhrase(); } }

这样做的好处是,调用方拿到一个固定的、结构化的错误码和描述,方便客户端做提示与重试策略。同样在网关层,也可以在GlobalFilter里对响应状态码做进一步处理,比如向响应头写入Retry-After,让调用方知道几秒后再试。

4.3 多实例部署下的限流准确性

Spring Cloud Gateway在生产环境通常不会只部署一个实例,至少是两个起步。此时最关心的问题就是:限流计数是各算各的,还是共享一份?答案是共享。

因为RequestRateLimiter把令牌桶的计数存储在Redis里,所有网关实例操作的是同一批key,所以不管请求被负载均衡到哪个实例,限流的总量始终是准确的。这也是Gateway选择Redis做限流存储的核心原因。

但这里有一个需要注意的坑:KeyResolver如果按IP限流,而网关注入X-Forwarded-For头的解析方式不一致,或不同实例拿到不同的header时,可能导致同一用户在不同实例上被拆分成两个不同的“IP”,限流效果就会打折扣。所以多实例部署时,自定义KeyResolver必须保证维度计算结果一致,尽量不依赖实例本地状态。

还有一个顺带的好处:因为限流状态在Redis,你不必像本地限流那样为每个实例单独配置阈值,replenishRate和burstCapacity直接按“总QPS目标”配置即可,不用除以实例数。

4.4 是否要引入Sentinel这类扩展组件

RedisRateLimiter已经能覆盖大部分场景,但如果有更复杂的限流需求,比如动态规则推送、细粒度指标监控、熔断降级一体,这时候可以考虑在网关上引入Sentinel。Spring Cloud Gateway官方也提供了对应的适配包。

我做了一个对比,供选型参考:

维度RequestRateLimiterSentinel网关适配Resilience4j
配置方式YAML、Java规则中心配置,支持动态推送代码级配置
存储Redis不依赖Redis(默认本地,也可持久化)本地状态
限流算法令牌桶滑动窗口、令牌桶等多种支持
集群限流支持有专门集群模式不支持,需要自研
监控面板需要自己接Redis指标有控制台需要第三方
侵入性低,配置即可中,需要理解规则模型高,代码内嵌
适合场景大多数微服务网关需要精细流量治理应用级轻量保护

以我的实践经验,如果只是要给网关加一层基础兜底限流,直接用内置RequestRateLimiter就够了,配置量小,维护成本低。如果公司已经有完整的监控和配置中心体系,想对网关流量做精细化运营,再上Sentinel不迟。

5. 常见问题排查与避坑实录

配置限流这件事,看起来就是几行YAML,但实际操作过程中踩坑还是很多的。我把遇到过的几个高频问题整理出来,附带排查思路。

5.1 问题一:限流完全没有生效

现象是配置照抄,接口高并发打上去,一个429都没有。

第一排查点:Gateways的路由是否真的命中了。如果请求的Path和predicates不匹配,路由根本不会进入RequestRateLimiter过滤器,自然不限流。可以在idea或启动日志里开启trace日志,观察请求命中了哪个route id。

第二排查点:KeyResolver是否注册成功。如果你的项目里定义了多个KeyResolver Bean,但没有指定key-resolver用哪一个,启动过程可能直接报错。如果没报错但也没生效,查看是否存在多个不同名的Bean被Spring注入到了错误位置。

第三排查点:确认KeyResolver返回的key是否统一。如果按IP解析,但是每次请求拿到的IP都不一样,限流维度就被打散成数千个key,每个key每秒限200,总流量毫无压力。

5.2 问题二:Redis连接异常导致网关启动失败

由于RequestRateLimiter强依赖Redis,网关启动时会创建ReactiveRedisTemplate,如果Redis连接不上,启动日志会报连接异常。这个问题在生产上很致命,因为网关往往是最先启动的基础设施之一。

我的建议是:保证Gateway的Redis和业务Redis隔离,至少用不同的db index,不然限流计数器被业务数据冲掉或者被监控扫描拖慢,对网关都不是好事。另外要对Redis配置合适的超时,确认Lettuce连接池参数没有设置过大的max-active。

5.3 问题三:burstCapacity调太大导致限流形同虚设

我有一次给搜索服务配了replenishRate=200,burstCapacity=5000。本意是想让突发流量进来,结果因为5000的容量太大,桶里长期积攒着大量令牌,搜索服务被压测流量瞬间打满,限流等于没配。

burstCapacity和replenishRate的比例,不应该拍脑袋定,而是按下游服务“能容忍的瞬时突刺量”来定。我的建议是从2倍起步,压测之后再逐步调。如果下游服务本身抗突发能力弱,倍数就调低一点,比如1.5倍。其实最稳的方式是先做压测,找出后端在不出现超时和5xx前提下的最大突发QPS,这个值才是burstCapacity的依据。

5.4 问题四:KeyResolver拿不到真实IP

前面提到了X-Forwarded-For的坑,这里再展开一下。如果你的网关前面还有一层Nginx或者云负载均衡,RemoteAddress永远是那层代理的IP,按IP限流永远只对代理IP限流。

解决方法是自定义KeyResolver,从X-Forwarded-For中取第一个IP。但注意,X-Forwarded-For是可以伪造的,客户端在直连网关时可以直接传一个假的X-Forwarded-For头。因此生产环境里,如果要对IP做严格的限流,信任边界要落在“网关前面的负载均衡是否清洗了该请求头”。一个稳妥的做法是,外层负载均衡统一覆盖X-Forwarded-For头,整理成标准格式,网关只信任这层的处理结果。

5.5 常见问题速查表

顺手整理了一张排查表,遇到问题可以先对着查:

现象可能原因排查动作
限流不生效路由predicates未匹配开启gateway日志,确认route id
限流不生效多个KeyResolver未指定在配置中指定key-resolver
限流不生效Key值维度太散或不准检查KeyResolver返回结果是否稳定
429全部打满burstCapacity太小压测后按2到3倍replenishRate调整
429全部打满网关前面代理IP未归一检查KeyResolver是否有解析XFF逻辑
启动失败没引reactive redis依赖换成spring-boot-starter-data-redis-reactive
启动失败Redis连接超时检查网络、密码、Redis服务状态
大量误伤X-Forwarded-For被伪造在负载均衡层统一清洗请求头
响应体为空默认429返回空body自定义ErrorAttributes返回JSON错误体

6. 写在最后的几点经验

限流配置配完之后,我建议你在上线前至少做一份压测报告,明确记录三个指标:正常QPS、触发限流的QPS阈值、恢复成功率。这样将来领导问“网关限流到底起没起作用”的时候,你能拿数据说话,而不是只讲道理。

另外一个小技巧:把网关限流的Redis key纳入监控大盘。通过监控request_rate_limiter.*.tokens字段的变化,可以提前观察到某个路由是否频繁被打满。这比等到用户反馈页面打不开再排查,要主动得多。

在整个配置过程中,我觉得最有价值的一点,是理解了网关限流不是把自己锁死,而是给系统一个优雅的“拒绝”能力——面对超出容量的流量,不慌、不崩、有明确的协议反馈,等到流量恢复又能自动回到正常状态。这个能力,是微服务架构稳定性的底线。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进