使用 Spring Cloud Gateway 构建可扩展的微服务网关
在微服务架构中,服务网关扮演着至关重要的角色。它作为所有外部请求的单一入口点,负责请求路由、负载均衡、认证授权、限流熔断等关键功能。一个设计良好、性能卓越的网关是微服务体系稳定运行的基石。Spring Cloud Gateway 是 Spring Cloud 生态中一个强大的网关组件,它基于 Project Reactor、Spring WebFlux 和 Spring Boot 构建,提供了非阻塞 API 和高性能的请求处理能力。本文将深入探讨如何使用 Spring Cloud Gateway 构建一个可扩展的微服务网关,涵盖其核心概念、配置方法、高级特性以及实践中的最佳实践。
1. Spring Cloud Gateway 核心概念
在深入了解如何使用 Spring Cloud Gateway 之前,我们需要先理解其几个核心概念:
- Route(路由): 路由是网关最基本的构建块。它由一个 ID、一个目标 URI、一组 Predicate(断言)和一个或多个 Filter(过滤器)组成。当请求到达网关时,会根据 Predicate 的匹配结果来决定是否将请求转发到目标 URI。
- Predicate(断言): 断言用于匹配 HTTP 请求的各种属性,例如请求头、路径、参数等。只有当请求满足所有 Predicate 的条件时,才会匹配到对应的路由。Spring Cloud Gateway 内置了多种 Predicate,例如 Path、Header、Method、Host 等。
- Filter(过滤器): 过滤器用于在请求被路由到目标服务之前或之后修改请求和响应。过滤器可以实现各种功能,例如添加请求头、修改响应体、记录日志、执行认证等。Spring Cloud Gateway 提供了两种类型的过滤器:GatewayFilter 和 GlobalFilter。GatewayFilter 应用于特定路由,而 GlobalFilter 应用于所有路由。
这三个核心概念之间的关系可以用下图表示:
+---------------------+ +---------------------+ +---------------------+
| Request | --> | Predicate | --> | Filter | --> | Target Service |
+---------------------+ +---------------------+ +---------------------+ +---------------------+
| (Matching) | | (Modification) |
+---------------------+ +---------------------+
2. 快速入门:构建一个简单的网关
让我们从一个简单的例子开始,构建一个基本的网关,将请求转发到目标服务。
2.1. 添加依赖
首先,我们需要在项目中添加 Spring Cloud Gateway 的依赖:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
如果你的项目是基于 Spring Boot 的, 那么可以这样引入, 否则, 还需要引入 Spring Boot 的依赖:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
2.2. 配置路由
接下来,我们需要在 application.yml
文件中配置路由:
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://example.com
predicates:
- Path=/foo/**
这个配置定义了一个名为 my_route
的路由。它将所有路径以 /foo/
开头的请求转发到 http://example.com
。
2.3. 启动网关
创建一个 Spring Boot 启动类:
“`java
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
“`
启动应用程序,现在你的网关已经在运行了。你可以通过访问 http://localhost:8080/foo/bar
来测试它,请求将被转发到 http://example.com/bar
。
3. 深入路由配置
Spring Cloud Gateway 提供了丰富的路由配置选项,可以满足各种复杂的路由需求。
3.1. Predicate 工厂
Spring Cloud Gateway 内置了许多 Predicate 工厂,用于匹配 HTTP 请求的不同属性。以下是一些常用的 Predicate 工厂:
- Path Route Predicate Factory: 根据请求路径进行匹配。
- Header Route Predicate Factory: 根据请求头进行匹配。
- Method Route Predicate Factory: 根据 HTTP 方法进行匹配。
- Host Route Predicate Factory: 根据请求的 Host 头进行匹配。
- Query Route Predicate Factory: 根据请求参数进行匹配。
- After Route Predicate Factory: 在指定时间之后匹配请求。
- Before Route Predicate Factory: 在指定时间之前匹配请求。
- Between Route Predicate Factory: 在指定时间范围内匹配请求。
- Cookie Route Predicate Factory: 匹配请求中是否存在指定的Cookie,以及Cookie的值(可选).
- RemoteAddr Route Predicate Factory: 匹配请求的来源IP地址。
你可以在路由配置中组合使用多个 Predicate,以实现更精细的匹配规则。例如:
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://example.com
predicates:
- Path=/foo/**
- Method=GET
- Header=X-Request-Id, \d+
这个配置将匹配路径以 /foo/
开头、HTTP 方法为 GET 且包含名为 X-Request-Id
且值为数字的请求头的请求。
3.2. URI 的多种形式
uri
属性指定了路由的目标地址。除了使用 http
和 https
协议外,还可以使用以下形式:
lb://<serviceId>
: 使用服务发现机制,将请求转发到名为<serviceId>
的服务实例。这种方式需要集成服务发现组件,例如 Eureka、Consul 或 Nacos。forward://<path>
: 进行本地请求转发, 目标是一个本地的handler.
例如,使用 lb://
协议:
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: lb://my-service
predicates:
- Path=/bar/**
这个配置将路径以 /bar/
开头的请求转发到名为 my-service
的服务实例。
3.3 路由排序
当多个路由都能够匹配到同一个请求时,Spring Cloud Gateway 会根据路由的优先级来决定使用哪个路由。
路由的优先级由以下因素决定:
- Predicate 的数量: Predicate 数量越多的路由优先级越高。
- Predicate 的顺序: Predicate 的顺序越靠前,优先级越高。
- 路由的定义顺序: 如果两个路由的Predicate完全相同, 那么定义在前面的优先级更高.
可以使用order
属性手动设置路由的优先级,order
值越小,优先级越高。
例如:
yaml
spring:
cloud:
gateway:
routes:
- id: route1
uri: http://example.com/route1
predicates:
- Path=/test/**
order: 1
- id: route2
uri: http://example.com/route2
predicates:
- Path=/test/**
order: 2
对于/test/abc
这个路径, 两个路由都能匹配, 但是由于route1
的order
更小, 所以会优先使用route1
.
4. 过滤器详解
过滤器是 Spring Cloud Gateway 的另一个核心功能,它允许你在请求被路由到目标服务之前或之后修改请求和响应。
4.1. GatewayFilter
GatewayFilter 应用于特定路由。Spring Cloud Gateway 内置了许多 GatewayFilter 工厂,用于实现各种常见功能。以下是一些常用的 GatewayFilter 工厂:
- AddRequestHeader GatewayFilter Factory: 添加请求头。
- AddResponseHeader GatewayFilter Factory: 添加响应头。
- RemoveRequestHeader GatewayFilter Factory: 删除请求头。
- RemoveResponseHeader GatewayFilter Factory: 删除响应头。
- RewritePath GatewayFilter Factory: 重写请求路径。
- SetStatus GatewayFilter Factory: 设置响应状态码。
- Hystrix GatewayFilter Factory: 集成 Hystrix 实现熔断。
- RateLimiter GatewayFilter Factory: 实现限流。
- Retry GatewayFilter Factory: 为上游服务调用配置重试策略.
你可以在路由配置中添加一个或多个 GatewayFilter:
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://example.com
predicates:
- Path=/foo/**
filters:
- AddRequestHeader=X-Request-Foo, Bar
- RewritePath=/foo/(?<segment>.*), /$\{segment}
这个配置为 my_route
添加了两个过滤器:
AddRequestHeader=X-Request-Foo, Bar
:添加一个名为X-Request-Foo
,值为Bar
的请求头。RewritePath=/foo/(?<segment>.*), /$\{segment}
:将请求路径从/foo/xxx
重写为/xxx
。
4.2. GlobalFilter
GlobalFilter 应用于所有路由。你可以通过实现 GlobalFilter
接口并将其注册为 Spring Bean 来创建自定义的 GlobalFilter。
“`java
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 在请求被路由之前执行的逻辑
exchange.getAttributes().put("start-time", System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
// 在请求被路由之后执行的逻辑
Long startTime = exchange.getAttribute("start-time");
if (startTime != null) {
long executeTime = System.currentTimeMillis() - startTime;
System.out.println(exchange.getRequest().getURI().getRawPath() + ": " + executeTime + "ms");
}
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //设置过滤器的执行顺序
}
}
“`
这个 GlobalFilter 在请求被路由之前记录请求开始时间,在请求被路由之后计算并打印请求执行时间。
getOrder()
方法用于指定过滤器的执行顺序,值越小,优先级越高。
4.3. 自定义 GatewayFilter
除了使用内置的 GatewayFilter 工厂外,你还可以通过实现 GatewayFilter
接口并将其注册为 Spring Bean 来创建自定义的 GatewayFilter。通常我们会继承AbstractGatewayFilterFactory
来实现自定义的GatewayFilterFactory
:
“`java
@Component
public class MyCustomGatewayFilterFactory extends AbstractGatewayFilterFactory
public MyCustomGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 在请求被路由之前执行的逻辑
if(config.isEnabled()) {
// 执行自定义逻辑…
}
return chain.filter(exchange);
};
}
public static class Config {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
“`
然后,你可以在路由配置中使用自定义的 GatewayFilter:
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: http://example.com
predicates:
- Path=/foo/**
filters:
- MyCustom=true # 这里的MyCustom 对应MyCustomGatewayFilterFactory的名字(去掉Factory)
5. 高级特性
除了基本的路由和过滤功能外,Spring Cloud Gateway 还提供了一些高级特性,可以帮助你构建更强大、更可靠的网关。
5.1. 限流
限流是保护微服务免受过载的重要手段。Spring Cloud Gateway 集成了 spring-cloud-starter-circuitbreaker-reactor-resilience4j
,可以使用RequestRateLimiter
过滤器实现限流功能。
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: lb://my-service
predicates:
- Path=/bar/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 #允许用户每秒执行多少个请求
redis-rate-limiter.burstCapacity: 20 # 令牌桶的容量
key-resolver: "#{@ipKeyResolver}" # 使用 SpEL 表达式从 exchange 中提取限流 key, 这里使用ipKeyResolver, 根据请求ip限流
要使用RequestRateLimiter
,你需要添加以下依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
并且定义KeyResolver
:
java
@Bean
KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
5.2. 熔断
熔断是另一种保护微服务的重要手段。当目标服务出现故障时,熔断器可以阻止请求继续发送到该服务,从而避免级联故障。Spring Cloud Gateway 通过集成 Resilience4j 提供了熔断功能。
yaml
spring:
cloud:
gateway:
routes:
- id: my_route
uri: lb://my-service
predicates:
- Path=/baz/**
filters:
- name: CircuitBreaker
args:
name: myCircuitBreaker
fallbackUri: forward:/fallback
这个配置为 my_route
添加了一个名为 myCircuitBreaker
的熔断器。当请求 my-service
失败时,熔断器会打开,后续请求将被转发到 /fallback
路径。
5.3. 负载均衡
Spring Cloud Gateway 可以与服务发现组件(例如 Eureka、Consul 或 Nacos)集成,实现负载均衡。当使用 lb://
协议时,Spring Cloud Gateway 会自动从服务发现组件获取服务实例列表,并使用负载均衡算法将请求分发到不同的实例。
Spring Cloud Gateway 默认使用轮询(Round Robin)算法进行负载均衡。你可以通过配置 spring.cloud.gateway.loadbalancer.type
属性来更改负载均衡算法。例如,使用随机(Random)算法:
yaml
spring:
cloud:
gateway:
loadbalancer:
type: random
如果需要使用更复杂的负载均衡策略,可以自定义 ReactorLoadBalancer
类型的 Bean。
5.4. 监控和指标
Spring Cloud Gateway 集成了 Micrometer,可以收集各种网关指标,例如请求数、响应时间、错误率等。你可以通过 Actuator 端点 /actuator/gateway
查看这些指标。
如果需要将这些指标暴露给Prometheus等监控系统,可以添加以下依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
然后配置 Actuator 暴露 metrics 端点:
yaml
management:
endpoints:
web:
exposure:
include: gateway, prometheus # 暴露 gateway 和 prometheus 端点
5.5 WebSocket支持
Spring Cloud Gateway本身就支持WebSocket的代理,不需要额外配置,只需要将uri
设置为ws://
协议即可,例如:
yaml
spring:
cloud:
gateway:
routes:
- id: websocket_route
uri: ws://localhost:9000
predicates:
- Path=/echo
6. 最佳实践
以下是一些使用 Spring Cloud Gateway 的最佳实践:
- 使用 YAML 配置: 尽量使用 YAML 文件进行路由和过滤器配置,因为它比 Java 代码更简洁、更易读。
- 合理使用 Predicate 和 Filter: 根据实际需求选择合适的 Predicate 和 Filter,避免过度配置。
- 配置限流和熔断: 为所有路由配置限流和熔断,以保护微服务免受过载和故障的影响。
- 监控网关指标: 监控网关的请求数、响应时间、错误率等指标,及时发现并解决问题。
- 使用服务发现: 与服务发现组件集成,实现动态路由和负载均衡。
- 安全性考虑: 使用 HTTPS 保护网关与客户端之间的通信,并对请求进行认证和授权。
- 版本控制: 将网关的配置(例如路由配置)纳入版本控制系统, 以便追踪变更和回滚.
- 测试: 对网关进行充分的测试, 包括单元测试, 集成测试和性能测试.
总结
Spring Cloud Gateway 是一个功能强大、性能卓越的微服务网关。它提供了丰富的路由和过滤功能,可以满足各种复杂的网关需求。通过合理配置 Predicate、Filter、限流、熔断等特性,你可以构建一个可扩展、可靠的微服务网关,为你的微服务体系保驾护航。希望本文能够帮助你深入理解 Spring Cloud Gateway,并在实践中更好地应用它。