Spring Gateway 入门指南:快速搭建微服务网关 – wiki基地


Spring Cloud Gateway 入门指南:快速搭建微服务网关

目录

  1. 引言:微服务与API网关的崛起
    • 微服务架构的挑战
    • API网关的诞生与作用
    • Spring Cloud Gateway 的优势
  2. Spring Cloud Gateway 核心概念解析
    • 路由 (Route)
    • 断言 (Predicate)
    • 过滤器 (Filter)
  3. 准备工作:搭建开发环境
    • Java Development Kit (JDK)
    • Maven 或 Gradle
    • IDE (IntelliJ IDEA, VS Code, Eclipse)
    • Spring Initializr
  4. 实战:快速搭建第一个 Spring Cloud Gateway
    • 步骤一:创建后端微服务 (UserService & ProductService)
      • 创建 UserService
      • 创建 ProductService
    • 步骤二:创建 Spring Cloud Gateway 服务
      • 项目初始化
      • 添加依赖
      • 配置 Gateway 路由
      • 启动与测试
  5. 深入理解 Gateway 配置:路由规则与断言详解
    • YAML 配置方式
    • Java API 配置方式
    • 常用路由断言 (Predicate)
      • Path 断言:路径匹配
      • Host 断言:域名匹配
      • Method 断言:HTTP方法匹配
      • Query 断言:查询参数匹配
      • Header 断言:请求头匹配
      • After, Before, Between 断言:时间匹配
      • Cookie 断言:Cookie匹配
      • RemoteAddr 断言:IP地址匹配
      • Weight 断言:权重路由 (灰度发布)
  6. Spring Cloud Gateway 过滤器 (Filter) 详解
    • Gateway Filter vs. Global Filter
    • 内置 Gateway Filter 工厂 (GatewayFilterFactory)
      • AddRequestHeader, AddResponseHeader, AddRequestParameter
      • StripPrefix:剥离路径前缀
      • PrefixPath:增加路径前缀
      • RewritePath:重写路径
      • Retry:请求重试
      • RequestRateLimiter:请求限流
      • CircuitBreaker (与 Resilience4j 集成):熔断器
      • 其他常用过滤器
    • 自定义 Global Filter
      • 实现 GlobalFilter 接口
      • 实现 Ordered 接口控制执行顺序
    • 自定义 Gateway Filter 工厂
      • 实现 AbstractGatewayFilterFactory
      • 定义配置类
  7. 集成服务注册与发现 (Eureka)
    • 搭建 Eureka Server
    • 微服务注册到 Eureka
    • Gateway 通过 Eureka 动态路由
    • 负载均衡 (LoadBalancer)
  8. 网关安全防护与鉴权
    • 集成 Spring Security
    • JWT 认证流程与网关鉴权
    • CORS 跨域配置
  9. 监控、日志与错误处理
    • Spring Boot Actuator
    • 分布式链路追踪 (Spring Cloud Sleuth & Zipkin)
    • 自定义错误页面与异常处理
  10. 高级特性与最佳实践
    • WebSocket 代理
    • 高可用部署
    • 配置管理 (Spring Cloud Config)
    • 性能优化
    • Spring Cloud Gateway vs. Zuul
  11. 总结与展望

1. 引言:微服务与API网关的崛起

微服务架构的挑战

随着互联网业务的飞速发展,传统的单体应用(Monolithic Application)在可伸缩性、可维护性、开发效率等方面逐渐暴露出瓶颈。微服务架构(Microservices Architecture)应运而生,它将一个大型应用拆分成一组小型、松耦合的服务,每个服务都运行在自己的进程中,并通过轻量级机制(通常是HTTP RESTful API)进行通信。

微服务带来了诸多优势:
* 独立部署与扩展: 每个服务可以独立部署和伸缩。
* 技术栈多样性: 不同服务可以使用不同的技术栈。
* 故障隔离: 单个服务的故障不会导致整个系统崩溃。
* 团队自治: 小团队可以独立负责一个或多个服务。

然而,微服务也带来了一系列挑战:
* 客户端如何访问服务? 客户端需要知道所有微服务的地址,管理复杂的请求。
* 如何进行认证与授权? 每个服务都需要处理安全问题,重复劳动。
* 如何处理跨域请求? 多个服务意味着多个源,需要统一处理CORS。
* 如何进行请求限流、熔断? 保护后端服务免受过载。
* 如何进行日志记录与监控? 统一的入口方便日志收集与性能监控。
* 如何进行协议转换、版本管理?

API网关的诞生与作用

为了解决上述微服务带来的挑战,API网关(API Gateway)模式应运而生。API网关是微服务架构中的一个核心组件,它是所有客户端请求的统一入口。它就像一个智能的“门面”,将外部请求路由到内部的各个微服务,并在请求到达微服务之前或响应返回客户端之前执行一系列横切关注点(Cross-cutting Concerns)的处理。

API网关的主要作用包括:
* 请求路由 (Routing): 将外部请求转发到正确的微服务实例。
* 请求聚合 (Request Aggregation): 将多个微服务返回的数据聚合成一个响应。
* 认证与授权 (Authentication & Authorization): 统一处理用户身份验证和权限校验。
* 限流与熔断 (Rate Limiting & Circuit Breaking): 保护后端服务,防止系统过载。
* 监控与日志 (Monitoring & Logging): 统一收集请求日志和性能指标。
* 安全防护 (Security): 阻止恶意请求,例如SQL注入、DDoS攻击。
* 协议转换 (Protocol Translation): 例如,将HTTP请求转换为内部服务的RPC调用。
* 灰度发布/A/B测试 (Canary Release/A/B Testing): 根据规则将一部分流量路由到新版本服务。
* 统一错误处理 (Unified Error Handling): 提供友好的错误响应。

Spring Cloud Gateway 的优势

在 Spring Cloud 生态系统中,早期使用的是 Netflix Zuul 作为 API 网关。Zuul 1.x 是基于 Servlet 阻塞 I/O 的,在处理高并发时性能存在瓶颈。随着 Spring 5 引入 Reactive Programming(反应式编程)和 Project Reactor,Spring Cloud Gateway 应运而生,它是 Spring Cloud 官方孵化的下一代网关,旨在替代 Zuul 1.x。

Spring Cloud Gateway 的核心优势在于:
* 基于 Spring 5, Spring Boot 2 和 Project Reactor: 构建在非阻塞 API 上,支持响应式编程模型,性能优异。
* 高性能: 基于 Netty 而非 Servlet 容器,I/O 模型是非阻塞的,能够处理更多的并发请求。
* 易于集成: 与 Spring Cloud 生态系统无缝集成,如服务发现 (Eureka, Consul)、负载均衡 (Spring Cloud LoadBalancer)、断路器 (Resilience4j) 等。
* 高度可扩展: 提供丰富的路由断言和过滤器,且支持自定义扩展。
* 声明式配置: 路由规则、断言和过滤器可以通过 YAML/Properties 文件或 Java API 进行声明式配置,非常直观。
* 强大的路由功能: 支持多种路由匹配方式,包括路径、Host、Header、Query 参数等。

