跳到主要内容

API 网关

API 网关是微服务架构的重要组件,作为系统的统一入口,负责请求路由、协议转换、认证授权、限流熔断等工作。

为什么需要 API 网关

理解网关的价值,需要先看看没有网关时的架构问题。

客户端直接调用的问题

在微服务架构中,服务数量众多。如果客户端直接调用各个服务,会面临以下问题:

客户端需要知道每个服务的地址,服务地址变更时客户端需要修改。每个服务可能部署多个实例,客户端需要自己实现负载均衡。每个服务可能需要独立的认证,客户端需要重复实现认证逻辑。跨域问题、协议转换等横切关注点分散在各个服务中。

网关的作用

API 网关作为系统的统一入口,解决了上述问题:

统一路由:客户端只需要知道网关地址,网关根据路径将请求转发到对应服务。负载均衡:网关集成负载均衡能力,自动分发请求到服务实例。认证授权:网关统一处理认证授权,后端服务无需关心。协议转换:网关可以处理 HTTP、WebSocket 等协议的转换。限流熔断:网关可以在入口处实现限流和熔断,保护后端服务。日志监控:网关统一记录请求日志,便于监控和问题排查。

Spring Cloud Gateway 简介

Spring Cloud Gateway 是 Spring 官方开发的 API 网关,基于 Spring Framework 6、Spring Boot 3 和 Project Reactor 构建。它是 Zuul 的替代方案,提供了更好的性能和更丰富的功能。

核心概念

Spring Cloud Gateway 有三个核心概念:

Route(路由)是网关的基本构建块,由 ID、目标 URI、断言和过滤器组成。当断言为真时,路由匹配成功。

Predicate(断言)用于匹配 HTTP 请求的各个方面,如路径、请求头、请求参数、时间等。Spring Cloud Gateway 提供了丰富的内置断言。

Filter(过滤器)用于在请求发送到下游服务之前或之后修改请求和响应。可以在路由级别配置,也可以全局配置。

工作原理

请求到达网关后的处理流程:

  1. DispatcherHandler 接收请求
  2. RoutePredicateHandlerMapping 根据断言匹配路由
  3. FilteringWebHandler 执行过滤器链
  4. 过滤器链中的过滤器依次执行前置逻辑
  5. 请求转发到下游服务
  6. 过滤器链依次执行后置逻辑
  7. 响应返回给客户端

快速开始

添加依赖

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

如果需要服务发现功能:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

基础配置

server:
port: 8080

spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**

lb:// 前缀表示使用 LoadBalancer 进行负载均衡,后面的 user-service 是服务名。网关会从注册中心获取该服务的实例列表并负载均衡。

动态路由

开启服务发现的路由定位器,可以自动根据服务名创建路由:

spring:
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true

开启后,可以通过 /服务名/路径 的方式访问服务,如 /user-service/user/1

路由断言详解

Spring Cloud Gateway 提供了丰富的内置断言,可以组合使用。

Path 路径断言

最常用的断言,根据请求路径匹配:

