一、为什么需要网关层限流
微服务架构中,每个业务服务独立部署并通过内部 RPC 或 HTTP 通信。当突发流量(秒杀、热点事件、恶意爬虫)到来时,单个服务的崩溃可能通过级联效应拖垮整条调用链。传统的方式是在每个业务服务里嵌入限流逻辑——但这样做会带来几个问题:
- 重复代码:每个服务都要配置限流阈值,维护成本高
- 缺少全局视角:服务 A 限了 100 QPS,服务 B 也限了 100 QPS,但整体入口流量可能是 500 QPS,无法统一管控
- 改配置要重启:限流阈值调整往往需要修改代码或配置后重新部署
在 API 网关层集中做限流降级,用一个统一的流量控制层保护下游所有微服务,是目前工业界的最佳实践。今天以 Spring Cloud Gateway + Sentinel 组合为例,从零搭建一个生产可用的网关限流方案。
二、技术选型为什么选 Sentinel
Java 生态的限流方案主要有三个:
- Guava RateLimiter:单机令牌桶,只能用在进程内,不支持分布式
- Redis + Lua 脚本:可实现分布式限流,但没有可视化控制台,规则调整需要开发介入
- Sentinel:阿里开源的流量治理组件,支持限流/降级/熔断/热点防护,提供控制台动态规则推送,Spring Cloud Gateway 有官方适配
Sentinel 的核心优势在于:① 实时监控面板可以看到每个接口的 QPS、线程数、RT;② 动态规则推送不改代码、不重启;③ 丰富的流控模式(直接限流、关联限流、链路限流)。
三、Spring Cloud Gateway 集成 Sentinel(实战配置)
3.1 引入依赖
<!-- Spring Cloud Gateway 核心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Sentinel 对 Spring Cloud Gateway 的适配 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel-gateway</artifactId>
<version>2023.0.1.0</version>
</dependency>
<!-- Sentinel 数据源(Nacos 做动态规则持久化,可选) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
3.2 网关路由配置(application.yml)
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
- id: product-service
uri: lb://product-service
predicates:
- Path=/api/product/**
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
sentinel:
transport:
dashboard: localhost:8800 # Sentinel 控制台地址
port: 8719 # 客户端与 dashboard 通信的端口
eager: true # 饥饿加载,不设置的话要第一次请求才注册
这里的关键是 sentinel.transport.dashboard,指向 Sentinel 控制台的地址。eager: true 确保网关启动后就注册到控制台,而不是等到第一条请求进来。
3.3 定义网关限流规则(编码方式)
@Configuration
public class GatewaySentinelConfig {
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
// 订单服务限流:每 5 秒最多 100 个请求,超过后直接拒绝
rules.add(new GatewayFlowRule("order-service")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
.setCount(100)
.setIntervalSec(5)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT));
// 商品服务限流:每 5 秒最多 200 个请求(商品浏览量大)
rules.add(new GatewayFlowRule("product-service")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_ROUTE_ID)
.setCount(200)
.setIntervalSec(5)
.setGrade(RuleConstant.FLOW_GRADE_QPS));
// API 分组限流:全部 API 加起来不超过 500 QPS
rules.add(new GatewayFlowRule("api-group")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(500)
.setIntervalSec(1)
.setGrade(RuleConstant.FLOW_GRADE_QPS));
GatewayRuleManager.loadRules(rules);
}
}
这里有一个容易踩的坑:RESOURCE_MODE_ROUTE_ID 必须是路由 id(即配置文件里 route 的 id),不是路径。很多人误传为路径去配置,导致规则永远不生效。规则持久化建议用 Nacos 数据源,这样控制台修改后自动同步,不需要重启网关。
四、配置 Sentinel 控制台
Sentinel 控制台是一个独立的 Spring Boot 应用,从 GitHub 下载 release JAR 后直接启动:
java -Dserver.port=8800 -jar sentinel-dashboard-1.8.8.jar
启动后访问 http://localhost:8800(默认账号 sentinel/sentinel),就可以看到已注册的网关应用。在「机器列表」能看到网关实例,在「流控规则」可以动态增删改限流阈值——修改即刻生效,不需要重启网关。
生产中建议把 Sentinel 控制台挂在内网,用反向代理做 HTTPS 和认证,避免暴露到公网。
五、降级与熔断配置
限流解决的是"流量太多怎么办",降级解决的是"下游服务挂了怎么办"。Sentinel 的降级规则通过 DegradeRule 配置:
@PostConstruct
public void initDegradeRules() {
Set<DegradeRule> rules = new HashSet<>();
DegradeRule rule = new DegradeRule("order-service")
.setGrade(RuleConstant.DEGRADE_GRADE_RT) // 按响应时间降级
.setCount(2000) // RT 超过 2000 ms
.setTimeWindow(60); // 熔断 60 秒后自动恢复
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
降级后的默认响应是 Sentinel 返回一个 429 页面,但这在全栈分离架构中体验不好。更好的做法是自定义降级处理器:
@Component
public class CustomBlockRequestHandler implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
Map<String, Object> result = new HashMap<>();
result.put("code", 429);
result.put("message", "系统繁忙,请稍后重试");
result.put("timestamp", System.currentTimeMillis());
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
}
然后在配置类中注册:
@Bean
public BlockRequestHandler blockRequestHandler() {
return new CustomBlockRequestHandler();
}
这样被限流或熔断时,前端收到的就是一个结构化的 JSON 响应,方便统一做错误处理。
六、生产踩坑记录
坑 1:Sentinel 规则不持久化,重启就丢
Sentinel 的规则默认存在内存里。网关重启后所有规则清零,流量直接淹到后端。解决方案:用 Nacos 或 Apollo 做数据源,Sentinel 在启动时从配置中心拉取规则,控制台的修改也写入配置中心。
坑 2:RESOURCE_MODE_ROUTE_ID 与路径的混淆
前面代码里已经强调了——路由 id 是你在 yml 里 routes: 下面写的 id: order-service 这个值,不是 /api/order/**。误写成路径会导致规则挂了但完全不生效,流量全部通过。
坑 3:控制台与网关不在同一时区
Sentinel 控制台的监控数据按时间聚合。如果控制台与网关的服务器时区不一致,看到的监控曲线会错位,排查问题时容易误判。务必将控制台 JVM 时区设为 -Duser.timezone=Asia/Shanghai。
坑 4:网关本身成为瓶颈
Sentinel 的限流判断是在网关线程中同步执行的。当规则数量庞大(几百条),每次请求都走一遍规则匹配,网关本身的 RT 会上升。建议规则数控制在 50 条以内,超过时用 API 分组(CUSTOM_API_NAME)聚合限流。
七、最佳实践总结
- 网关层限 80% 流量,服务层也保留兜底:网关限流主要防突发流量,但在网关被突破的情况下,业务服务自身也应有限流(如 Hystrix 或 Sentinel 注解方式),形成双层防护。
- 动态规则 > 静态编码:生产环境用 Nacos 数据源,规则可以通过控制台随时调整,不需要割接发布。
- 限流阈值先宽后严:上线第一天设一个较宽的阈值(如 1000 QPS),观察一周的业务峰值后逐步收紧到合理水位。
- 降级不等于摆烂:降级响应应该包含友好提示和预估恢复时间,而不是赤裸裸的 500 错误页。
- 监控告警闭环:Sentinel 控制台的监控只能看过去 5 分钟,长期趋势需要对接 Prometheus + Grafana。当限流触发率超过 5% 时触发告警,及时扩容或优化。
网关限流不是银弹,但它是微服务防护的第一道防线。Spring Cloud Gateway + Sentinel 的组合上手快、配置灵活、社区活跃,是中小团队落地流量治理的一个不错选择。