《十万个why》系列持续更新中。
用 Spring Cloud Gateway 的团队基本都知道它自带限流功能,配合 Redis 就能实现令牌桶限流。但你去看任何一家稍有规模的公司的架构图,流量进来之后第一道限流几乎都在 Nginx 层,Gateway 的限流反而是第二道甚至第三道。
既然 Gateway 已经能限流了,为什么还要在 Nginx 层再做一遍?
这两层限流解决的问题完全不同,缺一不可。
Gateway 的限流是怎么工作的
Spring Cloud Gateway 内置了 RequestRateLimiterGatewayFilterFactory,底层默认用 Redis + Lua 脚本实现令牌桶算法。配置起来很简单:
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 500 # 每秒生成 500 个令牌
redis-rate-limiter.burstCapacity: 1000 # 桶容量 1000
key-resolver: "#{@userKeyResolver}" # 按用户维度限流
每个请求进来,Gateway 会通过 Lua 脚本在 Redis 里执行一次令牌桶的扣减操作。令牌够就放行,不够就返回 429。
这个方案在业务逻辑层面没问题,但有一个前提条件:请求必须先到达 Gateway 进程,才能被限流。
Gateway 本身也会被打垮
Spring Cloud Gateway 跑在 JVM 上,底层是 Netty。单实例的处理能力大概在什么量级?
在 4 核 8GB 的机器上,Gateway 单实例能扛住的 QPS 大约在 2 万到 3 万之间,具体取决于过滤器链的复杂度和下游响应时间。
假设你配置的限流阈值是每秒 5000 个请求,正常情况下 Gateway 在自己能力范围内完成限流判断,超出的返回 429。但如果来的是突发流量呢?比如大促期间瞬间涌入每秒 10 万个请求,这些请求会怎么走?

所有 10 万个请求都会先打到 Gateway 进程上,Gateway 要为每个请求做以下事情:
- 接收 TCP 连接、完成 TLS 握手
- 解析 HTTP 请求头和请求体
- 执行过滤器链(认证、日志、路由匹配)
- 调用 Redis 执行令牌桶 Lua 脚本
- 构造 429 响应并写回
就算限流判断只需要 2 毫秒,10 万个请求同时涌入,Gateway 的 Netty 线程池也扛不住这个并发量,更要命的是 JVM 还有 GC 停顿,高并发下 Young GC 可能频繁触发,每次停顿几十毫秒,请求堆积更严重。
结果就是:限流逻辑还没来得及执行,Gateway 自己先被流量压垮了。 连接队列溢出、响应超时、甚至 OOM。
这就像大门口安排了一个保安查票,但有 10 万人同时冲过来,保安还没来得及看票就被人群挤倒了。
Nginx 限流为啥这么猛
Nginx 用 C 语言写的,事件驱动 + 多进程模型,单机轻松扛住 10 万+ 并发连接,内存占用极低,一个 Nginx worker 进程处理一个请求的开销是微秒级的。
Nginx 的限流模块 ngx_http_limit_req_module 用的是漏桶算法,配置也简单:
http {
# 定义限流规则:按客户端 IP 限流,每秒 5000 个请求,共享内存区 10MB
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=5000r/s;
server {
location /api/ {
limit_req zone=api_limit burst=2000 nodelay;
# burst=2000 允许突发 2000 个请求
# nodelay 超出的请求立即返回 503,不排队
proxy_pass http://gateway_upstream;
}
}
}
Nginx 做限流判断的成本就是一次共享内存的读写操作,不涉及任何外部调用(不需要 Redis),不涉及 JVM 和 GC,纯 C 代码在内存里算一下计数器就完事了。
同样是 10 万 QPS 打过来:

Nginx 在自己的进程内完成限流判断,95% 的请求直接返回 503,根本不会到达 Gateway,Gateway 只需要处理被放行的 5000 QPS,完全在能力范围内。
这就是 Nginx 层限流的核心价值:保护 Gateway 本身不被流量打垮。
两层限流的职责完全不同
现在能看清楚这两层限流各自负责什么。
Nginx 层:粗粒度的全局流量管控
Nginx 限流的维度通常比较粗,按 IP、按接口路径、按整体 QPS 来限,它的目标不是精确的业务限流,而是在流量入口处把洪峰削掉,确保下游系统不会被瞬间打垮。
可以理解为一道物理防线:不管你是正常用户还是恶意请求,超过阈值就直接挡回去。
Gateway 层:细粒度的业务限流
Gateway 限流可以按用户 ID、按商户 ID、按接口 + 参数组合等业务维度来限。比如:
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
);
}
按用户 ID 限流,每个用户每秒最多 100 次请求,这种细粒度的限流需要解析请求内容、读取 Header 或 Token,只有到了应用层才能做。
Nginx 拿不到用户 ID,除非你在 Nginx 层做 JWT 解析,但那样 Nginx 就变成应用服务器了,失去了轻量级的优势。
实际生产架构长什么样
稍有规模的公司,限流通常不止两层,而是三层甚至四层:

每一层限流的位置越靠前,处理能力越强,限流粒度越粗。越靠后的层处理能力越弱,但限流粒度越细。这样每一层只需要处理上一层放行的流量,压力逐层递减。
如果只在 Gateway 层做限流,等于把所有流量压力都甩给了一个 JVM 进程。这就像一栋楼只在电梯门口装了门禁,楼门口不拦人。平时没问题,一到高峰期电梯口挤满了人,门禁系统自己先崩了。
Gateway 重启怎么办
JVM 应用发版要重启,重启期间这个实例是不处理请求的。如果限流只做在 Gateway 层,那么在 Gateway 滚动发布期间,可用实例减少,每个实例承受的压力增大,限流阈值可能需要动态调整。
Nginx 层的限流不受 Gateway 发版影响,不管后端有几个 Gateway 实例在跑,Nginx 始终按总体 QPS 控制入口流量。
还有 Gateway 的 Full GC 停顿,一次 Full GC 可能停顿几百毫秒到几秒,这期间 Gateway 无法处理任何请求,如果限流只依赖 Gateway,这段时间等于限流完全失效,Nginx 层不受影响,它在操作系统层面稳定运行。
说在最后
Spring Cloud Gateway 的限流解决的是业务层面的精细化流量管控,Nginx 的限流解决的是基础设施层面的流量保护,前者需要理解业务语义(用户、商户、接口),后者只需要简单粗暴地按 QPS 控数量。
限流的核心原则是在流量能够压垮下游之前,就在上游把它拦住。Gateway 能限流,但谁来保护 Gateway?
答案是 Nginx。
架构设计里,任何单点防护都不可靠。就像在 云栈社区 的技术讨论中常提到的一个观点:合理的系统总是在不同层级设置多重保障。把鸡蛋放在一个篮子里,迟早要出事。