Gateway网关层如何实现全局限流?
在微服务架构中,Gateway 网关层是整个系统的统一入口,在这一层实现全局限流可以有效地保护后端微服务免受突发大流量的击垮。
在 Spring Cloud Gateway 中,实现分布式全局限流最主流、最成熟的方案是使用 Redis + Lua 脚本(基于令牌桶算法)。此外,也可以引入 Alibaba Sentinel 来实现。
以下是实现全局限流的完整方案和步骤:
方案一:基于 Spring Cloud Gateway 原生 + Redis(推荐)
Spring Cloud Gateway 官方内置了 RequestRateLimiter 过滤器,底层默认使用 Redis 和 Lua 脚本来实现分布式的令牌桶限流。
1. 引入依赖
因为 Gateway 是基于 WebFlux 的,所以需要引入响应式的 Redis 依赖:
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 响应式 Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- commons-pool2 (Redis 连接池需要) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
2. 编写 KeyResolver(限流维度)
网关需要知道按照什么规则进行限流(按 IP、按用户 ID、按接口路径,还是全局统一限流)。我们需要注入一个 KeyResolver Bean。
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
@Configuration
public class RateLimiterConfig {
/**
* 按 IP 限流 (最常用)
*/
@Bean
@Primary
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
/**
* 按用户 ID 限流 (需要从 Token 中解析出 userId)
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getHeaders().getFirst("userId")
);
}
/**
* 真正的“全局统一限流” (所有请求共享一个限流配额)
*/
@Bean
public KeyResolver globalKeyResolver() {
return exchange -> Mono.just("global_limit_key");
}
}
3. 配置 application.yml
配置 Redis 连接,并将 RequestRateLimiter 添加到全局过滤器(default-filters)中,这样它就会对所有路由生效。
spring:
redis:
host: 127.0.0.1
port: 6379
password: yourpassword
cloud:
gateway:
default-filters: # 全局默认过滤器,对所有路由生效
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 令牌桶的填充速率(每秒生成10个令牌)
redis-rate-limiter.burstCapacity: 20 # 令牌桶的容量(允许瞬间突发的最大请求数为20)
redis-rate-limiter.requestedTokens: 1 # 每次请求消耗的令牌数,默认为1
key-resolver: "#{@ipKeyResolver}" # 引用刚才注入的 Bean 名字
注:如果你只想对特定的路由限流,把上述 - name: RequestRateLimiter... 放到具体路由的 filters 下即可。
方案二:基于 Alibaba Sentinel 实现限流
如果你需要更强大的可视化控制台、动态规则修改、降级规则等,使用 Spring Cloud Alibaba Sentinel 是更好的选择。
1. 引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
2. yml 配置 Sentinel 控制台
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080 # Sentinel 控制台地址
3. 配置限流规则与自定义异常
通过 Java 代码配置网关限流规则,并自定义限流后返回的 JSON 格式(否则默认返回一段英文文本)。
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Set;
@Configuration
public class SentinelGatewayConfig {
@PostConstruct
public void init() {
// 1. 加载网关限流规则 (也可以在 Sentinel 控制台动态配置,这里是代码硬编码示例)
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("your_route_id") // 路由ID 或 自定义API分组
.setCount(10) // 阈值 QPS = 10
.setIntervalSec(1)); // 统计时间窗口,单位是秒
GatewayRuleManager.loadRules(rules);
// 2. 自定义限流后的异常返回
BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) ->
ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"code\":429, \"message\":\"请求过于频繁,请稍后再试!\"}");
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
方案三:自定义 GlobalFilter 实现简易限流
如果不使用 Redis 也不使用 Sentinel,仅仅想在单机网关节点上做简易限流,可以使用 Guava 的 RateLimiter 结合自定义 GlobalFilter。(不推荐用于生产环境的多节点网关集群)
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomRateLimitFilter implements GlobalFilter, Ordered {
// 每秒生成 50 个令牌 (单机版)
private static final RateLimiter RATE_LIMITER = RateLimiter.create(50.0);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (!RATE_LIMITER.tryAcquire()) {
// 获取不到令牌,返回 429 状态码
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100; // 优先级尽量高
}
}
总结与最佳实践建议
- 技术选型:
- Spring Cloud Gateway 原生 Redis:适合绝大多数微服务项目,轻量、稳定,无需额外部署庞大的组件。
- Sentinel:适合业务复杂、有专门运维团队、需要可视化监控和动态规则调整的中大型项目。
- 限流维度:绝对的“全局限流”(所有接口加起来算一个配额)往往没有实际意义,通常说的全局限流是指:在全局过滤器中配置规则,按请求的 IP 或者 UserID 进行统一下发限流。
- 用户体验:被限流时(HTTP 状态码通常是
429 Too Many Requests),一定要自定义异常处理逻辑,返回给前端统一的 JSON 格式结构(如{"code": 429, "msg": "系统繁忙"}),避免直接给前端返回晦涩的错误堆栈。对于原生的 Redis 限流,可以通过实现ErrorWebExceptionHandler来拦截处理。