Gateway 详解:API 网关
Spring Cloud Gateway 是 Spring 官方基于 WebFlux + Netty 框架开发的 API 网关,使用非阻塞异步 I/O 模型,是替代 Zuul 的下一代网关方案。Gateway 本质上是一个反向代理,所有外部请求先经过它,再由它路由到后端微服务。它的核心价值在于将鉴权、限流、日志、熔断、灰度发布等通用能力统一收敛到网关层,让下游服务只关注业务逻辑。
一、为什么需要网关?
在微服务架构中,网关是所有外部请求的统一入口。没有网关时:
前端 → order-service (鉴权、限流、日志、熔断、灰度)
前端 → user-service (鉴权、限流、日志、熔断、灰度)
前端 → product-service (鉴权、限流、日志、熔断、灰度)
每个服务都要重复实现这些通用能力,代码严重重复,升级维护极其困难有了网关后:
前端 → Gateway (鉴权、限流、日志、熔断、灰度、路由) → order-service (纯业务逻辑)
→ user-service (纯业务逻辑)
→ product-service (纯业务逻辑)网关的核心价值:
| 序号 | 价值 | 说明 | 组件 |
|---|---|---|---|
| 1 | 解耦前端 | 后端服务拆分对前端透明,网关做聚合和适配 | Route + StripPrefix |
| 2 | 统一入口 | 外部请求统一经过网关,统一管理 | Gateway 本身 |
| 3 | 路由转发 | 动态路由、灰度发布、负载均衡 | Route + Predicate + LoadBalancer |
| 4 | 协议转换 | HTTP → gRPC,REST → WebSocket | NettyRoutingFilter + WebSocket |
| 5 | 可观测性 | 统一日志、链路追踪、指标采集 | GlobalFilter + Sleuth + Micrometer |
| 6 | 安全防护 | 鉴权、防篡改、防重放、SQL 注入防护、XSS 防护 | GlobalFilter(自定义鉴权) + SecurityFilter |
| 7 | 流量管控 | 限流、熔断、降级、超时控制 | RequestRateLimiter + CircuitBreaker + Retry |
二、Gateway 架构原理
2.1 基于 WebFlux 的非阻塞架构
Gateway 选择 WebFlux + Netty 而非传统的 Servlet 容器,是因为网关的本质是 I/O 密集型应用——它不执行复杂业务逻辑,主要工作就是接收请求 → 转发请求 → 返回响应。对于 I/O 密集型场景,非阻塞异步模型比阻塞同步模型在资源利用率上有数量级的优势。
Servlet 容器(Tomcat)阻塞模型:
每个请求占用一个线程,200 个线程 = 最多 200 个并发请求
线程阻塞在 I/O 等待上,CPU 大量空闲
Netty 非阻塞模型(Gateway):
少数几个 EventLoop 线程处理所有请求
请求不阻塞,I/O 事件到达时回调处理
少量线程即可支撑上万并发连接核心线程模型:
┌─────────────────────────┐
│ Boss EventLoop │ ← 1 个线程,负责接收 TCP 连接
│ (NioEventLoopGroup) │
└────────────┬────────────┘
│ 将连接注册到 Worker
┌────────────▼────────────┐
│ Worker EventLoops │ ← CPU 核数 × 2 个线程
│ (NioEventLoopGroup) │ 负责读写、编解码、业务处理
└────────────┬────────────┘
│ 事件驱动
┌────────────▼────────────┐
│ Reactor Netty │
│ (HttpServer/HttClient) │
└─────────────────────────┘2.2 请求处理全流程
Gateway 的请求处理流程可以拆解为以下步骤:
客户端请求
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 1. Netty Server 接收 HTTP 请求 │
│ - 解析 HTTP 请求行、请求头、请求体 │
│ - 构造 ServerHttpRequest / ServerHttpResponse │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 2. RoutePredicateHandlerMapping 路由匹配 │
│ - 遍历所有 Route,依次用 Predicate 匹配请求 │
│ - 匹配到第一个符合条件的 Route 后停止(短路匹配) │
│ - 如果所有 Route 都不匹配,返回 404 │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 3. FilteringWebHandler 执行过滤器链 │
│ - 加载该 Route 的全部 GatewayFilter(前置过滤) │
│ - 加载所有 GlobalFilter(前置过滤) │
│ - 按 Order 排序后依次执行 │
│ - 过滤器链执行完成后,发起对下游服务的请求 │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 4. NettyRoutingFilter 转发请求 │
│ - 根据 Route 的 uri 构造目标地址 │
│ - lb://order-service → 从注册中心获取实例列表 → 负载均衡选一个 │
│ - 通过 Netty HttpClient 发起非阻塞请求 │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 5. 接收下游服务响应 │
│ - 收到响应后,过滤器链中的后置过滤器执行(倒序) │
│ - 如:添加响应头、记录日志、收集指标 │
└──────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ 6. 将响应写回客户端 │
│ - Netty 将响应数据写回客户端连接 │
└──────────────────────────────────────────────────────────────┘三、三大核心组件深度解析
┌──────────────────────────────────────────────────────────────┐
│ Gateway 请求处理流程 │
│ │
│ 请求 ──→ [Predicate 断言] ──→ [Filter 过滤器链] ──→ [Route 路由] ──→ 目标服务
│ 匹配请求条件 前置/后置处理 转发目标 │
│ │
│ 示例: │
│ GET /api/order/123 │
│ → Path Predicate: /api/order/** 匹配成功 │
│ → Auth Filter: 校验 JWT Token │
│ → RateLimit Filter: 检查 QPS 是否超限 │
│ → Route: lb://order-service → 转发到订单服务 │
│ → Log Filter: 记录请求日志 │
│ → 响应返回给客户端 │
└──────────────────────────────────────────────────────────────┘3.1 Route(路由)
路由是 Gateway 的基本构建块,包含三个要素:
| 要素 | 说明 |
|---|---|
| id | 路由唯一标识,用于日志、监控、管理 |
| uri | 转发目标地址,支持 lb://(注册中心)、http://(直连)、forward://(内部转发) |
| predicates | 断言集合,所有断言都满足时路由才生效 |
| filters | 过滤器集合,对该路由的请求进行处理 |
| order | 路由优先级,值越小越优先匹配(默认 0) |
| metadata | 自定义元数据,可用于自定义逻辑 |
uri 的三种形式:
spring:
cloud:
gateway:
routes:
# 1. lb:// 从注册中心获取实例(最常用)
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
# 2. http:// 直连指定地址(测试、第三方服务)
- id: external-api
uri: https://third-party-api.example.com
predicates:
- Path=/api/external/**
# 3. forward:// 内部转发(重定向到网关自身的接口)
- id: fallback
uri: forward:/fallback
predicates:
- Path=/api/**路由匹配规则:
Gateway 的路由匹配采用短路优先策略——遍历所有路由定义,返回第一个匹配成功的路由。因此路由的定义顺序很重要,具体规则应该放在前面,通用规则放在后面。
路由匹配伪代码:
for (Route route : routes) {
if (route.getPredicates().stream().allMatch(p -> p.test(request))) {
return route; // 匹配成功,短路返回
}
}
return null; // 所有路由都不匹配,返回 4043.2 Predicate(断言)详解
断言是 Java 8 的 Predicate<ServerWebExchange> 函数式接口,用于判断请求是否满足路由条件。Gateway 内置了 12 种断言工厂。
3.2.1 路径断言:Path
最常用的断言,支持 Ant 风格路径匹配。
predicates:
- Path=/api/order/** # 匹配 /api/order/ 下的所有路径
- Path=/api/{service}/** # 路径变量 {service} 可被后续 Filter 引用Ant 风格通配符:
| 通配符 | 含义 | 示例 |
|---|---|---|
? | 匹配单个字符 | /api/order/? 匹配 /api/order/1 |
* | 匹配零个或多个字符(不含路径分隔符) | /api/*/detail 匹配 /api/order/detail |
** | 匹配零个或多个路径段 | /api/order/** 匹配 /api/order/a/b/c |
3.2.2 方法断言:Method
predicates:
- Method=GET,POST # 只允许 GET 和 POST 请求
- Method=GET # 只允许 GET 请求3.2.3 请求头断言:Header
predicates:
- Header=X-Request-Id, \d+ # 必须有 X-Request-Id 头,且值为数字
- Header=Authorization, Bearer.* # 必须有 Bearer Token
- Header=X-Gray, true # 灰度标识头3.2.4 请求参数断言:Query
predicates:
- Query=version, v2 # 必须包含 version 参数,且值为 v2
- Query=token # 必须包含 token 参数,值不限3.2.5 Cookie 断言:Cookie
predicates:
- Cookie=sessionId, .+ # 必须包含 sessionId Cookie
- Cookie=lang, zh-CN # Cookie lang 必须为 zh-CN3.2.6 主机名断言:Host
predicates:
- Host=**.order.example.com # 匹配 order.example.com 的所有子域名
- Host=order.example.com # 精确匹配3.2.7 时间断言:After / Before / Between
用于定时发布、活动时间窗口等场景。
predicates:
# 2025 年 1 月 1 日 0 点之后才生效
- After=2025-01-01T00:00:00+08:00
# 2025 年 12 月 31 日 23:59 之前生效
- Before=2025-12-31T23:59:59+08:00
# 仅在活动期间生效
- Between=2025-06-01T00:00:00+08:00, 2025-06-18T23:59:59+08:003.2.8 权重断言:Weight(灰度核心)
实现流量染色和灰度发布的核心断言。
spring:
cloud:
gateway:
routes:
# 80% 流量到稳定版
- id: order-v1
uri: lb://order-service
predicates:
- Path=/api/order/**
- Weight=order-group, 80
# 20% 流量到灰度版
- id: order-v2
uri: lb://order-service-gray
predicates:
- Path=/api/order/**
- Weight=order-group, 20Weight 底层原理: Weight 断言基于哈希取模。对请求进行哈希(默认用请求的 routeId + 随机数),然后对权重总和取模,落在哪个区间就匹配哪个路由。同一组内的所有权重之和必须为 100。
3.2.9 IP 地址断言:RemoteAddr
predicates:
- RemoteAddr=192.168.1.0/24 # 只允许内网 IP
- RemoteAddr=10.0.0.0/8, 172.16.0.0/12 # 允许多个内网段3.2.10 自定义断言
@Component
public class TokenRoutePredicateFactory
extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {
public TokenRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
// 自定义判断逻辑
return token != null && token.startsWith("Bearer ") && token.length() > 10;
};
}
@Data
public static class Config {
// 配置属性
}
}使用方式(方法名去掉 RoutePredicateFactory 前缀即为配置名):
predicates:
- Token=true3.3 Filter(过滤器)详解
过滤器分为三种类型:
| 类型 | 作用范围 | 实现方式 |
|---|---|---|
| GatewayFilter | 单个路由 | 通过 filters 配置指定 |
| GlobalFilter | 全局所有路由 | 实现 GlobalFilter 接口并注册为 Bean |
| 过滤器工厂 | 单个路由(可配置) | 继承 AbstractGatewayFilterFactory |
3.3.1 过滤器执行顺序
过滤器的执行分为前置和后置两个阶段。前置过滤器在请求转发到下游服务之前执行,后置过滤器在下游服务返回响应之后执行。
请求 → [前置 Filter 1] → [前置 Filter 2] → [前置 Filter 3]
│
▼
转发请求到下游服务
│
▼
响应 ← [后置 Filter 3] ← [后置 Filter 2] ← [后置 Filter 1] ← 客户端注意:后置过滤器按照与前置相反的顺序执行(类似栈的后进先出)。
Order 优先级: 值越小,越先执行(前置)或越后执行(后置)。
@Bean
public GlobalFilter customFilter() {
return (exchange, chain) -> {
// === 前置处理 ===
System.out.println("前置:请求到达");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// === 后置处理 ===
System.out.println("后置:响应返回");
}));
};
}3.3.2 内置 GatewayFilter 详解
(1)StripPrefix — 去掉路径前缀
最常见的过滤器,去掉网关路由前缀,让下游服务看到的路径与其内部路由一致。
filters:
- StripPrefix=1 # /api/order/123 → /order/123
- StripPrefix=2 # /gateway/api/order/123 → /order/123(2)PrefixPath — 添加路径前缀
filters:
- PrefixPath=/v1 # /order/123 → /v1/order/123(3)RewritePath — 正则重写路径
filters:
# 将 /api/order/123 → /order/123
- RewritePath=/api/(?<segment>.*), /$\{segment}
# 将 /api/v1/order/123 → /order/v2/123
- RewritePath=/api/v1/(?<segment>.*), /api/v2/$\{segment}(4)AddRequestHeader — 添加请求头
filters:
- AddRequestHeader=X-Request-Source, gateway # 固定值
- AddRequestHeader=X-Request-Time, ${now} # 动态值
- AddRequestHeader=X-User-Id, #{@authUtil.getUserId()} # SpEL 表达式(5)AddRequestParameter — 添加请求参数
filters:
- AddRequestParameter=source, gateway # 添加 ?source=gateway(6)RemoveRequestHeader — 移除请求头
filters:
- RemoveRequestHeader=X-Internal-Token # 移除内部 Token,防止泄露
- RemoveRequestHeader=Cookie # 移除 Cookie(7)SetResponseHeader — 设置响应头
filters:
- SetResponseHeader=X-Response-Time, ${now}
- SetResponseHeader=X-Gateway-Version, 2.0(8)DedupeResponseHeader — 响应头去重
filters:
- DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_FIRST(9)Retry — 重试
filters:
- name: Retry
args:
retries: 3 # 重试 3 次
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE # 这些状态码才重试
methods: GET, POST # 只重试 GET 和 POST
backoff:
firstBackoff: 100ms # 第一次重试等待 100ms
maxBackoff: 500ms # 最大等待 500ms
factor: 2 # 退避因子:100ms → 200ms → 400ms(10)MapRequestHeader — 请求头映射
filters:
- MapRequestHeader=X-Custom-Token, Authorization # 将 X-Custom-Token 映射为 Authorization3.3.3 自定义过滤器工厂
@Component
public class AuthGatewayFilterFactory
extends AbstractGatewayFilterFactory<AuthGatewayFilterFactory.Config> {
public AuthGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
try {
// 解析 JWT
String userId = JwtUtil.parseUserId(token);
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.build();
return chain.filter(exchange.mutate().request(newRequest).build());
} catch (Exception e) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
};
}
@Data
public static class Config {
private boolean enabled = true;
}
}使用方式:
filters:
- Auth=true3.3.4 自定义 GlobalFilter
@Component
public class RequestLogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
String method = request.getMethodValue();
long startTime = System.currentTimeMillis();
log.info("请求进入网关: {} {} ", method, path);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
long cost = System.currentTimeMillis() - startTime;
HttpStatus status = exchange.getResponse().getStatusCode();
log.info("请求完成: {} {} {} {}ms", method, path, status, cost);
}));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE; // 最先执行,最后完成
}
}3.3.5 内置 GlobalFilter 一览
| 类名 | Order | 作用 |
|---|---|---|
RemoveCachedBodyFilter | -2147483648 | 清除缓存请求体 |
AdaptCachedBodyGlobalFilter | -2147482648 | 缓存请求体以便重复读取 |
NettyWriteResponseFilter | -1 | 将响应写回客户端(最后执行) |
ForwardPathFilter | 0 | 处理 forward:// 路由 |
RouteToRequestUrlFilter | 10000 | 将 Route 的 URI 转为请求 URL |
ReactiveLoadBalancerClientFilter | 10150 | 处理 lb:// 负载均衡 |
LoadBalancerServiceInstanceCookieFilter | 10151 | 负载均衡粘性会话 |
NettyRoutingFilter | 2147483647 | 发起 HTTP 请求到下游服务 |
ForwardRoutingFilter | 2147483647 | 处理 forward:// 转发 |
自定义 GlobalFilter 的 Order 建议:
- 鉴权:
-100 - 限流:
-50 - 日志:
-200(最先执行)/200(最后完成)
四、负载均衡与注册中心集成
Gateway 通过 lb://service-name 与注册中心联动,自动发现服务实例。
4.1 负载均衡策略
Gateway 默认使用 Spring Cloud LoadBalancer 的轮询策略。
# 切换到随机策略
spring:
cloud:
loadbalancer:
ribbon:
enabled: false # 禁用 Ribbon
configurations:
order-service:
loadbalancer:
health-check:
path: /actuator/health// 自定义负载均衡策略
@Bean
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
Environment env, LoadBalancerClientFactory factory) {
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}4.2 服务实例动态刷新
Gateway 与 Nacos 联动:
Gateway 启动
│
├── 注册到 Nacos
│
├── 订阅 order-service、user-service、product-service
│
├── Nacos 推送服务实例列表
│ order-service: [192.168.1.10:8080, 192.168.1.11:8080]
│
├── 请求到达:GET /api/order/123
│ → 匹配路由 order-service
│ → lb://order-service
│ → LoadBalancer 从实例列表中选择一个
│ → 转发到 192.168.1.10:8080
│
└── 实例变更(order-service 扩容到 3 个实例)
→ Nacos 推送变更事件
→ Gateway 更新本地实例列表
→ 新请求自动感知新实例五、限流详解
Gateway 内置了基于 Redis 的令牌桶限流算法。
5.1 令牌桶算法原理
令牌桶示意图:
令牌生成器(每秒 replenishRate 个令牌)
│
▼
┌─────────────┐
│ 令牌桶 │ ← 容量 burstCapacity
│ [●][●][●] │ 超过容量时丢弃令牌
└──────┬──────┘
│
▼
请求到达 → 取令牌
├── 有令牌 → 放行,令牌减 1
└── 无令牌 → 拒绝(429 Too Many Requests)| 参数 | 说明 |
|---|---|
redis-rate-limiter.replenishRate | 每秒生成令牌数,即允许的 QPS |
redis-rate-limiter.burstCapacity | 令牌桶容量,允许的瞬时突发流量 |
redis-rate-limiter.requestedTokens | 每次请求消耗令牌数,默认 1 |
示例: replenishRate=10, burstCapacity=20
- 匀速状态:每秒最多 10 个请求
- 突发状态:桶中积攒了 20 个令牌,可以瞬间处理 20 个请求(冷启动时)
- 突发后:桶空了,只能按 10 个/秒的速度处理
5.2 限流配置
spring:
redis:
host: localhost
port: 6379
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@userKeyResolver}"5.3 限流 Key 解析器
// 按用户 ID 限流(每个用户独立限流)
@Bean
public KeyResolver userKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
return Mono.just(userId != null ? userId : "anonymous");
};
}
// 按 IP 限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = Objects.requireNonNull(
exchange.getRequest().getRemoteAddress()).getAddress().getHostAddress();
return Mono.just(ip);
};
}
// 按接口限流(全局限流)
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> {
String path = exchange.getRequest().getURI().getPath();
return Mono.just(path);
};
}
// 组合限流:用户 + 接口
@Bean
public KeyResolver combinedKeyResolver() {
return exchange -> {
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
String path = exchange.getRequest().getURI().getPath();
return Mono.just((userId != null ? userId : "anonymous") + ":" + path);
};
}5.4 限流响应处理
默认限流返回 429,可以自定义响应内容:
@Bean
public WebExceptionHandler rateLimitExceptionHandler() {
return (exchange, ex) -> {
if (ex instanceof ResponseStatusException
&& ((ResponseStatusException) ex).getStatusCode() == HttpStatus.TOO_MANY_REQUESTS) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String body = "{\"code\":429,\"message\":\"请求过于频繁,请稍后再试\"}";
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(body.getBytes());
return exchange.getResponse().writeWith(Mono.just(buffer));
}
return Mono.error(ex);
};
}六、熔断降级
Gateway 可以集成 Resilience4j 或 Sentinel 实现熔断。
6.1 Resilience4j 熔断
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: CircuitBreaker
args:
name: orderCircuitBreaker
fallbackUri: forward:/fallback/order # 熔断后跳转
resilience4j:
circuitbreaker:
configs:
default:
sliding-window-size: 10 # 滑动窗口大小
minimum-number-of-calls: 5 # 最小调用次数
failure-rate-threshold: 50 # 失败率阈值 50%
wait-duration-in-open-state: 10s # 熔断开启后等待 10s 进入半开
permitted-number-of-calls-in-half-open-state: 3 # 半开状态允许 3 次调用熔断状态机:
CLOSED(关闭)
│ 失败率 ≥ 50%
▼
OPEN(开启)
│ 等待 10s
▼
HALF_OPEN(半开)
├── 3 次调用都成功 → CLOSED(恢复)
└── 任意一次失败 → OPEN(重新熔断)降级接口:
@RestController
public class FallbackController {
@GetMapping("/fallback/order")
public Mono<Map<String, Object>> orderFallback() {
return Mono.just(Map.of(
"code", 503,
"message", "订单服务暂时不可用,请稍后重试"
));
}
}6.2 Sentinel 集成
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel 控制台
scg:
fallback:
mode: response
response-body: '{"code":429,"message":"被限流了"}'七、灰度发布
7.1 基于权重的灰度
spring:
cloud:
gateway:
routes:
- id: order-stable
uri: lb://order-service
predicates:
- Path=/api/order/**
- Weight=order, 90
filters:
- AddRequestHeader=X-Version, stable
- id: order-gray
uri: lb://order-service-gray
predicates:
- Path=/api/order/**
- Weight=order, 10
filters:
- AddRequestHeader=X-Version, gray7.2 基于请求头的灰度
spring:
cloud:
gateway:
routes:
- id: order-stable
uri: lb://order-service
predicates:
- Path=/api/order/**
- Header=X-Gray, false
- id: order-gray
uri: lb://order-service-gray
predicates:
- Path=/api/order/**
- Header=X-Gray, true客户端在请求头中携带 X-Gray: true 即可命中灰度版本。这种方式适合内部测试人员或 VIP 用户优先体验新功能。
7.3 基于注册中心的灰度(Nacos 元数据)
# 灰度实例的 Nacos 元数据
spring:
cloud:
nacos:
discovery:
metadata:
version: gray// 自定义负载均衡,优先路由到同版本实例
@Bean
public ReactorLoadBalancer<ServiceInstance> versionLoadBalancer(
Environment env, LoadBalancerClientFactory factory) {
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new VersionBasedLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}八、跨域配置(CORS)
Gateway 统一处理跨域,下游服务无需各自配置。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOriginPatterns: "*.example.com" # 允许的域名模式
allowedMethods: "*" # 允许的方法
allowedHeaders: "*" # 允许的请求头
allowCredentials: true # 允许携带 Cookie
maxAge: 3600 # 预检请求缓存时间(秒)CORS 原理简述:
跨域请求流程:
1. 预检请求(OPTIONS)
浏览器 → OPTIONS /api/order/123
Origin: https://app.example.com
Access-Control-Request-Method: POST
网关 → 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Content-Type,Authorization
2. 实际请求
浏览器 → POST /api/order/123
Origin: https://app.example.com
网关 → 转发到 order-service
order-service → 响应
网关 → 添加 Access-Control-Allow-Origin 头
→ 返回给浏览器九、Gateway vs Zuul
| 对比维度 | Spring Cloud Gateway | Zuul 1.x | Zuul 2.x |
|---|---|---|---|
| 底层框架 | WebFlux + Netty | Servlet 3.0(Tomcat) | Netty |
| I/O 模型 | 非阻塞异步 | 阻塞同步 | 非阻塞异步 |
| 线程模型 | 少量 EventLoop 线程 | 一个请求一个线程 | 事件驱动 |
| 并发能力 | 数万连接 / 少量线程 | 数百连接 / 数百线程 | 数万连接 / 少量线程 |
| 性能(QPS) | 高(约 3 倍于 Zuul 1.x) | 低 | 高 |
| 开发体验 | 函数式路由,YAML 配置 | Filter 链式编程 | Filter 链式编程 |
| Spring 官方支持 | 是 | 否(已停止维护) | 否(已停止维护) |
| WebSocket | 原生支持 | 需要额外配置 | 支持 |
| 动态路由 | 支持 | 需自建路由表 | 支持 |
| 社区活跃度 | 活跃 | 不活跃 | 不活跃 |
十、动态路由
生产环境中,路由规则可能需要动态变更(如临时封禁某个接口、新增路由),无需重启网关。
@RestController
@RequestMapping("/gateway/routes")
public class RouteController {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher publisher;
// 动态添加路由
@PostMapping("/add")
public Mono<String> addRoute(@RequestBody RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
return Mono.just("路由添加成功");
}
// 动态删除路由
@DeleteMapping("/delete/{routeId}")
public Mono<String> deleteRoute(@PathVariable String routeId) {
routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
publisher.publishEvent(new RefreshRoutesEvent(this));
return Mono.just("路由删除成功");
}
// 查看所有路由
@GetMapping("/list")
public Flux<RouteDefinition> listRoutes() {
// 需要注入 RouteDefinitionLocator
return routeDefinitionLocator.getRouteDefinitions();
}
}动态路由适用的场景:
- 临时封禁某个接口(添加一个返回 403 的路由)
- 新服务上线,无需重启网关即可添加路由
- 灰度发布中动态调整流量比例
- A/B 测试路由切换
十一、生产环境最佳实践
11.1 高可用部署
┌──────────────┐
│ DNS / VIP │
└──────┬───────┘
│
┌───────┼───────┐
│ │ │
┌─────▼──┐ ┌─▼──────┐ ┌─────▼──┐
│Gateway1│ │Gateway2│ │Gateway3│
│ :8080 │ │ :8080 │ │ :8080 │
└────┬───┘ └───┬────┘ └───┬────┘
│ │ │
└──────────┼──────────┘
│
┌─────────▼─────────┐
│ Nacos 集群 │ ← 服务发现
└─────────┬─────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
│ Order │ │ User │ │ Product │
│ Service │ │ Service │ │ Service │
└─────────┘ └──────────┘ └──────────┘11.2 超时配置
spring:
cloud:
gateway:
httpclient:
connect-timeout: 2000 # 连接超时 2s
response-timeout: 10s # 响应超时 10s
pool:
max-connections: 500 # 最大连接数
max-idle-time: 30s # 空闲连接最大存活时间
max-life-time: 60s # 连接最大生命周期
acquire-timeout: 5000 # 等待连接超时 5s11.3 请求体大小限制
spring:
codec:
max-in-memory-size: 10MB # 请求体最大 10MB,防止 OOM11.4 安全防护
@Component
public class SecurityFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
// 1. SQL 注入防护
String query = request.getURI().getQuery();
if (query != null && containsSqlInjection(query)) {
return reject(exchange, "非法请求参数");
}
// 2. XSS 防护
// 在请求头中设置 XSS 过滤标记
// 3. IP 黑名单
String ip = getClientIp(request);
if (isBlacklisted(ip)) {
return reject(exchange, "IP 已被封禁");
}
return chain.filter(exchange);
}
private boolean containsSqlInjection(String input) {
String[] patterns = {"drop ", "delete ", "insert ", "update ", "select ", "union ", "exec "};
String lower = input.toLowerCase();
for (String p : patterns) {
if (lower.contains(p)) return true;
}
return false;
}
private Mono<Void> reject(ServerWebExchange exchange, String msg) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
byte[] bytes = ("{\"code\":403,\"message\":\"" + msg + "\"}").getBytes();
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
return exchange.getResponse().writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -200; // 在鉴权之前执行
}
}11.5 常见问题
Q:Gateway 适合做聚合层吗?
不适合。Gateway 是非阻塞的,但聚合操作(如并行调用多个服务、合并结果)会让 Gateway 承担业务逻辑,违背网关「轻量级」原则。聚合层应放在 BFF(Backend For Frontend)层。
Q:Gateway 能处理文件上传吗?
可以,但需要配置 max-in-memory-size。大文件上传建议走直连或 CDN 上传后回调。
Q:Gateway 和 Nginx 的区别?
| 维度 | Gateway | Nginx |
|---|---|---|
| 定位 | 应用层网关(L7) | 通用反向代理(L4/L7) |
| 动态路由 | 原生支持 | 需 OpenResty/Lua |
| 服务发现 | 与注册中心联动 | 需自建或插件 |
| 限流 | Redis 令牌桶 | 内置 limit_req |
| 灰度发布 | 原生支持 | 需 Lua 脚本 |
| 性能 | 高(Java/Netty) | 极高(C) |
| 适用场景 | 微服务架构 | 所有场景 |
常见组合: Nginx(前端 + 静态资源 + SSL 终止) → Gateway(微服务路由 + 鉴权 + 限流)
Q:文件上传 max-in-memory-size 配置不生效?
Spring Cloud Gateway 默认使用 DefaultDataBufferFactory 而非 NettyDataBufferFactory,在某些版本中 spring.codec.max-in-memory-size 可能不生效。可以显式配置:
@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory() {
NettyReactiveWebServerFactory factory = new NettyReactiveWebServerFactory();
factory.addServerCustomizers(server ->
server.httpRequestDecoder(spec -> spec.maxHeaderSize(8192)));
return factory;
}Q:Gateway 如何做链路追踪?
添加 spring-cloud-starter-sleuth 依赖,Gateway 会自动生成 TraceId 和 SpanId,并在转发请求时透传给下游服务。无需额外代码。
Q:Gateway 如何排查路由匹配问题?
logging:
level:
org.springframework.cloud.gateway: DEBUG开启 DEBUG 日志后,可以看到每个请求匹配了哪个路由、执行了哪些过滤器。