2. Spring Cloud Gateway 核心概念解析

理解 Spring Cloud Gateway 的核心概念是掌握其使用的关键。它主要由三个核心组件构成:路由 (Route)断言 (Predicate)过滤器 (Filter)

路由 (Route)

路由是 Spring Cloud Gateway 的基本构建块。它定义了如何将来自客户端的特定请求转发到后端的目标服务。一个路由包含以下关键属性:
* ID (id): 路由的唯一标识符。
* URI (uri): 目标服务的URI,可以是具体的HTTP地址,也可以是服务发现中的服务ID(例如 lb://SERVICE-ID)。
* 断言 (predicates): 一个或多个断言,用于匹配请求是否符合此路由规则。
* 过滤器 (filters): 一个或多个过滤器,在请求转发到目标服务前或目标服务响应返回客户端前对请求或响应进行处理。
* 顺序 (order – 可选): 如果有多个路由规则可以匹配同一个请求,通过 order 属性(整数,值越小优先级越高)来决定哪个路由生效。

简而言之,一个路由就是一个完整的“路由规则”,它告诉网关:“如果请求满足这些断言,那么就将请求转发到这个URI,并在转发过程中或响应返回时执行这些过滤器。”

断言 (Predicate)

路由断言是 java.util.function.Predicate 的一个实例。它们用于判断一个给定的 HTTP 请求是否与路由规则匹配。如果请求满足路由定义的所有断言,那么该路由就会被选中。

Spring Cloud Gateway 内置了多种断言工厂,可以根据不同的条件(如路径、HTTP 方法、请求头、查询参数、Host、时间等)来匹配请求。您可以将多个断言组合在一个路由中,只有所有断言都为真时,路由才会被激活。

例如:Path=/api/** 表示匹配所有以 /api/ 开头的路径。Method=GET 表示只匹配 GET 请求。

过滤器 (Filter)

过滤器是 Spring Cloud Gateway 处理请求和响应的核心机制。它们允许在请求被路由到目标服务之前或响应返回给客户端之前,对请求或响应进行修改。过滤器可以用于实现诸如认证、授权、限流、日志记录、请求头修改、参数修改等功能。

Spring Cloud Gateway 提供了两种类型的过滤器:
1. Gateway Filter (局部过滤器): 作用于特定的路由。你可以在一个路由定义中配置一个或多个 Gateway Filter,它们只对该路由生效。
2. Global Filter (全局过滤器): 作用于所有路由。它们在 Gateway Filter 之前或之后执行,影响所有经过网关的请求。

过滤器链的执行顺序如下:
客户端请求 -> Global Filters (前置) -> Gateway Filters (前置) -> 目标服务 -> Gateway Filters (后置) -> Global Filters (后置) -> 客户端响应。

3. 准备工作:搭建开发环境

在开始之前,请确保您的开发环境已就绪。

Java Development Kit (JDK)

建议使用 JDK 17 或更高版本。
* 下载地址:Oracle JDKOpen JDK

Maven 或 Gradle

选择您熟悉的构建工具。本文将以 Maven 为例。
* Maven:https://maven.apache.org/download.cgi
* Gradle:https://gradle.org/install/

IDE (IntelliJ IDEA, VS Code, Eclipse)

推荐使用 IntelliJ IDEA Ultimate Edition 或 Community Edition,它对 Spring Boot 项目有很好的支持。

Spring Initializr

Spring 官方提供的快速生成 Spring Boot 项目的工具。我们将主要通过它来创建项目骨架。
* 访问地址:https://start.spring.io/

4. 实战:快速搭建第一个 Spring Cloud Gateway

我们将创建一个简单的微服务系统,包含两个后端服务 (UserServiceProductService) 和一个 GatewayService

步骤一:创建后端微服务 (UserService & ProductService)

这两个服务非常简单,只提供一个 RESTful 接口。

1. 创建 UserService

  • 访问 Spring Initializr: https://start.spring.io/
  • 项目元数据:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.x (选择最新稳定版)
    • Group: com.example.gateway
    • Artifact: user-service
    • Name: user-service
    • Package name: com.example.gateway.userservice
    • Java: 17
  • 依赖 (Dependencies):
    • Spring Web
    • Spring Boot DevTools (可选,方便开发)
  • 点击 GENERATE 下载项目压缩包。解压到您的工作目录。

  • 修改 application.yml (位于 src/main/resources):
    yaml
    server:
    port: 8081 # UserService 运行在 8081 端口
    spring:
    application:
    name: user-service # 服务名称

  • 创建 UserController.java (位于 src/main/java/com/example/gateway/userservice):
    “`java
    package com.example.gateway.userservice;

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    @RequestMapping(“/users”)
    public class UserController {

    @GetMapping("/{id}")
    public String getUserById(@PathVariable String id) {
        return "User " + id + " from UserService (port 8081)";
    }
    
    @GetMapping("/list")
    public String getAllUsers() {
        return "List of all users from UserService (port 8081)";
    }
    

    }
    “`

  • 启动 UserServiceApplication 访问 http://localhost:8081/users/1http://localhost:8081/users/list 确认服务正常运行。

2. 创建 ProductService

  • 同样通过 Spring Initializr 创建:
    • Artifact: product-service
    • Name: product-service
    • Package name: com.example.gateway.productservice
    • 依赖:Spring Web, Spring Boot DevTools
  • 下载并解压。

  • 修改 application.yml:
    yaml
    server:
    port: 8082 # ProductService 运行在 8082 端口
    spring:
    application:
    name: product-service # 服务名称

  • 创建 ProductController.java:
    “`java
    package com.example.gateway.productservice;

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    @RequestMapping(“/products”)
    public class ProductController {

    @GetMapping("/{id}")
    public String getProductById(@PathVariable String id) {
        return "Product " + id + " from ProductService (port 8082)";
    }
    
    @GetMapping("/catalog")
    public String getProductCatalog() {
        return "Product catalog from ProductService (port 8082)";
    }
    

    }
    “`

  • 启动 ProductServiceApplication 访问 http://localhost:8082/products/1http://localhost:8082/products/catalog 确认服务正常运行。

步骤二:创建 Spring Cloud Gateway 服务

现在我们来创建核心的网关服务。

1. 项目初始化

  • 访问 Spring Initializr: https://start.spring.io/
  • 项目元数据:
    • Project: Maven Project
    • Language: Java
    • Spring Boot: 3.x (选择最新稳定版)
    • Group: com.example.gateway
    • Artifact: gateway-service
    • Name: gateway-service
    • Package name: com.example.gateway.gatewayservice
    • Java: 17
  • 依赖 (Dependencies):
    • Spring Cloud Gateway
    • Spring Boot DevTools (可选)
  • 点击 GENERATE 下载项目压缩包。解压到您的工作目录。

2. 添加依赖 (Maven pom.xml)

确保 pom.xml 中包含 spring-cloud-starter-gateway 依赖。

“`xml


4.0.0 org.springframework.boot
spring-boot-starter-parent
3.2.5
com.example.gateway
gateway-service
0.0.1-SNAPSHOT
gateway-service
Demo project for Spring Cloud Gateway 17
2023.0.1


org.springframework.cloud
spring-cloud-starter-gateway


org.springframework.boot
spring-boot-starter-test
test


org.springframework.boot
spring-boot-devtools
runtimetrue





org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import


<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

“`

3. 配置 Gateway 路由 (application.yml)

src/main/resources/application.yml 中配置网关的端口和路由规则。这里我们使用最简单的 Path 断言。

“`yaml
server:
port: 8080 # Gateway 运行在 8080 端口,作为统一入口
spring:
application:
name: gateway-service
cloud:
gateway:
routes: # 定义路由规则
– id: user_service_route # 路由的唯一ID
uri: http://localhost:8081 # 目标服务的URI
predicates: # 断言列表,匹配请求的条件
– Path=/user/** # 当请求路径以 /user/ 开头时匹配
filters: # 过滤器列表
– StripPrefix=1 # 剥离路径的第一个部分,即 /user

    - id: product_service_route # 另一个路由ID
      uri: http://localhost:8082 # 目标服务的URI
      predicates:
        - Path=/product/** # 当请求路径以 /product/ 开头时匹配
      filters:
        - StripPrefix=1 # 剥离路径的第一个部分,即 /product

“`

配置说明:
* server.port: 8080: 网关将监听 8080 端口。
* spring.cloud.gateway.routes: 定义了一组路由规则。
* id: 每个路由规则必须有一个唯一的 id
* uri: 指定请求最终转发到的目标地址。这里直接指向了本地的 UserServiceProductService 的端口。
* predicates: 定义了路由的匹配条件。
* Path=/user/**: 表示任何以 /user/ 开头的请求路径都将匹配此路由。例如 /user/1, /user/list
* Path=/product/**: 表示任何以 /product/ 开头的请求路径都将匹配此路由。
* filters: 定义了应用于此路由的过滤器。
* StripPrefix=1: 这是一个内置过滤器。它会将请求路径的第一个部分(即 /user/product)剥离掉,然后将剩余的路径转发给目标服务。
* 例如,当请求 http://localhost:8080/user/1 时,网关会将其路由到 http://localhost:8081,但实际转发给 UserService 的路径是 /1 (因为 /user 被剥离了)。UserService 中的 /users/{id} 路径会匹配到 1
* 同理,http://localhost:8080/product/catalog 经过网关转发后,ProductService 接收到的路径是 /catalog

4. 启动与测试

  • 确保 UserService (8081) 和 ProductService (8082) 都在运行。
  • 启动 GatewayServiceApplication

  • 进行测试:

    • 访问 http://localhost:8080/user/1 -> 预期得到 User 1 from UserService (port 8081)
    • 访问 http://localhost:8080/user/list -> 预期得到 List of all users from UserService (port 8081)
    • 访问 http://localhost:8080/product/1 -> 预期得到 Product 1 from ProductService (port 8082)
    • 访问 http://localhost:8080/product/catalog -> 预期得到 Product catalog from ProductService (port 8082)

如果一切正常,您将看到网关成功地将请求路由到了正确的后端服务,并正确处理了路径。恭喜,您已经成功搭建了第一个 Spring Cloud Gateway!

5. 深入理解 Gateway 配置:路由规则与断言详解

除了 YAML 配置方式,我们还可以通过 Java API 来定义路由。

YAML 配置方式

上面已经展示了 YAML 配置,它通常是更推荐的方式,因为它更简洁直观,并且不需要重新编译代码即可修改路由规则。

Java API 配置方式

对于更复杂的逻辑或动态路由需求,可以通过 Java 代码来定义路由。这需要创建一个配置类,并注入 RouteLocator Bean。

“`java
package com.example.gateway.gatewayservice;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GatewayConfig {

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        // 定义用户服务的路由
        .route("user_service_route_java", r -> r.path("/user-api/**") // 断言:匹配 /user-api/** 路径
            .filters(f -> f.stripPrefix(1) // 过滤器:剥离前缀 /user-api
                            .addRequestHeader("X-Gateway-Request", "true")) // 过滤器:添加请求头
            .uri("http://localhost:8081")) // 目标URI

        // 定义商品服务的路由
        .route("product_service_route_java", r -> r.path("/product-api/**") // 断言:匹配 /product-api/** 路径
            .filters(f -> f.stripPrefix(1) // 过滤器:剥离前缀 /product-api
                            .addRequestHeader("X-Gateway-Request", "true"))
            .uri("http://localhost:8082")) // 目标URI
        .build();
}

}
``
**说明:**
* 当同时存在 YAML 和 Java API 配置时,两者都会生效。如果存在冲突,则需要根据具体规则和
order` 属性来判断优先级。通常,建议选择一种方式为主。
* Java API 方式可以利用 Java 的强大表现力,比如根据条件动态生成路由,或者从数据库/配置中心加载路由规则。

常用路由断言 (Predicate)

Spring Cloud Gateway 提供了丰富的内置断言工厂来匹配不同的请求条件。以下是几个常用的断言及其示例:

1. Path 断言:路径匹配
最常用的断言,根据请求路径进行匹配。

  • 语法: - Path=URI_TEMPLATE
  • 示例:
    “`yaml
    # 匹配 /foo/1, /foo/bar 等

    • Path=/foo/**

    匹配 /foo/1, /foo/2 等,占位符 {segment} 会被提取为 path variable

    • Path=/foo/{segment}
      “`

2. Host 断言:域名匹配
根据请求的 Host 头进行匹配。支持 Ant 风格的匹配模式。

  • 语法: - Host=HOST_PATTERN
  • 示例:
    “`yaml
    # 匹配 example.org, www.example.org 等

    • Host=*.example.org

    匹配 dev.example.org, pro.example.org

    • Host={sub}.example.org
      “`

3. Method 断言:HTTP方法匹配
根据请求的 HTTP 方法(GET, POST, PUT, DELETE 等)进行匹配。

  • 语法: - Method=HTTP_METHOD[,HTTP_METHOD...]
  • 示例:
    “`yaml
    # 只匹配 GET 请求

    • Method=GET

    匹配 GET 或 POST 请求

    • Method=GET,POST
      “`

4. Query 断言:查询参数匹配
根据请求的查询参数进行匹配。可以匹配是否存在某个参数,或参数值是否符合某个正则表达式。

  • 语法: - Query=PARAM_NAME[,PARAM_REGEX]
  • 示例:
    “`yaml
    # 匹配包含参数 “foo” 的请求 (值不限)

    • Query=foo

    匹配包含参数 “foo” 且值为 “bar” 的请求

    • Query=foo,bar

    匹配包含参数 “version” 且值为数字的请求

    • Query=version,\d+
      ``
      *请求示例:
      http://localhost:8080/api?foo=testhttp://localhost:8080/api?version=123`*

5. Header 断言:请求头匹配
根据请求的 Header 进行匹配。可以匹配是否存在某个 Header,或 Header 值是否符合某个正则表达式。

  • 语法: - Header=HEADER_NAME[,HEADER_REGEX]
  • 示例:
    “`yaml
    # 匹配包含 “X-Request-Id” 头部的请求

    • Header=X-Request-Id

    匹配包含 “X-Version” 头部且值为 v1 或 v2 的请求

    • Header=X-Version,(v1|v2)
      ``
      *请求示例:
      curl -H “X-Version: v1” http://localhost:8080/api`*

6. After, Before, Between 断言:时间匹配
用于基于时间窗口的路由。After 在指定时间之后,Before 在指定时间之前,Between 在两个时间之间。

  • 语法:
    • - After=DATETIME_STRING
    • - Before=DATETIME_STRING
    • - Between=DATETIME_STRING1,DATETIME_STRING2
  • DATETIME_STRING 格式: yyyy-MM-dd'T'HH:mm:ss.SSSZ (ISO 8601),例如 2024-05-15T10:00:00.000+08:00
  • 示例:
    “`yaml
    # 匹配 2024年5月15日10点之后的所有请求

    • After=2024-05-15T10:00:00.000+08:00

    匹配 2024年5月15日10点到11点之间的请求

    • Between=2024-05-15T10:00:00.000+08:00,2024-05-15T11:00:00.000+08:00
      “`
      主要用于特定时间段内的活动或维护。

7. Cookie 断言:Cookie匹配
根据请求的 Cookie 进行匹配。

  • 语法: - Cookie=COOKIE_NAME[,COOKIE_REGEX]
  • 示例:
    “`yaml
    # 匹配包含名为 “mycookie” 的 Cookie 的请求

    • Cookie=mycookie

    匹配名为 “user” 且值为 “guest” 或 “admin” 的 Cookie 的请求

    • Cookie=user,(guest|admin)
      ``
      *请求示例:浏览器中带有
      mycookie=somevalue` 的请求。*

8. RemoteAddr 断言:IP地址匹配
根据客户端的 IP 地址进行匹配。支持 CIDR 格式。

  • 语法: - RemoteAddr=IP_ADDRESS_RANGE[,IP_ADDRESS_RANGE...]
  • 示例:
    “`yaml
    # 匹配来自 192.168.1.1/24 网段的请求

    • RemoteAddr=192.168.1.1/24

    匹配来自 10.0.0.0/8 或 172.16.0.0/16 网段的请求

    • RemoteAddr=10.0.0.0/8,172.16.0.0/16
      “`
      主要用于 IP 白名单/黑名单。

9. Weight 断言:权重路由 (灰度发布)
根据设置的权重将请求分发到不同的路由。常用于灰度发布或 A/B 测试。

  • 语法: - Weight=GROUP_NAME,WEIGHT
    • GROUP_NAME: 组名,用于将多个路由关联起来。
    • WEIGHT: 权重值 (0-100)。
  • 示例:
    “`yaml
    routes:

    • id: canary_route_v1
      uri: http://localhost:8081
      predicates:

      • Path=/api/product/**
      • Weight=product-group,80 # 80% 的流量到 v1
        filters:
      • StripPrefix=1
    • id: canary_route_v2
      uri: http://localhost:8083 # 假设这是 v2 服务
      predicates:

      • Path=/api/product/**
      • Weight=product-group,20 # 20% 的流量到 v2
        filters:
      • StripPrefix=1
        ``
        *当匹配到
        /api/product/*时,网关会在product-group` 中的路由中,根据权重将请求按比例分发。

6. Spring Cloud Gateway 过滤器 (Filter) 详解

过滤器是网关的核心功能,用于在请求到达目标服务前或响应返回客户端前执行逻辑。

Gateway Filter vs. Global Filter

  • Gateway Filter (局部过滤器):
    • 在路由定义中配置,只应用于该特定路由。
    • GatewayFilterFactory 创建。
    • 例如 StripPrefixAddRequestHeader
  • Global Filter (全局过滤器):
    • 对所有路由生效。
    • 实现 GlobalFilter 接口。
    • 例如统一的日志记录、权限校验。

内置 Gateway Filter 工厂 (GatewayFilterFactory)

Spring Cloud Gateway 提供了大量的内置过滤器工厂,它们可以直接在 YAML 或 Java API 配置中使用。

1. AddRequestHeader, AddResponseHeader, AddRequestParameter
用于添加请求头、响应头或请求参数。

  • 语法:
    • - AddRequestHeader=HeaderName,HeaderValue
    • - AddResponseHeader=HeaderName,HeaderValue
    • - AddRequestParameter=ParamName,ParamValue
  • 示例:
    “`yaml
    filters:

    • AddRequestHeader=X-Request-Id, ${correlationId} # 添加请求头,可以引用 SpEL 表达式
    • AddResponseHeader=X-Response-Time, {after-routing-millis} # 添加响应头
    • AddRequestParameter=source,gateway # 添加请求参数
      “`

2. StripPrefix:剥离路径前缀
用于从请求路径中剥离指定数量的前缀。这是最常用的过滤器之一。

  • 语法: - StripPrefix=PARTS (PARTS 为要剥离的路径段数)
  • 示例:
    “`yaml
    # 原始请求: /api/users/1 -> 剥离 /api -> 转发到后端服务路径: /users/1
    filters:

    • StripPrefix=1
      “`

3. PrefixPath:增加路径前缀
在请求路径前面添加一个指定的前缀。

  • 语法: - PrefixPath=PATH_PREFIX
  • 示例:
    “`yaml
    # 原始请求: /users/1 -> 添加 /v1 -> 转发到后端服务路径: /v1/users/1
    filters:

    • PrefixPath=/v1
      “`

4. RewritePath:重写路径
使用正则表达式替换请求路径。

  • 语法: - RewritePath=REGEX,REPLACEMENT
  • 示例:
    “`yaml
    # 原始请求: /api/v1/users/1 -> 匹配 /api/v1/(?.*) -> 替换为 /v2/${segment}
    # 转发到后端服务路径: /v2/users/1
    filters:

    • RewritePath=/api/v1/(?.*),/v2/${segment}
      “`

5. Retry:请求重试
当后端服务发生故障时,网关可以自动重试请求。

  • 语法: - Retry=RETRIES (重试次数,可选配置 HTTP 方法和状态码)
  • 示例:
    “`yaml
    filters:
    # 最多重试 3 次

    • Retry=3
      # 针对 GET, POST 请求,并在 5xx 状态码或连接错误时重试
      # – Retry=retries=3, statuses=[500, 502], methods=[GET, POST]
      “`

6. RequestRateLimiter:请求限流
基于令牌桶算法实现请求限流,保护后端服务。需要结合 Redis 等外部存储。

  • 语法: - RequestRateLimiter=args
  • 示例 (使用 Redis 作为存储):
    首先,您需要添加 Redis 依赖:
    xml
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

    application.yml 中配置 Redis 和限流:
    yaml
    spring:
    data:
    redis:
    host: localhost
    port: 6379
    cloud:
    gateway:
    routes:
    - id: rate_limited_route
    uri: http://localhost:8081
    predicates:
    - Path=/limit/**
    filters:
    # 令牌桶算法配置:
    # 10: 令牌桶容量 (burstCapacity),表示桶中最多存储多少个令牌
    # 5: 填充速率 (replenishRate),每秒填充多少个令牌
    # #{#remoteAddress}:限流的 key,这里使用客户端IP地址 (SpEL 表达式)
    - RequestRateLimiter=10,5,#{#remoteAddress}
    redis-rate-limiter:
    # 默认 KeyResolver,也可以自定义 Bean
    # key-resolver: '#{@myKeyResolver}'

    您还需要一个 KeyResolver Bean 来定义限流的 key:
    “`java
    package com.example.gateway.gatewayservice;

    import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import reactor.core.publisher.Mono;

    @Configuration
    public class RateLimiterConfig {

    // 根据 IP 地址进行限流
    @Bean
    public KeyResolver remoteAddressKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    
    // 可以定义其他 KeyResolver,例如根据用户ID,或请求参数等
    // @Bean
    // public KeyResolver userKeyResolver() {
    //     return exchange -> Mono.just(exchange.getRequest().getHeaders().getFirst("user-id"));
    // }
    

    }
    “`

7. CircuitBreaker (与 Resilience4j 集成):熔断器
与 Resilience4j 集成,为路由提供熔断功能,防止故障扩散。

  • 语法: - CircuitBreaker=NAME[,config]
  • 示例:
    首先,添加 spring-cloud-starter-circuitbreaker-reactor-resilience4j 依赖:
    xml
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
    </dependency>

    application.yml 中配置:
    yaml
    spring:
    cloud:
    gateway:
    routes:
    - id: circuit_breaker_route
    uri: http://localhost:8081 # 假设这个服务可能不稳定
    predicates:
    - Path=/breaker/**
    filters:
    - CircuitBreaker=myCircuitBreaker # 熔断器名称
    # Resilience4j 配置 (可选,使用默认值也可以)
    circuitbreaker:
    resilience4j:
    circuitBreaker:
    configs:
    default:
    failureRateThreshold: 50 # 故障率阈值 (50%)
    slidingWindowSize: 10 # 滑动窗口大小 (10次请求)
    minimumNumberOfCalls: 5 # 最小请求数
    waitDurationInOpenState: 5s # 开启状态持续时间 (5秒)
    permittedNumberOfCallsInHalfOpenState: 3 # 半开状态允许的请求数

    myCircuitBreaker 路由的后端服务调用失败率超过阈值时,熔断器将打开,所有请求将直接失败或 fallback。

其他常用过滤器:
* RemoveRequestHeader, RemoveResponseHeader:移除请求/响应头。
* SetPath, SetRequestHeader, SetResponseHeader, SetStatus:设置请求路径、请求头、响应头、HTTP状态码。
* MapRequestHeader, MapResponseHeader:修改请求/响应头名称。
* DedupeResponseHeader:去重响应头。

自定义 Global Filter

全局过滤器作用于所有路由。例如,我们可以创建一个记录所有请求耗时的全局过滤器。

  1. 创建 CustomGlobalFilter.java:
    “`java
    package com.example.gateway.gatewayservice;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;

    @Component
    public class CustomGlobalFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(CustomGlobalFilter.class);
    private static final String START_TIME = "startTime";
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 前置处理:请求进入网关时记录开始时间
        exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
        log.info("CustomGlobalFilter pre: Request path = {}", exchange.getRequest().getPath());
    
        // 调用下一个过滤器或路由到目标服务
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            // 后置处理:请求从目标服务返回后计算耗时
            Long startTime = exchange.getAttribute(START_TIME);
            if (startTime != null) {
                long executeTime = System.currentTimeMillis() - startTime;
                log.info("CustomGlobalFilter post: Request path = {}, execute time = {}ms",
                        exchange.getRequest().getPath(), executeTime);
            }
        }));
    }
    
    @Override
    public int getOrder() {
        // 过滤器的执行顺序,值越小优先级越高
        // GatewayFilterConstants.ORDER_ROUTE_ID_PREFIX 是一个参考值,通常自定义全局过滤器可以设置在它之前或之后
        // 这里设置在 GatewayFilterConstants.LAST_ORDER_MINUS_1 之前,意味着它会比较靠后执行
        return -1; // 较高的优先级 (更早执行)
        // 或者使用 Ordered.HIGHEST_PRECEDENCE (最早执行)
        // 或者使用 Ordered.LOWEST_PRECEDENCE (最晚执行)
    }
    

    }
    ``
    *
    GlobalFilter接口定义了filter方法,其中包含ServerWebExchangeGatewayFilterChain
    *
    ServerWebExchange封装了请求和响应信息。
    *
    GatewayFilterChain用于调用下一个过滤器。
    *
    Ordered接口用于定义过滤器的执行顺序,值越小优先级越高。
    *
    Mono.fromRunnable()` 用于在响应返回后执行逻辑。

自定义 Gateway Filter 工厂

如果您需要一个可以带参数配置的局部过滤器,可以实现 AbstractGatewayFilterFactory

  1. 定义过滤器配置类 (例如 AuthGatewayFilterFactory.java):
    “`java
    package com.example.gateway.gatewayservice;

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.stereotype.Component;
    import reactor.core.publisher.Mono;

    import java.util.Arrays;
    import java.util.List;

    @Component
    public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory {

    private static final Logger log = LoggerFactory.getLogger(AuthGatewayFilterFactory.class);
    private static final String ROLE_KEY = "role";
    
    public AuthGatewayFilterFactory() {
        super(Config.class);
    }
    
    // 定义过滤器工厂的配置类,用于接收配置参数
    public static class Config {
        private String requiredRole; // 过滤器需要的一个配置参数
        // Getters and Setters
        public String getRequiredRole() {
            return requiredRole;
        }
        public void setRequiredRole(String requiredRole) {
            this.requiredRole = requiredRole;
        }
    }
    
    // 获取用于保存配置参数的字段名列表
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(ROLE_KEY);
    }
    
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            log.info("AuthGatewayFilterFactory pre: checking role = {}", config.getRequiredRole());
    
            // 简单的认证逻辑:检查请求头中是否包含 X-User-Role 且与配置的 requiredRole 匹配
            String userRole = exchange.getRequest().getHeaders().getFirst("X-User-Role");
            if (userRole == null || !userRole.equals(config.getRequiredRole())) {
                log.warn("AuthGatewayFilterFactory: Unauthorized access for role {}", config.getRequiredRole());
                exchange.getResponse().setRawStatusCode(401); // 设置未授权状态码
                return exchange.getResponse().setComplete(); // 终止请求
            }
    
            // 继续处理请求
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                log.info("AuthGatewayFilterFactory post: role check passed for {}", config.getRequiredRole());
            }));
        };
    }
    

    }
    2. **在 `application.yml` 中使用:**yaml
    spring:
    cloud:
    gateway:
    routes:
    – id: secure_user_route
    uri: http://localhost:8081
    predicates:
    – Path=/secure/users/**
    filters:
    – StripPrefix=1
    # 使用自定义过滤器,参数是 requiredRole
    – Auth=admin # 这里 Auth 就是过滤器工厂的 Bean Name (AuthGatewayFilterFactory -> Auth)
    # admin 会作为 requiredRole 传递给 Config
    ``
    * 请求
    http://localhost:8080/secure/users/1如果请求头X-User-Role不为admin`,则会被拦截,返回 401。

7. 集成服务注册与发现 (Eureka)

在真实的微服务场景中,后端服务的实例数量、IP地址和端口可能会动态变化。使用服务注册与发现中心(如 Eureka、Consul、Nacos)是必不可少的。这里以 Eureka 为例。

1. 搭建 Eureka Server

  • 创建新的 Spring Boot 项目:
    • Artifact: eureka-server
    • 依赖: Eureka Server
  • 修改 EurekaServerApplication.java:
    “`java
    package com.example.gateway.eurekaserver;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

    @SpringBootApplication
    @EnableEurekaServer // 启用 Eureka Server
    public class EurekaServerApplication {
    public static void main(String[] args) {
    SpringApplication.run(EurekaServerApplication.class, args);
    }
    }
    * **配置 `application.yml`:**yaml
    server:
    port: 8761 # Eureka Server 默认端口
    eureka:
    instance:
    hostname: localhost # Eureka Server 的主机名
    client:
    register-with-eureka: false # Eureka Server 自己不注册到 Eureka
    fetch-registry: false # Eureka Server 不从 Eureka 获取注册信息
    service-url:
    defaultZone: http://localhost:8761/eureka/ # Eureka Server 的注册地址
    ``
    * **启动
    EurekaServerApplication。** 访问http://localhost:8761` 可以在 Eureka 管理界面看到服务注册情况。

2. 微服务注册到 Eureka

修改 user-serviceproduct-service,使它们注册到 Eureka Server。

  • 添加依赖 (pom.xml):
    xml
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  • 修改 UserServiceApplication.javaProductServiceApplication.java:
    java
    // 在主类上添加 @EnableDiscoveryClient 或 @EnableEurekaClient
    @SpringBootApplication
    @EnableDiscoveryClient // 或者 @EnableEurekaClient
    public class UserServiceApplication {
    public static void main(String[] args) {
    SpringApplication.run(UserServiceApplication.class, args);
    }
    }
  • 修改 user-serviceapplication.yml:
    yaml
    server:
    port: 8081
    spring:
    application:
    name: user-service # 服务名称,必须与 Eureka 注册一致
    eureka:
    client:
    service-url:
    defaultZone: http://localhost:8761/eureka/ # Eureka Server 地址
    instance:
    prefer-ip-address: true # 优先使用 IP 地址注册
  • 修改 product-serviceapplication.yml (类似 user-service,端口 8082,服务名 product-service)。
  • 重启 UserServiceProductService 刷新 http://localhost:8761,您应该能看到 USER-SERVICEPRODUCT-SERVICE 注册成功。

3. Gateway 通过 Eureka 动态路由

现在让 Gateway 通过服务名称而不是固定 IP:Port 来路由。

  • gateway-service 添加依赖 (pom.xml):
    xml
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 如果是 Spring Boot 3.x,Spring Cloud LoadBalancer 已取代 Ribbon,通常已包含在 eureka-client 中 -->
    <!-- <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency> -->
  • 修改 GatewayServiceApplication.java:
    “`java
    package com.example.gateway.gatewayservice;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient; // 启用服务发现客户端

    @SpringBootApplication
    @EnableDiscoveryClient
    public class GatewayServiceApplication {
    public static void main(String[] args) {
    SpringApplication.run(GatewayServiceApplication.class, args);
    }
    }
    * **修改 `gateway-service` 的 `application.yml`:**yaml
    server:
    port: 8080
    spring:
    application:
    name: gateway-service
    cloud:
    gateway:
    routes:
    – id: user_service_route
    # uri: http://localhost:8081 # 旧的直接地址
    uri: lb://USER-SERVICE # 通过服务发现路由,lb:// 表示 LoadBalancer
    predicates:
    – Path=/user/**
    filters:
    – StripPrefix=1

        - id: product_service_route
          # uri: http://localhost:8082
          uri: lb://PRODUCT-SERVICE
          predicates:
            - Path=/product/**
          filters:
            - StripPrefix=1
    

    eureka:
    client:
    service-url:
    defaultZone: http://localhost:8761/eureka/ # 指向 Eureka Server
    ``
    * **重启
    GatewayService。** 再次测试http://localhost:8080/user/1http://localhost:8080/product/1。现在网关会从 Eureka 获取USER-SERVICEPRODUCT-SERVICE` 的可用实例列表,并通过负载均衡器选择一个实例进行转发。

4. 负载均衡 (LoadBalancer)

uri 使用 lb://SERVICE-ID 格式时,Spring Cloud Gateway 会自动集成 Spring Cloud LoadBalancer(在 Spring Cloud 2020.x 之后取代了 Ribbon)。LoadBalancer 会从服务注册中心获取服务实例列表,并使用默认的负载均衡策略(如轮询)来分发请求。

这意味着即使您启动多个 UserService 实例(例如一个在 8081,另一个在 8083),Gateway 也能自动发现它们并轮流将请求分发过去。

8. 网关安全防护与鉴权

API 网关是所有请求的入口,因此在此处进行统一的安全防护和鉴权至关重要。

1. 集成 Spring Security

您可以在 Gateway 中集成 Spring Security 来实现认证和授权。

  • 添加依赖:
    xml
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
  • 配置 (SecurityConfig.java):
    “`java
    package com.example.gateway.gatewayservice;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.Customizer;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.config.web.server.ServerHttpSecurity;
    import org.springframework.security.web.server.SecurityWebFilterChain;

    @Configuration
    @EnableWebFluxSecurity // 启用 WebFlux 安全
    public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
            .csrf(ServerHttpSecurity.CsrfSpec::disable) // 禁用 CSRF
            .authorizeExchange(exchanges -> exchanges
                .pathMatchers("/public/**").permitAll() // 允许 /public/** 路径匿名访问
                .pathMatchers("/actuator/**").permitAll() // 允许 Actuator 路径匿名访问
                .pathMatchers("/user/**").authenticated() // /user/** 路径需要认证
                .pathMatchers("/product/**").hasRole("ADMIN") // /product/** 路径需要 ADMIN 角色
                .anyExchange().authenticated() // 其他所有路径都需要认证
            )
            .httpBasic(Customizer.withDefaults()); // 使用 HTTP Basic 认证
        return http.build();
    }
    

    }
    ``
    *
    @EnableWebFluxSecurity启用 WebFlux 风格的安全配置。
    * 配置了路径匹配和访问权限:
    /public/允许所有访问,/user/需要认证,/product/需要ADMIN角色。
    *
    httpBasic(Customizer.withDefaults())启用了基本的 HTTP Basic 认证,您可以通过spring.security.user.namespring.security.user.passwordapplication.yml` 中配置用户。
    *
    注意:** 实际生产中通常使用 JWT 或 OAuth2 等更安全的认证机制。

2. JWT 认证流程与网关鉴权

在微服务中,JWT (JSON Web Token) 是一种常见的认证方式。

  • 流程概述:

    1. 用户登录: 客户端向认证服务(或网关的认证接口)发送登录请求。
    2. 生成 JWT: 认证服务验证用户凭据,生成 JWT,并将其返回给客户端。
    3. 携带 JWT: 客户端在后续请求中,将 JWT 放在 Authorization 请求头中(Bearer Token 格式)。
    4. 网关鉴权: Spring Cloud Gateway 收到请求后,通过自定义 Global Filter 拦截请求:
      • Authorization 头中提取 JWT。
      • 验证 JWT 的签名、有效期等(可能需要公钥或共享密钥)。
      • 从 JWT 中解析出用户信息(如用户ID、角色、权限)。
      • 将用户信息(或权限列表)注入到请求头中,传递给下游微服务。
      • 根据 JWT 中的权限信息判断是否有权访问目标路由。
    5. 微服务鉴权: 下游微服务接收到请求后,可以信任网关注入的权限信息,或者再次进行更细粒度的鉴权。
  • 实现思路 (Global Filter):
    “`java
    // 假设您有一个 JwtUtil 工具类用于解析和验证 JWT
    @Component
    public class JwtAuthenticationFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
    
        // 如果是认证服务的路径,直接放行 (或不进行 JWT 校验)
        if (path.startsWith("/auth")) {
            return chain.filter(exchange);
        }
    
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            log.warn("Missing or invalid Authorization header for path: {}", path);
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    
        String token = authHeader.substring(7); // 提取 JWT
        try {
            // 假设 JwtUtil.validateToken() 验证 JWT 并返回用户信息
            // 这里的验证逻辑需要您自己实现,例如通过 RSA 公钥验证签名
            // Map<String, Object> claims = JwtUtil.validateToken(token);
            // String userId = (String) claims.get("userId");
            // List<String> roles = (List<String>) claims.get("roles");
    
            // 将用户信息添加到请求头,传递给下游服务
            // ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
            //     .header("X-User-Id", userId)
            //     .header("X-User-Roles", String.join(",", roles))
            //     .build();
            // return chain.filter(exchange.mutate().request(mutatedRequest).build());
    
            // 简化:如果 token 有效,直接放行
            log.info("JWT token validated successfully for path: {}", path);
            return chain.filter(exchange);
    
        } catch (Exception e) {
            log.error("JWT validation failed for path: {}. Error: {}", path, e.getMessage());
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
    
    @Override
    public int getOrder() {
        // 在 Spring Security 的认证过滤器之前执行
        return -200; // 假设 Spring Security 默认是 -100
    }
    

    }
    ``
    * 在
    SecurityConfig中,您可能需要将 JWT 相关的路径设置为permitAll()以便在JwtAuthenticationFilter` 中处理。

3. CORS 跨域配置

当前端应用与网关部署在不同域名/端口时,需要处理跨域请求。

  • Java API 配置 (推荐):
    “`java
    package com.example.gateway.gatewayservice;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.reactive.CorsWebFilter;
    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

    import java.util.Arrays;
    import java.util.Collections;

    @Configuration
    public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfig = new CorsConfiguration();
        corsConfig.setAllowedOrigins(Collections.singletonList("*")); // 允许所有来源 (生产环境请具体指定)
        corsConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的HTTP方法
        corsConfig.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头
        corsConfig.setAllowCredentials(true); // 允许发送 Cookie
        corsConfig.setMaxAge(3600L); // 预检请求的缓存时间
    
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig); // 对所有路径生效
    
        return new CorsWebFilter(source);
    }
    

    }
    “`

9. 监控、日志与错误处理

网关作为流量入口,其本身的健康状态、性能指标以及请求日志都至关重要。

1. Spring Boot Actuator

Actuator 提供了生产级别的功能,如监控和管理您的应用程序。

  • 添加依赖:
    xml
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  • 配置 (application.yml):
    yaml
    management:
    endpoints:
    web:
    exposure:
    include: "*" # 暴露所有 Actuator 端点 (生产环境请谨慎)
    endpoint:
    health:
    show-details: always # 显示健康检查详情
    gateway:
    enabled: true # 启用 Gateway 相关的 Actuator 端点
  • 常用端点:
    • http://localhost:8080/actuator/health: 查看网关健康状态。
    • http://localhost:8080/actuator/gateway/routes: 查看所有路由定义。
    • http://localhost:8080/actuator/gateway/routes/{id}: 查看特定路由详情。
    • http://localhost:8080/actuator/gateway/globalfilters: 查看全局过滤器。
    • http://localhost:8080/actuator/gateway/routefilters: 查看路由过滤器工厂。

2. 分布式链路追踪 (Spring Cloud Sleuth & Zipkin)

在微服务架构中,一个请求可能经过多个服务。分布式链路追踪可以帮助我们追踪请求在各个服务之间的流转,方便排查问题。

  • 添加依赖 (Gateway 和所有微服务):
    xml
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId> <!-- Spring Boot 3.x 使用 micrometer-tracing-bridge-brave -->
    </dependency>
    <dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
    <dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
    </dependency>
  • 配置 (application.yml):
    yaml
    spring:
    application:
    name: gateway-service # 每个服务都需要有唯一的名称
    zipkin:
    base-url: http://localhost:9411 # Zipkin Server 地址
    sleuth: # Spring Boot 3.x 移到 management.tracing
    propagation:
    type: B3 # 推荐使用 B3 或 W3C tracecontext 传播格式
    management:
    tracing:
    enabled: true
    sampling:
    probability: 1.0 # 采样率,1.0 表示 100% 采样

    • 启动 Zipkin Server(通常以 Docker 方式运行:docker run -p 9411:9411 openzipkin/zipkin)。
    • 当请求通过网关和微服务时,Sleuth 会自动在请求头中注入 Trace ID 和 Span ID,Zipkin 会收集这些追踪信息并可视化。

3. 自定义错误页面与异常处理

当路由失败、后端服务不可用或发生其他网关内部错误时,Spring Cloud Gateway 默认会返回一个 JSON 格式的错误响应。您可以自定义错误页面或错误处理逻辑。

  • 方式一:自定义错误页面 (HTML)
    Spring Boot 的默认错误处理机制。创建一个 error 目录,并在其中放置错误页面:

    • src/main/resources/public/error/404.html
    • src/main/resources/public/error/5xx.html
    • src/main/resources/public/error.html (通用错误页)
  • 方式二:自定义错误处理 Bean
    实现 ErrorWebExceptionHandler 接口,可以捕获所有在网关层面发生的异常,并自定义响应。

    “`java
    package com.example.gateway.gatewayservice;

    import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.core.io.buffer.DataBufferFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.web.server.ResponseStatusException;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;

    import java.util.HashMap;
    import java.util.Map;

    @Configuration
    @Order(-1) // 优先级要高于 Spring Boot 默认的异常处理器
    public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }
    
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatusCode());
        } else {
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        // 构建统一的错误响应体
        Map<String, Object> errorAttributes = new HashMap<>();
        errorAttributes.put("timestamp", System.currentTimeMillis());
        errorAttributes.put("path", exchange.getRequest().getPath().value());
        errorAttributes.put("status", response.getStatusCode().value());
        errorAttributes.put("error", response.getStatusCode().getReasonPhrase());
        errorAttributes.put("message", ex.getMessage());
    
        try {
            // 将错误信息写入响应体
            return response.writeWith(Mono.just(response.bufferFactory().wrap(objectMapper.writeValueAsBytes(errorAttributes))));
        } catch (JsonProcessingException e) {
            return Mono.error(e);
        }
    }
    

    }
    “`
    这个处理器将捕获所有异常并返回一个统一的 JSON 格式错误响应。

10. 高级特性与最佳实践

1. WebSocket 代理

Spring Cloud Gateway 支持 WebSocket 代理。您只需在路由 uri 中使用 ws://wss:// 协议。

yaml
spring:
cloud:
gateway:
routes:
- id: websocket_route
uri: ws://localhost:8083 # WebSocket 服务地址
predicates:
- Path=/ws/**

2. 高可用部署

为了防止单点故障,Spring Cloud Gateway 应该部署为多个实例,并配合负载均衡器(如 Nginx、硬件负载均衡器、Kubernetes Service)进行流量分发。由于它本身是无状态的(限流等状态可以存储在 Redis 中),因此可以水平扩展。

3. 配置管理 (Spring Cloud Config)

在复杂的微服务系统中,路由规则可能会很多,并且需要动态调整。可以将网关的配置(特别是 spring.cloud.gateway.routes 部分)存储在 Spring Cloud Config Server 或 Nacos Config 中。

  • 添加依赖:
    xml
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    <!-- 如果使用 Nacos -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
  • 配置 (bootstrap.yml):
    yaml
    spring:
    application:
    name: gateway-service
    cloud:
    config:
    discovery:
    enabled: true
    service-id: config-server # Config Server 在 Eureka 中的服务ID
    uri: http://localhost:8888 # 或者直接指定 Config Server 地址

    配置 Gateway 客户端,从 Config Server 加载路由配置。

4. 性能优化

  • 增加 JVM 内存: 根据实际负载调整 JVM 堆大小。
  • 优化系统参数: 调整操作系统文件描述符限制、TCP 参数等。
  • 并发连接数: 根据需要调整 Netty 的工作线程数。
  • 缓存: 对静态资源或不常变动的响应进行缓存。
  • 限流与熔断: 合理配置限流和熔断,防止后端服务过载,从而间接提高网关的稳定性。

5. Spring Cloud Gateway vs. Zuul

特性 Spring Cloud Gateway Netflix Zuul 1.x
基础框架 基于 Spring 5, Spring Boot 2, Project Reactor, Netty 基于 Servlet 3.1, Apache HttpClient
I/O 模型 非阻塞、异步、反应式 I/O 阻塞式 I/O
性能 更高吞吐量,低延迟,适合高并发 吞吐量和并发处理能力有限
编程模型 反应式编程 (WebFlux) 同步编程
扩展性 路由断言和过滤器高度可定制,支持 Java API 配置 Groovy 脚本或 Java 代码自定义过滤器,配置相对复杂
功能 路由、断言、过滤器、限流、熔断 (Resilience4j) 路由、过滤器、限流、熔断 (Hystrix)
社区支持 Spring Cloud 官方主推,活跃开发中 Netflix 已不再维护 Zuul 1.x,社区活跃度低
适用场景 现代化微服务架构,对性能和并发有高要求 遗留系统,或对性能要求不高的场景

结论: 在大多数新项目中,强烈推荐使用 Spring Cloud Gateway。它在性能、可伸缩性和与 Spring Cloud 生态的集成方面都远超 Zuul 1.x。Zuul 2.x 虽是异步非阻塞,但并未集成到 Spring Cloud 主线,且维护情况不如 Gateway。

11. 总结与展望

通过本指南,我们详细探讨了 Spring Cloud Gateway 的核心概念、快速搭建方法、路由配置、断言与过滤器的使用、服务注册与发现集成、安全防护、监控以及一些高级特性和最佳实践。

Spring Cloud Gateway 作为 Spring Cloud 生态中的新一代 API 网关,凭借其响应式、高性能、易扩展的特点,已成为构建微服务架构不可或缺的关键组件。它不仅能统一管理请求入口,提供强大的路由和过滤功能,还能与服务注册中心、负载均衡、断路器、分布式追踪等 Spring Cloud 组件无缝集成,极大地简化了微服务治理的复杂性。

随着微服务架构的不断演进,API 网关的功能也会越来越强大。未来,我们可以期待 Spring Cloud Gateway 在以下方面有更深入的发展:
* 更智能的流量管理,如基于 AI/机器学习的动态路由。
* 更丰富的协议支持,如 gRPC 代理。
* 更完善的安全性特性,如 WebApplication Firewall (WAF) 集成。
* 更友好的可观测性,与 OpenTelemetry 等标准深度融合。

掌握 Spring Cloud Gateway,您就拥有了构建健壮、高效、可扩展微服务系统的利器。希望这篇指南能帮助您快速入门并深入实践,祝您的微服务之旅一帆风顺!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部