一、核心概念
1.1 什么是 API 网关?
API 网关是微服务架构中的入口点 (Entry Point),所有客户端请求都首先经过网关。它负责路由、认证、限流、监控、协议转换等横切关注点(Cross-Cutting Concerns),将非业务逻辑从微服务中剥离。
1.2 Spring Cloud Gateway 是什么?
Spring Cloud Gateway 是 Spring 官方基于 Spring 5, Spring Boot 2 和 Project Reactor 构建的响应式 API 网关。它旨在提供一种简单而有效的方式来路由到 API,并为它们提供横切关注点。
1.3 核心组件
组件 | 说明 |
---|---|
Route (路由) | 网关的基本构建块。它由一个 ID、一个目标 URI、一组断言(Predicates)和一组过滤器(Filters)组成。网关根据断言匹配请求,匹配成功则将请求路由到目标 URI。 |
Predicate (断言) | Java 8 的 Predicate 。输入类型是 Spring Framework 的 ServerWebExchange 。网关根据断言来匹配 HTTP 请求中的某些属性(如路径、方法、Header、参数等),决定该路由是否适用。 |
Filter (过滤器) | 用于修改请求和响应的机制。分为 GatewayFilter (作用于特定路由)和 GlobalFilter (作用于所有路由)。可以在请求被路由前(pre)或后(post)执行逻辑。 |
Handler Mapping | 负责将请求匹配到相应的路由。 |
1.4 关键特性
- 基于 Spring Framework 5, Project Reactor 和 Spring Boot 2:响应式、非阻塞。
- 动态路由:支持多种方式定义路由规则(配置文件、代码、注册中心)。
- 谓词和过滤器:强大的匹配和修改能力。
- 集成 Hystrix 断路器(已维护)或 Resilience4j。
- 集成 Spring Cloud DiscoveryClient:自动从注册中心(如 Eureka, Nacos)发现服务并创建路由。
- 易于扩展:支持自定义 Predicate 和 Filter。
- 限流 (Rate Limiting):支持基于 Redis 的限流。
- 路径重写 (Rewrite Path):修改请求路径。
- CORS 配置:跨域资源共享支持。
1.5 与 Zuul 1.x 的对比
特性 | Spring Cloud Gateway | Zuul 1.x |
---|---|---|
编程模型 | 响应式 (Reactive),非阻塞 | 阻塞式 (Blocking),基于 Servlet 2.5 |
性能 | 高(得益于非阻塞 I/O) | 相对较低 |
依赖 | Spring 5, WebFlux, Reactor | Servlet API, Spring MVC |
长连接支持 | 更好 | 较差 |
状态 | 推荐 | 已进入维护模式 |
结论:新项目必须使用 Spring Cloud Gateway,Zuul 1.x 仅用于维护旧项目。
二、详细操作步骤
步骤 1:创建 Spring Cloud Gateway 项目
使用 Spring Initializr 创建项目:
- Group:
com.example
- Artifact:
gateway-service
- Dependencies:
Spring WebFlux
(或Reactive Web
)Spring Cloud Gateway
Spring Cloud Discovery Client
(如果需要服务发现)Spring Boot Actuator
(可选,用于监控)
- Group:
检查
pom.xml
:<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- 如果需要从 Nacos/Eureka 自动发现服务 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- 或 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Actuator (可选) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
主启动类:
// GatewayApplication.java @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } } // 注意:**不需要** `@Enable*` 注解,Gateway 会自动配置。
步骤 2:配置静态路由(基于配置文件)
这是最常见的方式,通过 application.yml
定义路由规则。
# application.yml
server:
port: 8080 # 网关端口
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
# 路由 1: 路由到 order-service
- id: order-service-route # 路由唯一标识
uri: http://localhost:8081 # 目标服务地址 (静态)
predicates:
- Path=/api/orders/** # 断言:匹配 /api/orders 开头的路径
filters:
- StripPrefix=1 # 过滤器:去掉路径前缀 /api (请求 /api/orders/1 -> /orders/1)
- AddRequestHeader=X-Request-From, Gateway # 添加 Header
- AddResponseHeader=X-Response-From, Gateway # 添加响应 Header
# - name: RequestRateLimiter # 限流过滤器 (需配合 Redis)
# args:
# redis-rate-limiter.replenishRate: 1 # 每秒补充1个令牌
# redis-rate-limiter.burstCapacity: 3 # 令牌桶容量
# 路由 2: 路由到 product-service
- id: product-service-route
uri: lb://product-service # 使用 lb (loadbalancer) scheme,结合服务发现
predicates:
- Path=/api/products/**
- Method=GET,POST # 仅匹配 GET 和 POST 请求
filters:
- StripPrefix=1
- name: Hystrix # 断路器 (Hystrix 已维护,推荐 Resilience4j)
args:
name: fallbackCmd
fallbackUri: forward:/fallback/product # 降级处理
# 路由 3: 路由到外部服务
- id: external-api-route
uri: https://api.external.com
predicates:
- Host=**.myapp.com # 匹配 Host 头
- Query=apiKey, [a-zA-Z0-9]+ # 匹配名为 apiKey 的参数,且值为字母数字
# 全局 CORS 配置
globalcors:
cors-configurations:
'[/**]': # 对所有路径生效
allowedOrigins: "https://allowed-domain.com", "http://localhost:3000"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
# 如果使用服务发现 (Nacos/Eureka)
# cloud:
# nacos:
# discovery:
# server-addr: localhost:8848
# # 或 eureka:
# # client:
# # service-url:
# # defaultZone: http://localhost:8761/eureka/
# Actuator 配置 (可选)
management:
endpoints:
web:
exposure:
include: health, info, gateway # 暴露 gateway 端点
步骤 3:配置基于服务发现的动态路由
利用注册中心(如 Nacos)自动发现服务,无需手动指定 uri
。
确保服务已注册:
order-service
,product-service
等已成功注册到 Nacos 或 Eureka。修改网关配置:
spring: cloud: gateway: discovery: locator: enabled: true # 开启服务发现路由功能 lower-case-service-id: true # 将服务名转为小写 (如 PRODUCT-SERVICE -> product-service) routes: # 方式一:显式定义,使用 lb://service-name - id: order-route uri: lb://ORDER-SERVICE # lb scheme + 服务名 (Nacos 注册名) predicates: - Path=/api/orders/** filters: - StripPrefix=1 # 方式二:更简洁 - 使用 discovery.locator.enabled 后,会自动生成路由 # 默认规则:/service-name/** -> service-name (无需手动定义路由!) # 例如:访问 /order-service/orders/1 会自动路由到 ORDER-SERVICE 的 /orders/1 # 可通过 predicates 和 filters 覆盖或增强自动生成的路由。
访问:
- 直接访问
http://localhost:8080/order-service/orders/1
(利用自动生成路由)。 - 或访问
http://localhost:8080/api/orders/1
(利用手动定义路由)。
- 直接访问
步骤 4:编写降级 (Fallback) 处理
当服务不可用或断路器打开时,返回友好提示。
// FallbackController.java
@RestController
public class FallbackController {
@GetMapping("/fallback/product")
public Mono<String> productFallback() {
return Mono.just("商品服务暂时不可用,请稍后再试。");
}
@GetMapping("/fallback/**")
public Mono<String> defaultFallback() {
return Mono.just("服务暂时不可用,请稍后再试。");
}
}
步骤 5:使用 Java 代码配置路由 (可选)
// GatewayConfig.java
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("code_route", r -> r.path("/api/users/**")
.filters(f -> f.stripPrefix(1)
.addRequestHeader("X-Code-Route", "true"))
.uri("http://user-service:8082")) // 或 lb://user-service
.build();
}
}
步骤 6:测试网关
- 启动
gateway-service
。 - 启动后端微服务(如
order-service
on 8081)。 - 发送请求:
curl http://localhost:8080/api/orders/123 # 应该被路由到 http://localhost:8081/orders/123 (StripPrefix 生效)
- 查看日志,确认请求经过网关。
三、常见错误与解决方案
错误现象 | 原因分析 | 解决方案 |
---|---|---|
404 Not Found | 1. 请求路径未匹配任何路由的 predicates 2. 目标服务未启动 3. StripPrefix 配置错误 |
1. 检查 Path 断言2. 确认后端服务可用 3. 调整 StripPrefix 参数 |
500 Internal Server Error | 1. 目标服务返回 5xx 2. 网关配置错误 (如无效 URI) 3. 过滤器抛出异常 |
1. 检查后端服务日志 2. 检查 uri 配置3. 检查自定义 Filter 逻辑 |
Connection Refused / Timeout | 1. 目标服务地址/端口错误 2. 网络问题 3. 服务未注册 (lb://) |
1. 检查 uri 或服务名拼写2. telnet 测试3. 确认服务已注册到注册中心 |
网关启动失败 | 1. 依赖冲突 (如同时引入 Web MVC 和 WebFlux) 2. 端口占用 3. 配置语法错误 |
1. 确保项目是 Reactive 的,排除 spring-boot-starter-web (MVC),使用 spring-boot-starter-webflux 2. 更改 server.port 3. 检查 YAML/JSON 格式 |
服务发现路由不生效 | 1. spring.cloud.gateway.discovery.locator.enabled=false 2. 服务名大小写不匹配 3. 未添加 Discovery Client 依赖 |
1. 确保 enabled: true 2. 设置 lower-case-service-id: true 3. 检查依赖 |
四、注意事项
- Reactive 编程模型:
- Gateway 基于 WebFlux,是非阻塞、响应式的。
- 不要在 Gateway 中执行阻塞操作(如
Thread.sleep()
, 同步数据库调用)。 - 返回值通常是
Mono<T>
或Flux<T>
。
- 排除 MVC 依赖:
- 绝对不能在 Gateway 项目中引入
spring-boot-starter-web
(基于 Servlet 的 MVC)。 - 必须使用
spring-boot-starter-webflux
或spring-boot-starter-web
(Reactive)。
- 绝对不能在 Gateway 项目中引入
lb
Scheme:lb://service-name
依赖于Spring Cloud LoadBalancer
进行客户端负载均衡。
- 过滤器执行顺序:
GlobalFilter
执行在GatewayFilter
之前。- 同类型过滤器可通过
Ordered
接口或@Order
注解控制顺序。
- Actuator 端点:
/actuator/gateway/routes
:查看当前路由。/actuator/gateway/globalfilters
:查看全局过滤器。/actuator/gateway/routefilters
:查看路由过滤器。
- 线程模型:
- Netty (Reactor) 线程处理 I/O,应避免在这些线程上执行耗时任务。
五、使用技巧
5.1 自定义全局过滤器 (GlobalFilter)
实现 GlobalFilter
和 Ordered
。
@Component
@Order(-1) // 优先级最高
public class AuthGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !isValidToken(token)) {
// 返回 401
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
// 继续执行链
return chain.filter(exchange);
}
private boolean isValidToken(String token) {
// 实现 token 验证逻辑
return true;
}
}
5.2 自定义断言工厂 (Predicate Factory)
@Component
public class CheckHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckHeaderRoutePredicateFactory.Config> {
public CheckHeaderRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange -> {
String headerValue = exchange.getRequest().getHeaders().getFirst(config.headerName);
return headerValue != null && headerValue.equals(config.headerValue);
};
}
public static class Config {
private String headerName;
private String headerValue;
// getter/setter
}
}
在配置中使用:
predicates:
- name: CheckHeader
args:
headerName: X-Custom-Auth
headerValue: secret
5.3 重试机制 (Retry)
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
methods: GET, POST
backoff:
firstBackoff: 100ms
maxBackoff: 500ms
factor: 2
5.4 限流 (Rate Limiting with Redis)
添加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency>
配置:
spring: redis: host: localhost port: 6379 # 在路由 filters 中 filters: - name: RequestRateLimiter args: redis-rate-limiter.replenishRate: 10 # 每秒补充10个令牌 redis-rate-limiter.burstCapacity: 20 # 令牌桶最大容量20 # key-resolver: "#{@userKeyResolver}" # 自定义 Key 解析器 Bean 名
自定义 Key 解析器 (如按用户/IP):
@Component public class UserKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()); // 或从 Header/Token 获取用户ID } }
六、最佳实践
- 单一入口:所有外部请求都应通过网关。
- 安全前置:在网关层处理认证、鉴权、防重放、防刷。
- 统一日志:在网关记录访问日志(请求、响应、耗时、IP)。
- 监控与告警:集成 Actuator, Prometheus, Grafana 监控网关健康、流量、延迟。
- 灰度发布:利用 Predicate(如 Header、Cookie)将特定流量路由到新版本服务。
- 优雅降级:为关键服务配置降级策略。
- 配置管理:使用 Nacos/Config Server 管理网关路由配置,实现动态更新。
- 高可用部署:网关本身也需要集群部署,前面加负载均衡器(如 Nginx, SLB)。
- 文档化:明确网关的路由规则、过滤器作用。
七、性能优化
- JVM 调优:
- 合理设置堆内存 (
-Xms
,-Xmx
)。 - 选择合适的 GC 算法(如 G1GC)。
- 合理设置堆内存 (
- Netty 线程池:
- 默认使用 Reactor 的 EventLoopGroup。
- 通常无需调整,除非有特殊需求。
- 减少阻塞:
- 绝对避免在
filter
中执行Thread.sleep()
或同步 I/O。 - 如需调用外部服务,使用
WebClient
(Reactive) 而非RestTemplate
。
- 绝对避免在
- 缓存:
- 对频繁访问且不常变的数据,可在网关层做简单缓存(注意一致性)。
- 连接池:
- 配置合理的 HTTP 客户端连接池(如
HttpClient
配置)。
- 配置合理的 HTTP 客户端连接池(如
- 压缩:
- 启用 GZIP 压缩 (
server.compression.enabled=true
)。
- 启用 GZIP 压缩 (
- 限流:
- 防止突发流量压垮后端服务。
- 监控驱动优化:
- 使用监控数据定位瓶颈(CPU、内存、GC、网络、响应时间)。
总结
Spring Cloud Gateway 是现代微服务架构中不可或缺的组件。
核心要点:
- 响应式非阻塞:理解并适应 WebFlux 模型。
- 路由、断言、过滤器:掌握三大核心概念。
- 服务发现集成:利用
lb://
scheme 简化路由配置。 - 安全与治理:在网关层实现认证、限流、熔断、日志。
- 动态配置:结合 Nacos 等实现路由规则动态更新。
通过本指南,你已具备快速搭建和使用 Spring Cloud Gateway 的能力。务必注意避免阻塞操作和排除 MVC 依赖这两个最常见的陷阱。