Gateway 路由转发与网关治理实战
Gateway是什么
Spring Cloud Gateway 是基于 Spring WebFlux(响应式编程)的微服务网关,替代了已停更的 Zuul 1.x。它是微服务流量的统一入口,负责路由转发、认证鉴权、跨域处理、限流熔断和协议转换。
一句话定位: Nginx 承担边缘网关(SSL/WAF/全局限流),Gateway 承担业务网关(路由/鉴权/灰度/细粒度限流),两层的架构是目前生产的主流方案。
为什么需要
| 问题 | 没有网关的后果 | Gateway 怎么做 |
|---|---|---|
| 服务散落 | 前端需要知道每个微服务的地址,扩缩容改前端代码 | 统一入口 /order/** → order-server |
| 鉴权分散 | 每个服务自己写一遍认证逻辑,重复且容易漏 | 全局过滤器统一校验 Token |
| 跨域 | 前端调多个域名需要多处配置 CORS | 网关层一次性配置 |
| 限流 | 每个服务单独接入限流组件 | 网关层统配 Sentinel 限流 |
| 灰度发布 | 难以控制流量按规则切分到新版本 | 自定义断言按 Header/参数分流 |
核心架构:请求流转全流程
三核心组件:
| 组件 | 解释 | 例子 |
|---|---|---|
| Route(路由) | 网关的基本单元,包含 id + uri + predicates + filters | 把 /order/** 转到 lb://order-server |
| Predicate(断言) | 匹配条件,满足才转发 | Path=/order/** + Method=GET |
| Filter(过滤器) | 对请求/响应做前置或后置处理 | 鉴权、路径剥离、加请求头 |
新人上手指南
第一步:添加依赖
<!-- Gateway 核心(基于 WebFlux,不要加 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>
<!-- Sentinel 网关限流(可选,按需) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
⚠️ 重要: Gateway 基于 WebFlux,不能同时引入
spring-boot-starter-web(即 Spring MVC),否则启动冲突。如果你从旧项目迁移,务必排除 Spring MVC 依赖。
第二步:基础路由配置
# application.yml
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
# 路由一:动态路由(下游服务注册在 Nacos)
- id: order-service-route
uri: lb://order-server # lb=从 Nacos 负载均衡
predicates:
- Path=/order/**
filters:
- StripPrefix=1 # /order/xx → /xx
# 路由二:静态路由(指向固定地址)
- id: baidu-route
uri: http://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- RewritePath=/baidu/(?<seg>.*), /$\{seg}
# 自动从 Nacos 发现服务(路径 = 服务名小写)
discovery:
locator:
enabled: true
lower-case-service-id: true
server:
port: 8080
第三步:启动类
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
启动后访问 http://localhost:8080/order/create?userId=1 → Gateway 将路径剥离为 /create → 负载均衡转发到 order-server 的 /create 接口。
核心功能实战
1. 全局鉴权过滤器
统一校验 Token,不合规直接返回 401,不转发到下游。
@Configuration
public class GlobalAuthConfig {
/** 白名单路径:不用 Token */
private static final List<String> WHITELIST = Arrays.asList(
"/user/login", "/user/register"
);
@Bean
@Order(-100) // 数值越小优先级越高,在默认过滤器之前执行
public GlobalFilter authFilter() {
return (exchange, chain) -> {
String path = exchange.getRequest().getURI().getPath();
// 白名单直接放行
if (WHITELIST.contains(path)) {
return chain.filter(exchange);
}
// 校验 Token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || token.isBlank()) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 响应式写法
}
// 可选:校验 Token 合法性(调认证服务 / Redis 校验)
// 将解析后的用户信息放入请求头,透传给下游
exchange = exchange.mutate()
.request(r -> r.header("X-User-Id", parseUserId(token)))
.build();
return chain.filter(exchange);
};
}
}
和 Spring MVC 拦截器的区别: Gateway 是响应式架构,返回 Mono<Void>,不是 boolean。用 exchange.getResponse().setComplete() 终止请求,而不是 return false。
2. 跨域配置(CORS)
前后端分离项目中前端跨域调用的标准解决方案——在网关层统一配置,下游服务不需要任何 CORS 处理。
@Configuration
public class CorsGlobalConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*"); // 允许所有源(生产环境改成具体域名)
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true); // 允许携带 Cookie
config.setMaxAge(3600L); // 预检缓存 1 小时
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
注意:
addAllowedOriginPattern("*")配合allowCredentials(true)在 Spring Cloud Gateway 中必须用 Pattern 而非 Origin("*"),因为后者在与 Credentials 同时使用时会被浏览器拒绝。
3. 整合 Sentinel 网关限流
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
filter:
enabled: true
gateway:
sentinel:
enabled: true # 开启 Gateway 的 Sentinel 整合
Sentinel 控制台 → 网关路由限流 → 新增规则,选择路由 ID(如 order-service-route)并设置 QPS 阈值。超限请求返回 Blocked by Sentinel: FlowException,可通过 BlockRequestHandler 自定义返回格式。
4. 灰度发布实现
灰度发布(金丝雀发布):让一小部分用户先访问新版本服务,验证无误后全量切换。Gateway 通过自定义断言实现——按请求 Header 分流。
@Component
public class GrayRoutePredicateFactory
extends AbstractRoutePredicateFactory<GrayRoutePredicateFactory.Config> {
public GrayRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String tag = exchange.getRequest().getHeaders().getFirst("X-Gray-Tag");
return config.getTag().equals(tag);
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("tag");
}
public static class Config {
private String tag;
public String getTag() { return tag; }
public void setTag(String tag) { this.tag = tag; }
}
}
# 灰度路由配置
spring:
cloud:
gateway:
routes:
# 灰度版本:请求头 X-Gray-Tag=beta 时走新服务
- id: order-service-gray
uri: lb://order-server-beta
predicates:
- Path=/order/**
- Gray=beta
filters:
- StripPrefix=1
# 稳定版本:其余请求走老服务
- id: order-service-stable
uri: lb://order-server
predicates:
- Path=/order/**
filters:
- StripPrefix=1
测试: 带 X-Gray-Tag: beta 请求头 → 走到 order-server-beta;不带 → 走到 order-server。验证灰度版本无问题后,调整 Nacos 权重或切换 DNS 做全量发布。
5. 动态路由(基于 Nacos 配置中心)
静态路由写在 application.yml 中,改路由要重启网关。生产环境需要动态路由——在 Nacos 中修改配置,Gateway 实时生效。
# bootstrap.yml(必须用 bootstrap,优先级高于 application)
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
application:
name: gateway-server
在 Nacos 控制台创建配置 DataId = gateway-server.yaml,填入路由规则:
spring:
cloud:
gateway:
routes:
- id: order-service-route
uri: lb://order-server
predicates:
- Path=/order/**
filters:
- StripPrefix=1
- id: new-service-route # 新增路由无需重启
uri: lb://new-service
predicates:
- Path=/new/**
修改 Nacos 中的配置 → 保存 → 几秒后网关自动加载新路由。配置文件路径由 spring.cloud.nacos.config.server-addr 和 DataId 决定。
常见问题
1. Gateway 启动报错 "Failed to configure a data source"
最常见的原因是引入了 spring-boot-starter-web。Gateway 基于 WebFlux(反应式),与 Spring MVC(阻塞式)冲突。检查:
- pom.xml 中没有
spring-boot-starter-web或spring-boot-starter-webflux混用 - 如果有,排除:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
2. 路由 404 / 路由不生效
3. 跨域配置不生效
- 检查是否同时有多个 CorsWebFilter Bean(Gateway 默认带一个,自定义会覆盖)
- 检查
AllowCredentials(true)是否和AllowedOrigin("*")冲突——必须用addAllowedOriginPattern("*")替代 - 注意:跨域配置应只放在网关层,下游服务不要自己配 CORS,否则重复 Header 导致浏览器报错
4. 动态路由更新不及时
- 确认
bootstrap.yml中 Nacos Config 配置正确(不是 application.yml) - 确认
spring.cloud.nacos.config.file-extension和 DataId 后缀一致 - Nacos 配置需要是 DataId =
{spring.application.name}.{file-extension}的完整格式
面试题
Q1:Gateway 和 Zuul 的核心区别是什么?为什么选 Gateway?
参考答案:
| 维度 | Spring Cloud Gateway | Zuul 1.x(已停更) |
|---|---|---|
| 编程模型 | WebFlux 响应式(Netty + Reactor) | Servlet 阻塞式(Tomcat) |
| 线程模型 | 事件驱动,单线程处理大量连接 | 每个请求一个线程,连接多时线程数飙升 |
| 性能 | 高(无阻塞 IO) | 低(阻塞 IO,连接数上去后恶化) |
| 长连接 | 原生支持 WebSocket | 需要额外处理 |
| 集成 Nacos | 原生支持 | 需额外适配 |
两代网关的选择本质是编程模型的选择。Gateway 的响应式模型让它能在更少的线程下承载更多的并发连接,适合网关这种 IO 密集型场景。Zuul 1.x 在 Spring Cloud 体系中已被标记为停更维护状态,新项目不应该再使用。Netflix 推出的 Zuul 2.x 虽然也是响应式,但 Spring 官方选择了自己的 Gateway。
另外需要注意:Gateway 基于 WebFlux,如果项目强依赖 Spring MVC 的某些特性(如 @ControllerAdvice 的异常处理、spring-session 的 Servlet 集成),迁移到 Gateway 时需要调整实现方式。
Q2:全局过滤器和路由级过滤器的执行顺序是怎样的?同类型过滤器按什么排序?
参考答案:
一个请求到达 Gateway 后经过的过滤器链路顺序:
- 所有全局过滤器(GlobalFilter) 按
@Order值升序排列→进入匹配的路由 - 该路由的所有路由级过滤器(GatewayFilter) 按配置顺序执行(从上到下)
→转发到下游 - 下游返回响应后,路由级过滤器的后置逻辑按逆序执行
→全局过滤器的后置逻辑按逆序执行
排序规则详解:
- 全局过滤器通过
@Order或实现Ordered接口排序,数值越小优先级越高,-100会在0之前执行。 - 路由级过滤器按 yaml 中配置的书写顺序执行,没有独立的 Order 控制。
- 自定义的全局过滤器如果需要在内置过滤器之前/之后执行,通过调整
@Order值来控制。内置过滤器的 Order 值通常是 0 或更大的正数。
常见应用:
@Order(-100)的鉴权过滤器先执行,未登录的直接拦截,不走后续链路@Order(1000)的日志过滤器后执行,记录整个请求的处理结果
Q3:网关层限流和服务层限流有什么区别?什么场景下该用网关层限流?
参考答案:
| 对比维度 | 网关层限流 | 服务层限流 |
|---|---|---|
| 保护范围 | 所有请求在进入内部前就拦截 | 只保护当前服务自身 |
| 响应速度 | 在网关直接 reject,不消耗内部资源 | 请求已经穿透到服务,占用了连接 |
| 粒度 | 粗粒度(按路由、API、IP) | 细粒度(按方法、用户、来源) |
| 依赖 | 不依赖下游服务,网关独立运行即可 | 依赖服务自身接入限流组件 |
| 统一管理 | 一处配置,所有服务生效 | 每个服务各自配置 |
网关层限流最适合的场景:
- 全局限流: 对某个 API 或某个来源 IP 做整体限流,不管它打到哪个服务。例如对未认证的匿名请求做全局 QPS 限制。
- 入口保护: 防止突发流量冲垮整个集群。在网关层限制后,下游服务永远不会看到超过阈值的流量,这是最安全的一层防线。
- 廉价拒绝: 网关 reject 的开销远低于内部服务 reject 的开销(不需要走 RPC、数据库、业务逻辑)。
但网关层限流无法替代服务层限流——网关层只能看到经过它的流量,无法感知到服务内部的消息队列、定时任务、异步调用等流量。实践中通常两层都做:网关层做入口粗粒度限流,服务层做业务细粒度限流。
延伸阅读: Spring Cloud Gateway 官方文档 https://docs.spring.io/spring-cloud-gateway/reference/ ,重点是 Filter 的响应式编程模式和自定义 GatewayFilter 的开发方法。

浙公网安备 33010602011771号