predicates:
- Path=/user/** # 匹配 /user 开头的路径
- Path=/user/{id} # 匹配 /user/数字 的路径,并提取变量
- Path=/user/{id},/order/** # 多个路径

路径变量可以在过滤器中使用:

routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/user/{segment}
filters:
- SetPath=/user/{segment} # 重写路径

Method 方法断言

根据 HTTP 方法匹配:

predicates:
- Method=GET
- Method=GET,POST,PUT # 多个方法

Header 请求头断言

根据请求头匹配:

predicates:
- Header=X-Request-Id # 存在该请求头
- Header=X-Request-Id, \d+ # 请求头值匹配正则
- Header=Content-Type, application/json

Query 查询参数断言

根据查询参数匹配:

predicates:
- Query=name # 存在该查询参数
- Query=name, zhang.* # 参数值匹配正则

时间断言

根据请求时间匹配,常用于灰度发布:

predicates:
- After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
- Before=2024-12-31T23:59:59+08:00[Asia/Shanghai]
- Between=2024-01-01T00:00:00+08:00[Asia/Shanghai], 2024-12-31T23:59:59+08:00[Asia/Shanghai]

根据 Cookie 匹配:

predicates:
- Cookie=session # 存在该 Cookie
- Cookie=session, abc123 # Cookie 值匹配正则

Host 主机断言

根据 Host 请求头匹配:

predicates:
- Host=**.example.com
- Host=api.example.com,**.test.example.com

RemoteAddr 远程地址断言

根据客户端 IP 匹配:

predicates:
- RemoteAddr=192.168.1.1/24
- RemoteAddr=10.0.0.0/8,192.168.0.0/16

Weight 权重断言

按权重分流,常用于灰度发布:

routes:
- id: user-service-v1
uri: lb://user-service-v1
predicates:
- Path=/user/**
- Weight=group1, 80
- id: user-service-v2
uri: lb://user-service-v2
predicates:
- Path=/user/**
- Weight=group1, 20

同一 group1 组内,80% 请求到 v1,20% 请求到 v2。

组合断言

多个断言默认是 AND 关系。可以通过配置实现 OR 关系:

routes:
- id: example
uri: lb://example-service
predicates:
- name: Path
args:
patterns: /api/**
- name: Method
args:
methods: GET,POST

过滤器详解

Spring Cloud Gateway 提供了丰富的内置过滤器,按功能分为请求头处理、路径处理、请求参数处理等。

请求头过滤器

添加请求头:

filters:
- AddRequestHeader=X-Request-Id, 12345
- AddRequestHeader=X-Source, gateway

添加响应头:

filters:
- AddResponseHeader=X-Response-Time, 2024-01-01

移除请求头:

filters:
- RemoveRequestHeader=X-Unwanted-Header

移除响应头:

filters:
- RemoveResponseHeader=X-Unwanted-Header

设置请求头(覆盖已存在的):

filters:
- SetRequestHeader=X-Request-Id, 54321

路径过滤器

去除路径前缀:

filters:
- StripPrefix=1 # /api/user/1 -> /user/1
- StripPrefix=2 # /api/v1/user/1 -> /user/1

添加路径前缀:

filters:
- PrefixPath=/api # /user/1 -> /api/user/1

重写路径:

filters:
- RewritePath=/api/(?<segment>.*), /$\{segment}
# /api/user/1 -> /user/1

设置路径:

filters:
- SetPath=/api/user/{segment}

请求参数过滤器

添加请求参数:

filters:
- AddRequestParameter=foo, bar

移除请求参数:

filters:
- RemoveRequestParameter=unwanted

状态码过滤器

设置响应状态码:

filters:
- SetStatus=200
- SetStatus=NOT_FOUND

重定向过滤器

重定向:

filters:
- RedirectTo=302, https://example.com

重试过滤器

自动重试失败的请求:

filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY,SERVICE_UNAVAILABLE
methods: GET
backoff:
firstBackoff: 100ms
maxBackoff: 500ms
factor: 2

请求大小限制

限制请求体大小:

filters:
- name: RequestSize
args:
maxSize: 500KB

熔断过滤器

集成 Resilience4j 实现熔断:

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback

降级处理:

@RestController
public class FallbackController {

@GetMapping("/fallback")
public Mono<Map<String, Object>> fallback() {
Map<String, Object> result = new HashMap<>();
result.put("code", 503);
result.put("message", "服务暂时不可用");
return Mono.just(result);
}
}

限流过滤器

集成 Redis 实现限流:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒补充令牌数
redis-rate-limiter.burstCapacity: 20 # 令牌桶容量
key-resolver: "#{@userKeyResolver}" # 限流键解析器
@Configuration
public class GatewayConfig {

@Bean
public KeyResolver userKeyResolver() {
// 按用户 ID 限流
return exchange -> Mono.just(
exchange.getRequest()
.getQueryParams()
.getFirst("userId")
);
}

@Bean
public KeyResolver ipKeyResolver() {
// 按 IP 限流
return exchange -> Mono.just(
exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress()
);
}
}

自定义过滤器

当内置过滤器不能满足需求时,可以实现自定义过滤器。

全局过滤器

全局过滤器对所有路由生效:

@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();

// 排除不需要认证的路径
if (path.startsWith("/auth/") || path.equals("/health")) {
return chain.filter(exchange);
}

// 获取 Token
String token = request.getHeaders().getFirst("Authorization");
if (token == null || !validateToken(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}

// 解析用户信息并添加到请求头
String userId = parseUserId(token);
ServerHttpRequest newRequest = request.mutate()
.header("X-User-Id", userId)
.build();

return chain.filter(exchange.mutate().request(newRequest).build());
}

@Override
public int getOrder() {
return -100; // 数字越小,优先级越高
}

private boolean validateToken(String token) {
// 验证 Token 逻辑
return true;
}

private String parseUserId(String token) {
// 解析用户 ID
return "user123";
}
}

网关过滤器工厂

创建可配置的局部过滤器:

@Component
public class RequestTimeGatewayFilterFactory
extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {

public RequestTimeGatewayFilterFactory() {
super(Config.class);
}

@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
long startTime = System.currentTimeMillis();

return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;

if (config.isLogRequestTime()) {
log.info("请求 {} 耗时 {}ms",
exchange.getRequest().getURI(), duration);
}

// 添加响应头
exchange.getResponse().getHeaders()
.add("X-Response-Time", String.valueOf(duration));
})
);
};
}

public static class Config {
private boolean logRequestTime = true;

public boolean isLogRequestTime() {
return logRequestTime;
}

public void setLogRequestTime(boolean logRequestTime) {
this.logRequestTime = logRequestTime;
}
}
}

使用:

filters:
- name: RequestTime
args:
logRequestTime: true

跨域配置

@Configuration
public class CorsConfig {

@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();

// 允许的域名
config.addAllowedOriginPattern("*");
// 允许的方法
config.addAllowedMethod("*");
// 允许的头
config.addAllowedHeader("*");
// 允许携带凭证
config.setAllowCredentials(true);
// 预检请求缓存时间
config.setMaxAge(3600L);

UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);

return new CorsWebFilter(source);
}
}

高可用部署

网关作为系统入口,需要保证高可用。

多实例部署

部署多个网关实例,通过负载均衡器(如 Nginx、云厂商 LB)分发请求:

客户端 -> 负载均衡器 -> 网关实例 1
-> 网关实例 2
-> 网关实例 3

健康检查

配置健康检查,自动剔除故障实例:

management:
endpoints:
web:
exposure:
include: health
endpoint:
health:
show-details: always

性能调优

连接池配置

网关使用 Netty 作为 HTTP 客户端,可以配置连接池:

spring:
cloud:
gateway:
httpclient:
pool:
type: elastic
max-idle-time: 15000
connect-timeout: 3000
response-timeout: 30000

内存配置

调整 Netty 内存配置:

server:
netty:
connection-timeout: 5000
idle-timeout: 60000

响应式编程注意事项

Spring Cloud Gateway 基于响应式编程,需要注意:

不要在过滤器中阻塞操作,如 Thread.sleep()、JDBC 调用。必须阻塞时,应该使用 subscribeOn 指定调度器。避免在过滤器中创建大量对象,可能触发频繁 GC。

最佳实践

路由设计

路由粒度适中,太粗难以精细化控制,太细维护成本高。按服务名作为路径前缀,便于管理和监控。统一 API 版本管理,如 /v1/user//v2/user/

安全设计

敏感路径必须认证,开放路径白名单管理。不要在网关做复杂的权限校验,权限下沉到各服务。Token 解析在网关完成,用户信息透传给下游服务。

限流设计

根据服务能力配置限流阈值。区分不同接口的限流策略,核心接口更宽松。限流触发时返回友好提示,包含重试建议。

监控告警

监控网关的 QPS、响应时间、错误率。监控下游服务的健康状态。配置告警,异常时及时通知。

小结

本章详细介绍了 Spring Cloud Gateway:

API 网关作为系统统一入口,处理路由、认证、限流等横切关注点。Spring Cloud Gateway 是 Spring 官方的网关方案,基于响应式编程。断言用于匹配请求,过滤器用于处理请求和响应。支持熔断、限流、重试等高级特性。注意高可用部署和性能调优。