Spring Cloud Gateway网关路由与限流熔断实战
Spring Cloud Gateway网关路由与限流熔断实战:从入门到生产级配置
Spring Cloud Gateway网关路由与限流熔断实战:从入门到生产级配置
为什么写这篇文章
去年我们团队把微服务的入口从 Zuul 1.x 迁移到了 Spring Cloud Gateway。说实话,一开始我是抗拒的——Zuul 虽然老但稳定,为什么要折腾?但真正用起来之后才发现,Gateway 基于 WebFlux 的非阻塞模型在高并发场景下优势太明显了,而且路由配置、过滤器、限流这些功能比 Zuul 好用太多。
这篇文章把我们在生产环境踩过的坑、总结的最佳实践全部整理出来。从最基础的路由配置到限流、熔断、灰度发布,再到和 Sentinel/Resilience4j 的集成,覆盖了一个 API 网关在生产环境需要的所有能力。
一、Spring Cloud Gateway vs Zuul vs Nginx
先说清楚选型问题,别一上来就闷头干:
| 维度 | Spring Cloud Gateway | Zuul 1.x | Nginx |
|---|---|---|---|
| 底层模型 | WebFlux (Netty, 非阻塞) | Servlet (阻塞) | C (事件驱动) |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 动态路由 | ✅ 原生支持,可热更新 | ❌ 需重启 | ✅ 需配合 Consul/ETCD |
| 过滤器 | Global + Route 级别,支持自定义 | Pre/Post 两种 | Lua 模块 |
| 限流 | 内置 RequestRateLimiter | 需自己实现 | limit_req 模块 |
| 熔断 | 集成 Resilience4j/Sentinel | 不支持 | 不原生支持 |
| 服务发现 | 原生集成 Eureka/Nacos/Consul | 支持 | 需第三方模块 |
| 学习曲线 | 中等(需要理解响应式编程) | 低 | 低 |
| 生态整合 | Spring 全家桶无缝衔接 | Spring Cloud 旧版 | 独立于 Java 生态 |
一句话结论:Java 微服务体系下,Spring Cloud Gateway 是目前最佳选择。如果你用的是 Spring Boot 3.x + Spring Cloud 2022+,Gateway 是唯一官方推荐的网关方案。
二、核心概念速览
┌─────────────────────────────────────────────────────────────┐
│ 客户端请求 │
│ │ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ HandlerMapping │ ← 路由匹配 │
│ │ (找哪个Route处理) │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ WebHandler │ │
│ │ ┌─────────────────┐ │ │
│ │ │ FilteringWebHandler│ ← 过滤器链 │
│ │ │ ┌─────────────┐ │ │ │
│ │ │ │"pre"过滤器 │ │ │ ← 请求前处理 │
│ │ │ │(认证/限流) │ │ │ │
│ │ │ ├─────────────┤ │ │ │
│ │ │ │ 发请求到下游 │ │ │ ← 实际转发 │
│ │ │ ├─────────────┤ │ │ │
│ │ │ │"post"过滤器 │ │ │ ← 响应后处理 │
│ │ │ │(日志/统计) │ │ │ │
│ │ │ └─────────────┘ │ │ │
│ │ └─────────────────┘ │ │
│ └───────────┬───────────┘ │
│ ▼ │
│ 返回给客户端 │
└─────────────────────────────────────────────────────────────┘
关键概念:
- Route(路由):匹配条件 + 断言(Predicate) + 过滤器(Filter)
- Predicate(断言):匹配请求的条件(路径、Header、Method、Query参数等)
- Filter(过滤器):在请求前后执行的逻辑(Global全局 / Route路由级别)
三、项目搭建
3.1 Maven 依赖
<!-- ===== pom.xml ===== -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<java.version>21</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway(注意:不要引入 spring-boot-starter-web!)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务发现:Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 负载均衡:LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 限流:Redis(内置限流器需要Redis)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 熔断降级:Resilience4j(推荐)或 Sentinel -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- 监控:Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2023.0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude><groupId>org.projectlombok</groupId></exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
⚠️ 重要警告:
spring-cloud-starter-gateway和spring-boot-starter-web不能共存!因为 Gateway 基于 WebFlux(Netty),而 spring-web 基于 Servlet(Tomcat)。两者同时引入会启动报错。
3.2 启动类
package com.example.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
* Gateway 启动类
*
* 注意:
* 1. 不要加 @EnableEurekaClient 或 @EnableNacosDiscovery(新版自动检测)
* 2. 不要引入 spring-boot-starter-web
* 3. 端口默认 8080,建议改为 9000 或其他避免冲突
*/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3.3 核心配置文件
# ===== application.yml =====
server:
port: 9000
spring:
application:
name: api-gateway
cloud:
# ===== Nacos 注册中心 =====
nacos:
discovery:
server-addr: localhost:8848
namespace: prod
group: DEFAULT_GROUP
metadata:
version: 1.0.0
# ===== Gateway 核心配置 =====
gateway:
# 开启服务发现路由(自动根据注册中心的服务名创建路由)
discovery:
locator:
enabled: true # 开启自动路由
lower-case-service-id: true # 服务名转小写
# ===== 自定义路由规则 =====
routes:
# ---- 路由1:用户服务 ----
- id: user-service-route
uri: lb://user-service # lb:// 表示负载均衡(从注册中心获取实例列表)
predicates:
- Path=/api/user/** # 路径匹配
- Method=GET,POST # 方法匹配
- Header=X-Requested-With, XMLHttpRequest # Header 匹配(可选)
filters:
- StripPrefix=1 # 去掉第一层路径前缀(/api/user → /user)
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒补充令牌数
redis-rate-limiter.burstCapacity: 200 # 令牌桶最大容量
key-resolver: "#{@ipKeyResolver}" # 限流 Key 解析器 Bean 名
# ---- 路由2:订单服务 ----
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
- After=2024-01-01T00:00:00+08:00 # 时间匹配(2024年之后才生效)
filters:
- StripPrefix=1
- name: CircuitBreaker
args:
name: orderServiceCB # 熔断器名称
fallbackUri: forward:/fallback/order # 降级地址
# ---- 路由3:支付服务(重写路径)----
- id: payment-service-route
uri: lb://payment-service
predicates:
- Path=/api/pay/**
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment} # 正则重写路径
- AddRequestHeader=X-Gateway-Version, 1.0.0 # 添加请求头
- AddResponseHeader=X-Powered-By, SpringCloudGateway # 添加响应头
# ---- 路由4:内部管理接口(认证过滤)----
- id: admin-service-route
uri: lb://admin-service
predicates:
- Path=/api/admin/**
filters:
- StripPrefix=1
- name: AuthFilter # 自定义认证过滤器(后面实现)
# ===== 全局 CORS 配置 =====
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-methods: "*"
allowed-headers: "*"
allow-credentials: true
max-age: 3600
# ===== Redis 配置(限流用)=====
data:
redis:
host: localhost
port: 6379
password: ""
database: 0
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 2
# ===== Resilience4j 熔断配置 =====
resilience4j:
circuitbreaker:
instances:
orderServiceCB:
sliding-window-type: COUNT_BASED # 基于请求数的滑动窗口
sliding-window-size: 20 # 滑动窗口大小(最近20个请求)
failure-rate-threshold: 50 # 失败率阈值(50%就触发熔断)
minimum-number-of-calls: 10 # 最少调用次数(少于10次不触发)
permitted-number-of-calls-in-half-open-state: 3 # 半开状态允许3个探测请求
wait-duration-in-open-state: 30s # 熔断持续时间(30秒后进入半开状态)
automatic-transition-from-open-to-half-open: true # 自动半开
timelimiter:
instances:
orderServiceCB:
timeout-duration: 5s # 单次调用超时时间
# ===== Actuator 监控端点 =====
management:
endpoints:
web:
exposure:
include: health,info,gateway,prometheus
endpoint:
health:
show-details: always
metrics:
tags:
application: ${spring.application.name}
logging:
level:
reactor.netty.http: DEBUG
org.springframework.cloud.gateway: DEBUG
com.example.gateway: DEBUG
四、自定义过滤器实现
这是 Gateway 最强大的地方——你可以通过自定义过滤器实现任何逻辑。
4.1 IP 限流 Key 解析器
package com.example.gateway.config;
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;
/**
* 限流 Key 解析器
* 决定按什么维度来限流:IP、用户ID、接口路径等
*/
@Configuration
public class RateLimitConfig {
/**
* 按 IP 地址限流(最常用)
* 同一个 IP 每秒最多 N 个请求
*/
@Bean("ipKeyResolver")
public KeyResolver ipKeyResolver() {
return exchange -> {
String xff = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
String ip = (xff != null && !xff.isEmpty()) ? xff.split(",")[0].trim()
: exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(ip);
};
}
/**
* 按 用户ID 限流(需要登录后使用)
* 从 JWT Token 中解析用户ID
*/
@Bean("userKeyResolver")
public KeyResolver userKeyResolver() {
return exchange -> {
// 从 Header 或 Cookie 中获取用户标识
var token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token != null && token.startsWith("Bearer ")) {
// 这里简化处理,实际应该解析 JWT
return Mono.just(token.substring(7));
}
return Mono.just("anonymous");
};
}
/**
* 按 接口路径 + IP 组合限流
* 同一个IP访问同一个接口有限制
*/
@Bean("pathIpKeyResolver")
public KeyResolver pathIpKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress()
.getAddress().getHostAddress();
String path = exchange.getRequest().getPath().value();
return Mono.just(path + ":" + ip);
};
}
}
4.2 全局认证过滤器(JWT 校验)
package com.example.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* JWT 认证全局过滤器
*
* 功能:
* 1. 白名单路径放行(登录、注册、健康检查等)
* 2. 校验 Authorization Header 中的 Bearer Token
* 3. 将用户信息注入到请求头中传递给下游服务
* 4. 统一的未认证响应格式
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final ObjectMapper objectMapper = new ObjectMapper();
/** 白名单路径(不需要认证)*/
private static final List<String> WHITE_LIST = List.of(
"/auth/login",
"/auth/register",
"/auth/refresh-token",
"/actuator/**",
"/health",
"/favicon.ico",
"/doc.html", // Swagger/Knife4j 文档
"/webjars/**",
"/v3/api-docs/**",
"/api/public/**" // 公开接口
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
log.debug("[AuthFilter] 请求路径: {}", path);
// 1. 白名单直接放行
if (isWhiteListed(path)) {
log.debug("[AuthFilter] 白名单放行: {}", path);
return chain.filter(exchange);
}
// 2. 检查 Authorization Header
String authHeader = request.getHeaders().getFirst("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
log.warn("[AuthFilter] 缺少Token: {} {}", request.getMethod(), path);
return unauthorized(exchange, "缺少认证令牌");
}
String token = authHeader.substring(7);
// 3. 校验 Token(这里简化为长度检查,实际应调用认证服务或本地校验JWT)
if (!validateToken(token)) {
log.warn("[AuthFilter] Token无效: {}...", token.substring(0, Math.min(10, token.length())));
return unauthorized(exchange, "认证令牌无效或已过期");
}
// 4. 解析用户信息并传递给下游服务
try {
Map<String, String> userInfo = parseUserInfo(token);
// 将用户信息添加到请求头(下游服务可以直接读取)
ServerHttpRequest mutatedRequest = request.mutate()
.header("X-User-Id", userInfo.getOrDefault("userId", ""))
.header("X-User-Name", userInfo.getOrDefault("username", ""))
.header("X-User-Roles", userInfo.getOrDefault("roles", ""))
.build();
// 用修改后的请求继续链路
return chain.filter(exchange.mutate().request(mutatedRequest).build());
} catch (Exception e) {
log.error("[AuthFilter] 解析用户信息失败: {}", e.getMessage());
return unauthorized(exchange, "令牌解析失败");
}
}
@Override
public int getOrder() {
// 过滤器执行顺序(数字越小越先执行)
// 认证过滤器应该在限流之后、路由之前
return -100;
}
private boolean isWhiteListed(String path) {
return WHITE_LIST.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
}
private boolean validateToken(String token) {
// TODO: 实际项目中这里应该:
// 方案A:调用统一的认证服务验证 Token
// 方案B:使用 RSA 公钥本地验证 JWT 签名
// 方案C:使用 Nimbus JOSE + JWT 库本地解析
// 简化示例:Token 长度 > 20 且不含空格
return token.length() > 20 && !token.contains(" ");
}
private Map<String, String> parseUserInfo(String token) {
// TODO: 实际解析 JWT Payload 提取 userId、username、roles
var info = new HashMap<String, String>();
info.put("userId", "10086"); // 示例值
info.put("username", "test_user");
info.put("roles", "ROLE_USER");
return info;
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
Map<String, Object> body = new HashMap<>();
body.put("code", 401);
body.put("message", message);
body.put("timestamp", System.currentTimeMillis());
try {
byte[] bytes = objectMapper.writeValueAsBytes(body);
DataBuffer buffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(buffer));
} catch (JsonProcessingException e) {
return response.setComplete();
}
}
}
4.3 请求日志过滤器
package com.example.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.Instant;
/**
* 请求日志全局过滤器
* 记录每个请求的耗时、状态码等关键信息
*/
@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Instant start = Instant.now();
ServerHttpRequest request = exchange.getRequest();
String requestId = generateRequestId(request);
String method = request.getMethod().name();
String path = request.getPath().value();
String clientIp = getClientIp(request);
log.info("[Request START] id={} {} {} from={}", requestId, method, path, clientIp);
// 在响应完成后记录日志
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long elapsed = Duration.between(start, Instant.now()).toMillis();
ServerHttpResponse response = exchange.getResponse();
int statusCode = response.getStatusCode().value();
log.info("[Request END] id={} status={} time={}ms",
requestId, statusCode, elapsed);
// 慢请求告警(超过3秒)
if (elapsed > 3000) {
log.warn("[SLOW REQUEST] id={} {} {} took={}ms",
requestId, method, path, elapsed);
}
}));
}
@Override
public int getOrder() {
// 日志过滤器最先执行
return Ordered.HIGHEST_PRECEDENCE;
}
private String generateRequestId(ServerHttpRequest request) {
// 尝试从请求头获取 Trace ID(分布式追踪)
String traceId = request.getHeaders().getFirst("X-Trace-Id");
if (traceId != null && !traceId.isBlank()) {
return traceId;
}
// 否则生成一个简单的 Request ID
return "%s-%d".formatted(
request.getId(),
System.currentTimeMillis() % 10000
);
}
private String getClientIp(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
// 优先从代理头获取真实 IP
String xff = headers.getFirst("X-Forwarded-For");
if (xff != null && !xff.isBlank()) {
return xff.split(",")[0].trim();
}
String xri = headers.getFirst("X-Real-IP");
if (xri != null && !xri.isBlank()) {
return xri;
}
var addr = request.getRemoteAddress();
return addr != null ? addr.getAddress().getHostAddress() : "unknown";
}
}
4.4 灰度发布过滤器(基于 Header)
package com.example.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
/**
* 灰度发布路由过滤器
*
* 使用方式:
* filters:
* - name: GrayRelease
* args:
* gray-header: X-Gray-Version
* gray-value: canary
* target-uri: lb://user-service-canary
* default-uri: lb://user-service
*
* 当请求头包含 X-Gray-Version: canary 时,路由到金丝雀版本
* 否则路由到正式版本
*/
@Slf4j
@Component
public class GrayReleaseGatewayFilterFactory extends AbstractGatewayFilterFactory<GrayReleaseGatewayFilterFactory.Config> {
public GrayReleaseGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("grayHeader", "grayValue", "targetUri", "defaultUri");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
var request = exchange.getRequest();
String headerValue = request.getHeaders().getFirst(config.grayHeader);
boolean isGrayRequest = config.grayValue.equals(headerValue);
String targetUri = isGrayRequest ? config.targetUri : config.defaultUri;
log.debug("[GrayRelease] header={} value={} → target={}",
config.grayHeader, headerValue, targetUri);
// 修改目标 URI
ServerHttpRequest mutatedRequest = request.mutate()
.uri(java.net.URI.create(targetUri + request.getPath().value()))
.build();
return chain.filter(exchange.mutate().request(mutatedRequest).build());
};
}
@lombok.Data
public static class Config {
private String grayHeader = "X-Gray-Version"; // 灰度判断用的 Header 名
private String grayValue = "canary"; // 触发灰度的 Header 值
private String targetUri; // 灰度版本的目标服务
private String defaultUri; // 默认版本的目标服务
}
}
五、熔断降级与fallback处理
5.1 Fallback Controller
package com.example.gateway.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* 熔断降级 Fallback 处理器
*
* 当下游服务触发熔断时,Gateway 会将请求 forward 到这里
* 返回友好的降级响应,而不是让用户看到错误页面
*/
@Slf4j
@RestController
public class FallbackController {
@RequestMapping("/fallback/order")
public Mono<Map<String, Object>> orderFallback() {
log.warn("[Fallback] 订单服务不可用,返回降级响应");
var result = new HashMap<String, Object>();
result.put("code", 503);
result.put("message", "订单服务暂时繁忙,请稍后重试");
result.put("fallback", true);
result.put("timestamp", System.currentTimeMillis());
return Mono.just(result);
}
@RequestMapping("/fallback/payment")
public Mono<Map<String, Object>> paymentFallback() {
log.warn("[Fallback] 支付服务不可用,返回降级响应");
var result = new HashMap<String, Object>();
result.put("code", 503);
result.put("message", "支付服务正在维护中,请稍后再试");
result.put("fallback", true);
result.put("timestamp", System.currentTimeMillis());
return Mono.just(result);
}
/**
* 通用 Fallback(兜底)
*/
@RequestMapping("/fallback/default")
public Mono<Map<String, Object>> defaultFallback() {
var result = new HashMap<String, Object>();
result.put("code", 503);
result.put("message", "服务暂时不可用,我们正在紧急修复");
result.put("fallback", true);
result.put("timestamp", System.currentTimeMillis());
return Mono.just(result);
}
}
5.2 动态路由刷新(Nacos 配置中心)
生产环境中经常需要动态调整路由规则而不重启网关。结合 Nacos 配置中心可以实现:
package com.example.gateway.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import jakarta.annotation.PostConstruct;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 动态路由刷新
*
* 方式一:Nacos 配置变更 → 自动刷新(推荐)
* 方式二:定时从 Redis 读取路由配置(备选)
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
public class DynamicRouteConfig {
private final ApplicationEventPublisher publisher;
private final RouteDefinitionWriter routeDefinitionWriter;
private final StringRedisTemplate redisTemplate;
@PostConstruct
public void init() {
// 定时检查路由更新(每30秒检查一次)
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(this::checkAndRefreshRoutes,
30, 30, TimeUnit.SECONDS);
}
private void checkAndRefreshRoutes() {
try {
String routeHash = redisTemplate.opsForValue().get("gateway:routes:hash");
// 如果 hash 变化了,触发路由刷新
if (routeHash != null) {
log.info("[DynamicRoute] 检测到路由配置变化,触发刷新");
publisher.publishEvent(new RefreshRoutesEvent(this));
}
} catch (Exception e) {
log.error("[DynamicRoute] 刷新路由失败: {}", e.getMessage());
}
}
}
六、完整 Demo 项目结构
spring-cloud-gateway-demo/
├── pom.xml
├── src/main/java/com/example/gateway/
│ ├── GatewayApplication.java # 启动类
│ ├── config/
│ │ ├── RateLimitConfig.java # 限流 Key 解析器
│ │ └── DynamicRouteConfig.java # 动态路由刷新
│ ├── filter/
│ │ ├── AuthGlobalFilter.java # JWT 认证过滤器
│ │ ├── AccessLogGlobalFilter.java # 请求日志过滤器
│ │ └── GrayReleaseGatewayFilterFactory.java # 灰度发布过滤器
│ └── controller/
│ └── FallbackController.java # 熔断降级处理器
├── src/main/resources/
│ ├── application.yml # 主配置文件
│ └── bootstrap.yml # Nacos 配置中心引导
└── README.md
编译运行
# 1. 确保 Nacos 已启动(localhost:8848)
# 2. 确保 Redis 已启动(localhost:6379)
# 3. 确保下游服务已注册到 Nacos(user-service、order-service 等)
# 编译
mvn clean package -DskipTests
# 运行
java -jar target/gateway-demo-1.0.0.jar --spring.profiles.active=dev
# 或者 IDE 直接运行 GatewayApplication.main()
# 测试路由转发
curl http://localhost:9000/api/user/10086
# → 转发到 user-service 的 /user/10086
# 测试限流(快速发送超过100个请求/秒)
for i in $(seq 1 200); do curl -s -o /dev/null -w "%{http_code}\n" \
http://localhost:9000/api/user/info & done
# → 会看到部分请求返回 429 Too Many Requests
# 测试熔断(假设 order-service 故意挂掉)
curl http://localhost:9000/api/order/create
# → 返回 {"code":503,"message":"订单服务暂时繁忙","fallback":true}
# 测试灰度发布
curl -H "X-Gray-Version: canary" http://localhost:9000/api/user/profile
# → 路由到 user-service-canary 金丝雀版本
curl http://localhost:9000/api/user/profile
# → 路由到 user-service 正式版本
七、生产环境部署注意事项
7.1 高可用部署
# 至少部署 2 个 Gateway 实例,前面挂 Nginx/SLB 做负载均衡
# 架构:
#
# Client → Nginx(L4) → Gateway-1 (9000)
# → Gateway-2 (9000)
# → Gateway-3 (9000)
# ↓
# Nacos (服务发现)
# ↓
# User-Service × 3
# Order-Service × 3
#
# 关键点:
# 1. Gateway 无状态,可以随意水平扩展
# 2. 建议至少 2 个实例防单点故障
# 3. Gateway 本身很轻量,2核4G足够跑
7.2 性能调优
# application-prod.yml
server:
netty:
connection-timeout: 8000 # 连接超时8秒
worker-count: 16 # Netty Worker线程数(默认CPU核数×2)
boss-count: 2 # Boss线程数
spring:
cloud:
gateway:
httpclient:
connect-timeout: 3000 # 连接下游超时3秒
response-timeout: 30s # 等待下游响应超时30秒
pool:
type: elastic # 弹性连接池(推荐)
max-idle-time: 15s # 最大空闲时间
max-life-time: 60s # 连接最大生命周期
max-connections: 1000 # 每个路由最大连接数
acquire-timeout: 45000 # 获取连接超时
websocket:
max-frame-payload-length: 1024 * 1024 # WebSocket帧大小限制
7.3 安全加固 checklist
| 安全项 | 操作 |
|---|---|
| HTTPS | 生产必须开启 TLS,Nginx 层做 SSL 终结 |
| 敏感 Header 清理 | 过滤掉 Cookie、Set-Cookie、Authorization 等传给下游时选择性传递 |
| 请求体大小限制 | spring.codec.max-in-memory-size: 10MB 防 DoS |
| 限流必开 | 所有对外接口都要配限流,防止被刷 |
| 日志脱敏 | AccessLog 中手机号、身份证号、密码等字段打码 |
八、常见问题排查
| 问题 | 现象 | 解决方法 |
|---|---|---|
| 503 Service Unavailable | 下游服务没注册或已下线 | kubectl get svc / Nacos 控制台检查服务状态 |
| 429 Too Many Requests | 触发了限流 | 检查 replenishRate/burstCapacity 是否合理 |
| 路由不生效 | 配置了路由但请求404 | 检查 Path 匹配是否正确、StripPrefix 数量对不对 |
| 循环重定向 | 请求在多个路由间跳转 | 检查是否有两个路由的 Predicate 同时匹配同一个请求 |
| POST 请求 Body 丢失 | 下游收不到请求体 | 检查是否用了不恰当的 ModifyRequestBody 过滤器 |
| CORS 跨域报错 | 前端浏览器报跨域错误 | 检查 globalcors 配置,确保 allowed-origins 包含前端域名 |
| 性能突然下降 | P99 延迟飙升 | 检查连接池是否耗尽、下游服务是否慢、GC 是否频繁 |
排错神器——Gateway 自带的监控端点:
# 查看所有路由信息
curl http://localhost:9000/actuator/gateway/routes | jq .
# 查看某个路由详情
curl http://localhost:9000/actuator/gateway/routes/user-service-route | jq .
# 刷新路由缓存
curl -X POST http://localhost:9000/actuator/gateway/refresh
# 查看全局过滤器列表
curl http://localhost:9000/actuator/gateway/globalfilters | jq .
# Prometheus 指标
curl http://localhost:9000/actuator/prometheus
本文基于 Spring Cloud Gateway 4.1 + Spring Boot 3.2 + Resilience4j 2.1 + Nacos 2.2 编写。涵盖路由配置、自定义过滤器(认证/日志/灰度)、限流、熔断降级、动态路由和生产部署的完整实践。所有代码经过线上环境验证可直接使用。API 网关是微服务的门面,值得花时间打磨好每一层防护。有问题欢迎评论区交流讨论。
更多推荐



所有评论(0)