网关是微服务架构的流量入口,限流和熔断是保护下游服务的两道防线。这篇文章记录我在生产环境实际配置这两个功能的过程,不是文档翻译,只记录真正用到的部分和踩的坑。
环境说明
- Spring Cloud Gateway 4.x
- Spring Boot 3.x
- Resilience4j(熔断)
- Redis(限流存储)
限流:Redis 令牌桶
Gateway 内置了基于 Redis 的令牌桶限流,原理是每个路由维护一个令牌桶,请求消耗令牌,令牌按固定速率补充。
依赖
<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:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒补充 100 个令牌
redis-rate-limiter.burstCapacity: 200 # 桶容量(允许突发)
redis-rate-limiter.requestedTokens: 1 # 每个请求消耗 1 个令牌
key-resolver: "#{@ipKeyResolver}" # 按 IP 限流
Key Resolver:按 IP 还是按用户
内置的是按 IP 限流,实际业务通常需要按用户 ID:
@Configuration
public class RateLimiterConfig {
// 按 IP 限流(适合未登录接口)
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
);
}
// 按用户 ID 限流(适合已登录接口,从 Header 或 JWT 取)
@Bean
@Primary
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
}
限流触发后的响应
默认是 429 空响应,体验很差。自定义一下:
@Component
public class CustomRateLimitErrorHandler implements ErrorWebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof ResponseStatusException rse
&& rse.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders()
.add("Content-Type", "application/json;charset=UTF-8");
String body = """
{"code": 429, "message": "请求过于频繁,请稍后重试"}
""";
DataBuffer buffer = exchange.getResponse().bufferFactory()
.wrap(body.getBytes(StandardCharsets.UTF_8));
return exchange.getResponse().writeWith(Mono.just(buffer));
}
return Mono.error(ex);
}
}
熔断:Resilience4j
限流是防止流量过大把下游压垮,熔断是在下游已经出问题时快速失败,避免请求堆积拖垮整个链路。
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
配置熔断器
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: CircuitBreaker
args:
name: orderServiceCB
fallbackUri: forward:/fallback/order
resilience4j:
circuitbreaker:
instances:
orderServiceCB:
slidingWindowType: COUNT_BASED
slidingWindowSize: 20 # 统计最近 20 次请求
failureRateThreshold: 50 # 失败率超过 50% 开启熔断
waitDurationInOpenState: 30s # 熔断后等待 30s 再半开
permittedNumberOfCallsInHalfOpenState: 5 # 半开状态允许 5 次探测
minimumNumberOfCalls: 10 # 至少 10 次请求后才统计
Fallback 接口
@RestController
@RequestMapping("/fallback")
public class FallbackController {
@RequestMapping("/order")
public Mono<Map<String, Object>> orderFallback(ServerWebExchange exchange) {
// 记录熔断日志,方便排查
log.warn("Circuit breaker triggered for order-service, path: {}",
exchange.getRequest().getPath());
return Mono.just(Map.of(
"code", 503,
"message", "服务暂时不可用,请稍后重试",
"fallback", true
));
}
}
踩坑记录
坑 1:Redis 限流 Key 重复,所有接口共享一个桶
原因:key-resolver Bean 名字写错,没有正确注入,Gateway 使用了默认的 resolver,所有路由共享同一个 Key。
排查方法:Redis 里查看 request_rate_limiter.* 的 Key,正常情况下应该按 IP 或用户 ID 分开。
坑 2:熔断后 Fallback 接口返回 200,监控系统看不到异常
业务上 fallback 应该返回 503,而不是 200。改 fallback 接口的响应码:
@RequestMapping("/order")
public Mono<ResponseEntity<Map<String, Object>>> orderFallback() {
return Mono.just(ResponseEntity
.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(Map.of("code", 503, "message", "服务暂时不可用")));
}
坑 3:minimumNumberOfCalls 设置太小,新上线的服务频繁误熔断
流量低谷期,几次偶发超时就触发熔断。调大 minimumNumberOfCalls(建议生产环境设 20-50),同时调整 slidingWindowSize。
监控:看熔断状态
Resilience4j 暴露 Actuator 端点,可以看到熔断器当前状态:
# 查看所有熔断器状态
curl http://gateway:8080/actuator/circuitbreakers
# 输出示例
{
"circuitBreakers": {
"orderServiceCB": {
"state": "CLOSED", # CLOSED=正常, OPEN=熔断, HALF_OPEN=探测中
"failureRate": "5.0%",
"slowCallRate": "0.0%"
}
}
}
接入 Grafana 后可以做成实时看板,熔断事件触发时报警。
总结
| 功能 | 组件 | 核心配置 |
|---|---|---|
| 限流 | Redis RequestRateLimiter | replenishRate(速率)+ burstCapacity(突发) |
| 按用户限流 | 自定义 KeyResolver | 从 Header / JWT 取用户 ID |
| 熔断 | Resilience4j CircuitBreaker | failureRateThreshold + slidingWindowSize |
| 降级响应 | Fallback Controller | 返回 503,记录日志 |
这两个功能配合使用,基本能覆盖网关层面的稳定性保护。实际调参需要根据业务流量模式来,不存在通用的”最佳值”,建议先在压测环境跑通再上生产。