Gateway网关
开篇:为什么微服务需要一个"大门"?
想象一下,你住在一个大型小区里,小区有 20 栋楼,每栋楼都有自己的门禁。如果外卖小哥要挨个找门禁才能送餐,效率会非常低。所以小区会设一个统一的大门和门卫室,外卖到了先到门卫室登记,由门卫告诉他该去哪栋楼。
微服务系统也是一样。当你有几十个微服务,每个服务都有不同的 IP 和端口,让客户端直接调各个服务,会遇到一堆问题:跨域、认证、限流、日志……每个服务都要自己实现一遍。
网关就是那个"门卫室"。客户端所有请求先到网关,由网关统一处理路由、认证、限流等横切关注点,再转发到后端微服务。Spring Cloud Gateway 是 Spring 官方推出的第二代网关(替代 Zuul),基于 WebFlux 和 Netty 构建,天生支持异步非阻塞。
一、核心概念
Spring Cloud Gateway 的工作模型围绕三个核心概念:
1. 路由(Route)
路由是网关最基础的部分。每条路由包含一个 ID、一个目标 URI、一组谓词和一组过滤器。当谓词匹配成功时,请求就会被转发到目标 URI。
2. 谓词(Predicate)
谓词就是路由的匹配条件。Gateway 内置了十几种谓词工厂,可以根据请求路径、请求头、请求参数、时间等条件来判断是否匹配。
3. 过滤器(Filter)
过滤器可以在请求转发前后执行额外逻辑,比如添加请求头、修改路径、限流、鉴权等。分为 GatewayFilter(作用于单条路由)和 GlobalFilter(作用于所有路由)两种。
一句话总结:路由是转发规则,谓词是匹配条件,过滤器是附加逻辑。
二、工作原理
Gateway 处理请求的完整流程:
- 客户端发送请求到 Gateway。
HandlerMapping根据谓词找到匹配的路由。- 请求进入过滤器链,先执行所有 pre 过滤器。
- 转发请求到后端微服务(代理请求)。
- 收到响应后,执行所有 post 过滤器。
- 将最终响应返回给客户端。
过滤器链的执行顺序类似于洋葱模型:请求从外向内穿过 pre 过滤器,响应再从内向外穿过 post 过滤器。
三、快速上手
3.1 引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>注意:Gateway 基于 WebFlux,不能和 spring-boot-starter-web 共存。如果父工程引入了 web 依赖,需要在网关模块中排除:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>test</scope>
</dependency>3.2 基础配置
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
enabled: true四、路由配置
4.1 路由到指定 URL
最简单的场景:把某个路径的请求转发到固定地址。
spring:
cloud:
gateway:
routes:
- id: order-route
uri: http://localhost:8001/
predicates:
- Path=/order/**访问 http://gateway:port/order/create 会被转发到 http://localhost:8001/order/create。
4.2 路由到注册中心的微服务
通过 lb:// 前缀(load balance),Gateway 会从注册中心查找服务实例并做负载均衡。
spring:
cloud:
gateway:
routes:
- id: order-route
uri: lb://service-order
predicates:
- Path=/order/**这样 Gateway 会自动从 Nacos 中发现 service-order 的所有实例,并进行负载均衡转发。
4.3 常用谓词工厂
Gateway 内置了丰富的谓词工厂,以下是最常用的几种:
| 谓词工厂 | 说明 | 示例 |
|---|---|---|
| Path | 根据请求路径匹配 | Path=/api/** |
| Method | 根据 HTTP 方法匹配 | Method=GET,POST |
| Header | 根据请求头匹配 | Header=X-Token, \d+ |
| Query | 根据查询参数匹配 | Query=name, jack |
| Between | 根据时间范围匹配 | Between=2024-01-01T00:00:00+08:00, ... |
| RemoteAddr | 根据客户端 IP 匹配 | RemoteAddr=192.168.1.0/24 |
多个谓词之间是 AND 关系,必须全部满足才算匹配成功。
五、过滤器
5.1 内置过滤器
Gateway 提供了几十种内置过滤器,最常用的有:
RewritePath —— 路径重写:
filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}访问 /api/order/list 实际转发的路径是 /order/list。
AddRequestHeader —— 添加请求头:
filters:
- AddRequestHeader=X-Request-Source, gatewayStripPrefix —— 去掉路径前缀:
filters:
- StripPrefix=1访问 /api/order/list 实际转发的路径是 /order/list(去掉第一段)。
5.2 自定义过滤器
当内置过滤器不够用时,可以自己写。类名必须以 GatewayFilterFactory 结尾:
@Slf4j
@Component
public class PrintLogGatewayFilterFactory
extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
log.info("请求信息: {}={}", config.getName(), config.getValue());
ServerHttpRequest request = exchange.getRequest().mutate().build();
ServerWebExchange modifiedExchange = exchange.mutate()
.request(request).build();
return chain.filter(modifiedExchange);
};
}
}配置中直接用类名前缀引用:
filters:
- PrintLog=key, value5.3 全局过滤器
全局过滤器作用于所有路由,适合做统一鉴权、日志等。实现 GlobalFilter 接口即可:
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || token.isEmpty()) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; // 数字越小优先级越高
}
}六、跨域配置
前后端分离项目中,跨域是绑定遇到的问题。在网关层统一处理最合适:
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}注意:如果后端微服务自己也配置了跨域,会导致响应头重复。最佳实践是只在网关层配置,后端服务不做跨域处理。
七、Gateway vs Zuul vs Nginx
| 对比项 | Spring Cloud Gateway | Zuul 1.x | Nginx |
|---|---|---|---|
| 技术栈 | WebFlux + Netty | Servlet + 阻塞 I/O | C 语言 |
| 性能 | 高(异步非阻塞) | 较低(同步阻塞) | 非常高 |
| 编程模型 | 响应式 | 传统 Servlet | 配置文件 |
| 与 Spring 生态整合 | 原生支持 | 支持 | 需要额外集成 |
| 动态路由 | 支持 | 支持 | 需要 Lua 等扩展 |
| 适用场景 | 微服务内部网关 | 已停更,不推荐新项目使用 | 外部流量入口、反向代理 |
实际项目中,Nginx 和 Gateway 经常搭配使用:Nginx 做外部流量入口和静态资源处理,Gateway 做微服务内部的路由和治理。
八、路由配置注意事项
- 路由顺序很重要:路径越精确的路由要放在前面,否则会被模糊路由抢先匹配。
- 不要开启
discovery.locator.enabled:虽然开启后可以通过服务名直接路由,但会暴露内部服务名称,存在安全隐患。 - 合理设置超时:Gateway 转发请求时也需要设置连接超时和读取超时,避免长时间等待后端响应。
spring:
cloud:
gateway:
httpclient:
connect-timeout: 3000 # 连接超时 3 秒
response-timeout: 10s # 响应超时 10 秒九、常见面试题精选
Q1:Gateway 的三大核心概念是什么?
Route(路由)、Predicate(谓词)、Filter(过滤器)。路由定义转发规则,谓词定义匹配条件,过滤器在转发前后执行附加逻辑。
Q2:Gateway 和 Zuul 有什么区别?
最核心的区别是底层模型:Gateway 基于 WebFlux + Netty,异步非阻塞,性能更好;Zuul 1.x 基于 Servlet,同步阻塞。另外 Zuul 已经停更,Gateway 是 Spring Cloud 官方推荐。
Q3:Gateway 中如何实现统一鉴权?
通过自定义 GlobalFilter 实现。在 filter 方法中检查请求头中的 Token,校验通过则放行,否则直接返回 401。GlobalFilter 作用于所有路由,不需要在每条路由上单独配置。
Q4:Gateway 如何与 Sentinel 整合做限流?
引入 sentinel-spring-cloud-gateway-adapter 依赖后,可以在 Sentinel Dashboard 中直接对 Gateway 的路由或 API 分组配置流控规则。网关限流的维度可以是路由 ID、请求路径或请求头等。
小结
Spring Cloud Gateway 是微服务架构中的"统一大门",承担了路由转发、负载均衡、鉴权、限流、日志、跨域等横切关注点的统一处理。
核心要记住三件事:
- 三大概念:Route 定义去哪里,Predicate 定义什么时候去,Filter 定义路上要做什么。
- 异步非阻塞:基于 WebFlux + Netty,不能和 spring-boot-starter-web 共存。
- 实际部署:通常 Nginx 在最外层做反向代理和静态资源处理,Gateway 在内层做微服务路由和治理。两者各司其职,不是替代关系。