深入浅出 Spring Cloud Gateway:从入门到精通的网关实战指南

引言:为什么每个微服务架构都需要 Spring Cloud Gateway?

在微服务架构盛行的今天,当系统被拆分为数十甚至上百个微服务时,一个新的挑战出现了:如何有效地管理这些服务的入口?如何处理认证授权、流量控制、日志监控等横切关注点?如何让客户端能够简单地与众多微服务交互而无需了解其具体位置?

Spring Cloud Gateway 应运而生,作为 Spring Cloud 生态系统中的网关组件,它不仅解决了上述问题,还提供了更强大的功能和更好的性能。相比之前的 Zuul 网关,Spring Cloud Gateway 基于 ***ty 和 WebFlux 构建,采用非阻塞响应式编程模型,性能提升显著,同时提供了更丰富的功能和更灵活的配置方式。

本文将带你全面深入地了解 Spring Cloud Gateway,从核心概念到底层原理,从环境搭建到实战应用,让你真正掌握这一微服务架构的 "门户"。无论你是刚接触微服务的新手,还是寻求进阶的资深开发者,都能从本文中获益。

一、Spring Cloud Gateway 核心概念与架构解析

1.1 什么是 Spring Cloud Gateway?

Spring Cloud Gateway 是 Spring 官方推出的基于 Spring 5、Spring Boot 2 和 Project Reactor 的网关解决方案,旨在为微服务架构提供一种简单而有效的统一入口点。它提供了路由转发、负载均衡、熔断、限流、安全认证等一系列功能,是构建微服务架构不可或缺的组件。

Spring Cloud Gateway 的核心目标是:

  • 提供统一的路由方式
  • 基于 Filter 链的方式提供网关的基本功能
  • 集成 Spring 生态系统,与 Spring Boot 2.x、Spring Cloud 等无缝集成
  • 支持动态路由配置
  • 提供异步非阻塞的高性能体验

1.2 核心概念

在使用 Spring Cloud Gateway 之前,我们需要理解几个核心概念:

  1. 路由(Route):路由是网关的基本构建块,它由 ID、目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。当断言为真时,路由匹配成功,请求将被转发到目标 URI。

  2. 断言(Predicate):这是一个 Java 8 的 Function Predicate,用于匹配 HTTP 请求的各种属性,如路径、方法、请求头、参数等。所有断言都必须为真,路由才会被匹配。

  3. 过滤器(Filter):过滤器是在请求被路由之前或之后执行的操作,可以修改请求或响应。Spring Cloud Gateway 提供了两种类型的过滤器:GatewayFilter(针对特定路由)和 GlobalFilter(针对所有路由)。

  4. 网关(Gateway):由一系列路由、断言和过滤器组成,负责接收所有客户端请求,根据断言进行路由匹配,应用相应的过滤器,最后将请求转发到目标服务。

1.3 架构设计

Spring Cloud Gateway 的架构设计充分利用了响应式编程的优势,基于 ***ty 和 WebFlux 构建,实现了非阻塞的请求处理。其核心架构如图所示:

核心组件说明:

  1. DispatcherHandler:作为请求的入口点,负责将请求分发到相应的处理器。

  2. RoutePredicateHandlerMapping:根据路由断言查找匹配的路由。

  3. FilterWebHandler:负责执行路由关联的过滤器链,并最终将请求转发到目标服务。

  4. ***tyRoutingFilter:基于 ***ty 的 HTTP 客户端,负责将请求转发到目标服务并接收响应。

  5. 过滤器链:由多个过滤器组成,分为 "前置" 和 "后置" 阶段,分别在请求转发前和响应返回后执行。

1.4 工作原理

Spring Cloud Gateway 的工作流程可以概括为以下几个步骤:

  1. 客户端发送请求到 Spring Cloud Gateway。
  2. Gateway 接收请求后,由 DispatcherHandler 将请求转发给 RoutePredicateHandlerMapping。
  3. RoutePredicateHandlerMapping 根据请求的属性(如路径、方法等)匹配最合适的路由。
  4. 如果找到匹配的路由,请求将被发送到 FilterWebHandler。
  5. FilterWebHandler 创建一个包含所有相关过滤器的过滤器链,并按顺序执行 "前置" 过滤器。
  6. 过滤器链执行完成后,请求被转发到目标服务。
  7. 目标服务处理请求并返回响应。
  8. 响应返回后,FilterWebHandler 执行所有 "后置" 过滤器。
  9. 最后,响应被返回给客户端。

如果没有找到匹配的路由,Gateway 将返回 404 Not Found 响应。

二、Spring Cloud Gateway 环境搭建与入门示例

2.1 准备工作

在开始使用 Spring Cloud Gateway 之前,我们需要准备以下环境:

  • JDK 17+(推荐使用 JDK 17)
  • Maven 3.6+
  • Spring Boot 3.2.0+
  • Spring Cloud 2023.0.0+

2.2 创建基础网关项目

2.2.1 创建 Maven 项目,添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>***.example</groupId>
    <artifactId>spring-cloud-gateway-demo</artifactId>
    <version>1.0.0</version>
    <name>spring-cloud-gateway-demo</name>
    <description>Spring Cloud Gateway Demo Project</description>

    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.0</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- Spring Cloud Gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
            <optional>true</optional>
        </dependency>
        
        <!-- Spring Boot Starter Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- Reactor Test -->
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <!-- Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
            <version>2.3.0</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Cloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

注意:Spring Cloud Gateway 依赖于 Spring WebFlux,而不是传统的 Spring Web MVC。因此,在网关项目中不应添加 spring-boot-starter-web 依赖,否则会导致冲突。

2.2.2 创建启动类
package ***.example.gateway;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Spring Cloud Gateway示例应用启动类
 *
 * @author ken
 */
@SpringBootApplication
@OpenAPIDefinition(
    info = @Info(
        title = "Spring Cloud Gateway示例API",
        version = "1.0.0",
        description = "Spring Cloud Gateway功能演示API文档"
    )
)
public class GatewayApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

2.3 创建测试用的微服务

为了演示网关的功能,我们需要创建两个简单的微服务:用户服务和订单服务。

2.3.1 用户服务

1. 创建用户服务的 Maven 依赖

<!-- 仅列出核心依赖,其他基础依赖与网关项目类似 -->
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <optional>true</optional>
    </dependency>
    
    <!-- Swagger3 -->
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
        <version>2.3.0</version>
    </dependency>
</dependencies>

2. 创建用户实体类

package ***.example.userservice.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@Schema(description = "用户实体")
public class User {
    /**
     * 用户ID
     */
    @Schema(description = "用户ID")
    private Long id;
    
    /**
     * 用户名
     */
    @Schema(description = "用户名")
    private String username;
    
    /**
     * 用户年龄
     */
    @Schema(description = "用户年龄")
    private Integer age;
    
    /**
     * 用户邮箱
     */
    @Schema(description = "用户邮箱")
    private String email;
}

3. 创建用户服务控制器

package ***.example.userservice.controller;

import ***.example.userservice.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户服务控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/users")
@Slf4j
@Tag(name = "用户服务接口", description = "提供用户相关操作的接口")
public class UserController {
    
    /**
     * 模拟数据库存储用户信息
     */
    private static final Map<Long, User> USER_MAP = new ConcurrentHashMap<>();
    
    /**
     * 当前服务端口
     */
    @Value("${server.port}")
    private String serverPort;
    
    static {
        // 初始化测试数据
        User user1 = new User();
        user1.setId(1L);
        user1.setUsername("张三");
        user1.setAge(25);
        user1.setEmail("zhangsan@example.***");
        USER_MAP.put(1L, user1);
        
        User user2 = new User();
        user2.setId(2L);
        user2.setUsername("李四");
        user2.setAge(30);
        user2.setEmail("lisi@example.***");
        USER_MAP.put(2L, user2);
    }
    
    /**
     * 根据ID查询用户
     *
     * @param id 用户ID
     * @return 用户信息
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询用户", description = "根据用户ID查询用户详细信息")
    public User getUserById(
            @Parameter(description = "用户ID", required = true)
            @PathVariable Long id) {
        log.info("查询用户信息,ID: {}, 服务端口: {}", id, serverPort);
        return USER_MAP.get(id);
    }
    
    /**
     * 创建用户
     *
     * @param user 用户信息
     * @return 创建的用户信息
     */
    @PostMapping
    @Operation(summary = "创建用户", description = "创建新用户并返回用户信息")
    public User createUser(
            @Parameter(description = "用户信息", required = true)
            @RequestBody User user) {
        log.info("创建用户,用户信息: {}, 服务端口: {}", user, serverPort);
        USER_MAP.put(user.getId(), user);
        return user;
    }
    
    /**
     * 获取服务信息
     *
     * @return 服务信息
     */
    @GetMapping("/service-info")
    @Operation(summary = "获取服务信息", description = "获取当前服务的信息")
    public Map<String, String> getServiceInfo() {
        Map<String, String> info = new HashMap<>(2);
        info.put("serviceName", "user-service");
        info.put("serverPort", serverPort);
        log.info("获取服务信息: {}", info);
        return info;
    }
}

4. 创建用户服务启动类

package ***.example.userservice;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 用户服务启动类
 *
 * @author ken
 */
@SpringBootApplication
@OpenAPIDefinition(
    info = @Info(
        title = "用户服务API",
        version = "1.0.0",
        description = "用户服务的API文档"
    )
)
public class UserServiceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

5. 创建配置文件 application.properties

# 服务端口
server.port=8081

# 应用名称
spring.application.name=user-service
2.3.2 订单服务

订单服务的结构与用户服务类似,这里只列出核心代码:

1. 订单实体类

package ***.example.orderservice.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * 订单实体类
 *
 * @author ken
 */
@Data
@Schema(description = "订单实体")
public class Order {
    /**
     * 订单ID
     */
    @Schema(description = "订单ID")
    private Long id;
    
    /**
     * 用户ID
     */
    @Schema(description = "用户ID")
    private Long userId;
    
    /**
     * 商品名称
     */
    @Schema(description = "商品名称")
    private String productName;
    
    /**
     * 订单金额
     */
    @Schema(description = "订单金额")
    private Double amount;
}

2. 订单服务控制器

package ***.example.orderservice.controller;

import ***.example.orderservice.entity.Order;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 订单服务控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/orders")
@Slf4j
@Tag(name = "订单服务接口", description = "提供订单相关操作的接口")
public class OrderController {
    
    /**
     * 模拟数据库存储订单信息
     */
    private static final Map<Long, Order> ORDER_MAP = new ConcurrentHashMap<>();
    
    /**
     * 订单ID生成器
     */
    private static final AtomicLong ORDER_ID_GENERATOR = new AtomicLong(1000);
    
    /**
     * 当前服务端口
     */
    @Value("${server.port}")
    private String serverPort;
    
    /**
     * 根据ID查询订单
     *
     * @param id 订单ID
     * @return 订单信息
     */
    @GetMapping("/{id}")
    @Operation(summary = "根据ID查询订单", description = "根据订单ID查询订单详细信息")
    public Order getOrderById(
            @Parameter(description = "订单ID", required = true)
            @PathVariable Long id) {
        log.info("查询订单信息,ID: {}, 服务端口: {}", id, serverPort);
        return ORDER_MAP.get(id);
    }
    
    /**
     * 创建订单
     *
     * @param order 订单信息
     * @return 创建的订单信息
     */
    @PostMapping
    @Operation(summary = "创建订单", description = "创建新订单并返回订单信息")
    public Order createOrder(
            @Parameter(description = "订单信息", required = true)
            @RequestBody Order order) {
        long orderId = ORDER_ID_GENERATOR.incrementAndGet();
        order.setId(orderId);
        ORDER_MAP.put(orderId, order);
        log.info("创建订单,订单信息: {}, 服务端口: {}", order, serverPort);
        return order;
    }
    
    /**
     * 获取服务信息
     *
     * @return 服务信息
     */
    @GetMapping("/service-info")
    @Operation(summary = "获取服务信息", description = "获取当前服务的信息")
    public Map<String, String> getServiceInfo() {
        Map<String, String> info = new HashMap<>(2);
        info.put("serviceName", "order-service");
        info.put("serverPort", serverPort);
        log.info("获取服务信息: {}", info);
        return info;
    }
}

3. 配置文件 application.properties

# 服务端口
server.port=8082

# 应用名称
spring.application.name=order-service

2.4 配置基础路由

现在我们已经有了两个微服务,接下来配置 Spring Cloud Gateway,实现请求的路由转发。

2.4.1 使用配置文件配置路由

在网关项目的 src/main/resources 目录下创建 application.yml 文件:

server:
  port: 8080  # 网关端口

spring:
  cloud:
    gateway:
      routes:
        # 用户服务路由
        - id: user-service-route
          uri: http://localhost:8081  # 用户服务地址
          predicates:
            - Path=/api/users/**  # 匹配路径
          filters:
            - StripPrefix=1  # 移除路径中的第一个前缀(即/api)
            
        # 订单服务路由
        - id: order-service-route
          uri: http://localhost:8082  # 订单服务地址
          predicates:
            - Path=/api/orders/**  # 匹配路径
          filters:
            - StripPrefix=1  # 移除路径中的第一个前缀(即/api)
2.4.2 测试路由功能
  1. 启动用户服务(端口 8081)
  2. 启动订单服务(端口 8082)
  3. 启动网关服务(端口 8080)
  4. 测试路由功能:
    • 访问 http://localhost:8080/api/users/1,应该返回 ID 为 1 的用户信息
    • 访问 http://localhost:8080/api/orders/service-info,应该返回订单服务的信息

如果一切正常,说明网关已经成功将请求转发到对应的微服务。

2.4.3 使用 Java 代码配置路由

除了使用配置文件,我们还可以使用 Java 代码配置路由:

package ***.example.gateway.config;

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;

/**
 * 网关路由配置类
 *
 * @author ken
 */
@Configuration
public class GatewayRouteConfig {
    
    /**
     * 配置路由规则
     *
     * @param builder 路由构建器
     * @return 路由定位器
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                // 用户服务路由
                .route("user-service-route", r -> r
                        .path("/api/users/**")
                        .filters(f -> f.stripPrefix(1))
                        .uri("http://localhost:8081"))
                
                // 订单服务路由
                .route("order-service-route", r -> r
                        .path("/api/orders/**")
                        .filters(f -> f.stripPrefix(1))
                        .uri("http://localhost:8082"))
                
                .build();
    }
}

注意:如果同时存在配置文件和 Java 代码配置的路由,两者都会生效。在实际项目中,建议根据需求选择一种配置方式,或结合使用。

三、Spring Cloud Gateway 核心功能详解

3.1 路由断言(Predicate)详解

路由断言用于判断请求是否匹配某个路由,Spring Cloud Gateway 提供了多种内置的断言工厂,可以满足大部分场景需求。

3.1.1 路径断言(Path Predicate)

路径断言根据请求路径进行匹配,是最常用的断言之一。

spring:
  cloud:
    gateway:
      routes:
        - id: path-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**,/api/users/**  # 匹配多个路径
3.1.2 方法断言(Method Predicate)

方法断言根据 HTTP 请求方法进行匹配。

spring:
  cloud:
    gateway:
      routes:
        - id: method-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            - Method=GET,POST  # 只匹配GET和POST方法
3.1.3 请求头断言(Header Predicate)

请求头断言根据请求头信息进行匹配。

spring:
  cloud:
    gateway:
      routes:
        - id: header-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            - Header=X-Request-Id, \d+  # 匹配X-Request-Id头,值为数字
3.1.4 请求参数断言(Query Predicate)

请求参数断言根据请求参数进行匹配。

spring:
  cloud:
    gateway:
      routes:
        - id: query-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            - Query=name  # 匹配包含name参数的请求
            # - Query=name, ^[a-zA-Z]+$  # 匹配name参数且值为字母的请求
3.1.5 时间断言(Time Predicate)

时间断言根据时间进行匹配,有三种类型:

  • After:在指定时间之后
  • Before:在指定时间之前
  • Between:在两个时间之间
spring:
  cloud:
    gateway:
      routes:
        - id: time-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            # 在2023年1月1日之后
            - After=2023-01-01T00:00:00+08:00[Asia/Shanghai]
            # 在2024年1月1日之前
            # - Before=2024-01-01T00:00:00+08:00[Asia/Shanghai]
            # 在两个时间之间
            # - Between=2023-01-01T00:00:00+08:00[Asia/Shanghai], 2024-01-01T00:00:00+08:00[Asia/Shanghai]
3.1.6 远程地址断言(RemoteAddr Predicate)

远程地址断言根据客户端 IP 地址进行匹配。

spring:
  cloud:
    gateway:
      routes:
        - id: remoteaddr-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            - RemoteAddr=192.168.1.0/24,10.0.0.0/8  # 匹配指定网段的IP
3.1.7 组合断言

一个路由可以配置多个断言,只有当所有断言都匹配时,路由才会被选中。

spring:
  cloud:
    gateway:
      routes:
        - id: ***posite-predicate-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
            - Method=GET
            - Header=A***ept, application/json
            - Query=format, json

3.2 过滤器(Filter)详解

过滤器是 Spring Cloud Gateway 的另一个核心组件,用于在请求被路由前后修改请求或响应。Spring Cloud Gateway 提供了丰富的内置过滤器,同时也支持自定义过滤器。

3.2.1 过滤器的类型

Spring Cloud Gateway 的过滤器分为两种类型:

  1. GatewayFilter:应用于特定路由的过滤器,需要在路由配置中显式声明。
  2. GlobalFilter:应用于所有路由的全局过滤器,不需要在路由中配置,会自动生效。

此外,过滤器还可以根据执行时机分为:

  • 前置过滤器:在请求被路由到目标服务之前执行
  • 后置过滤器:在目标服务返回响应之后执行
3.2.2 常用内置 GatewayFilter
  1. AddRequestHeader:添加请求头
spring:
  cloud:
    gateway:
      routes:
        - id: add-request-header-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
          filters:
            - AddRequestHeader=X-Request-From, gateway  # 添加请求头
            - StripPrefix=1
  1. AddResponseHeader:添加响应头
spring:
  cloud:
    gateway:
      routes:
        - id: add-response-header-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
          filters:
            - AddResponseHeader=X-Response-From, gateway  # 添加响应头
            - StripPrefix=1
  1. RewritePath:重写路径
spring:
  cloud:
    gateway:
      routes:
        - id: rewrite-path-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/v1/**
          filters:
            - RewritePath=/v1/(?<segment>.*), /users/$\{segment}  # 重写路径,将/v1/xxx重写为/users/xxx
  1. PrefixPath:添加路径前缀
spring:
  cloud:
    gateway:
      routes:
        - id: prefix-path-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/**
          filters:
            - PrefixPath=/users  # 为所有请求添加/users前缀
  1. RequestRateLimiter:请求限流
spring:
  cloud:
    gateway:
      routes:
        - id: request-rate-limiter-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 10  # 令牌桶填充速率(每秒)
                redis-rate-limiter.burstCapacity: 20  # 令牌桶容量
                key-resolver: "#{@userKeyResolver}"  # 限流键解析器

需要添加 Redis 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

配置限流键解析器:

package ***.example.gateway.config;

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;

/**
 * 限流配置
 *
 * @author ken
 */
@Configuration
public class RateLimiterConfig {
    
    /**
     * 用户限流键解析器,基于用户ID限流
     *
     * @return 限流键解析器
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            // 从请求参数中获取用户ID作为限流键
            String userId = exchange.getRequest().getQueryParams().getFirst("userId");
            return Mono.justOrEmpty(userId)
                    .defaultIfEmpty("anonymous");  // 匿名用户
        };
    }
    
    /**
     * IP限流键解析器,基于客户端IP限流
     *
     * @return 限流键解析器
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
}
  1. CircuitBreaker:熔断降级
spring:
  cloud:
    gateway:
      routes:
        - id: circuit-breaker-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users  # 降级回调地址

需要添加 Resilience4j 依赖:

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

创建降级回调控制器:

package ***.example.gateway.controller;

import lombok.extern.slf4j.Slf4j;
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;

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

/**
 * 降级回调控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
    
    /**
     * 用户服务降级处理
     *
     * @return 降级响应
     */
    @GetMapping("/users/{id}")
    public Map<String, Object> userServiceFallback(@PathVariable Long id) {
        log.warn("用户服务降级,用户ID: {}", id);
        Map<String, Object> result = new HashMap<>(2);
        result.put("su***ess", false);
        result.put("message", "用户服务暂时不可用,请稍后再试");
        result.put("userId", id);
        return result;
    }
    
    /**
     * 用户服务通用降级处理
     *
     * @return 降级响应
     */
    @GetMapping("/users/**")
    public Map<String, Object> userServiceGeneralFallback() {
        log.warn("用户服务通用降级");
        Map<String, Object> result = new HashMap<>(2);
        result.put("su***ess", false);
        result.put("message", "用户服务暂时不可用,请稍后再试");
        return result;
    }
}
3.2.3 自定义 GatewayFilter

除了使用内置过滤器,我们还可以自定义 GatewayFilter 以满足特定需求。

package ***.example.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.***ponent;
import reactor.core.publisher.Mono;

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

/**
 * 自定义日志过滤器工厂
 *
 * @author ken
 */
@***ponent
@Slf4j
public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> {
    
    /**
     * 配置类,用于存储过滤器参数
     */
    public static class Config {
        /**
         * 是否记录请求体
         */
        private boolean logRequestBody;
        
        /**
         * 是否记录响应体
         */
        private boolean logResponseBody;
        
        public boolean isLogRequestBody() {
            return logRequestBody;
        }
        
        public void setLogRequestBody(boolean logRequestBody) {
            this.logRequestBody = logRequestBody;
        }
        
        public boolean isLogResponseBody() {
            return logResponseBody;
        }
        
        public void setLogResponseBody(boolean logResponseBody) {
            this.logResponseBody = logResponseBody;
        }
    }
    
    public LoggingGatewayFilterFactory() {
        super(Config.class);
    }
    
    /**
     * 解析配置参数
     *
     * @return 参数名称列表
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("logRequestBody", "logResponseBody");
    }
    
    /**
     * 创建过滤器
     *
     * @param config 过滤器配置
     * @return 网关过滤器
     */
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 前置处理:记录请求信息
            log.info("请求路径: {}", exchange.getRequest().getPath());
            log.info("请求方法: {}", exchange.getRequest().getMethod());
            log.info("请求参数: {}", exchange.getRequest().getQueryParams());
            
            // 如果需要记录请求体
            if (config.isLogRequestBody()) {
                // 注意:获取请求体需要特殊处理,这里简化处理
                log.info("记录请求体: 已开启");
            }
            
            // 继续执行过滤器链
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // 后置处理:记录响应信息
                log.info("响应状态码: {}", exchange.getResponse().getStatusCode());
                
                // 如果需要记录响应体
                if (config.isLogResponseBody()) {
                    log.info("记录响应体: 已开启");
                }
            }));
        };
    }
}

在路由中使用自定义过滤器:

spring:
  cloud:
    gateway:
      routes:
        - id: custom-filter-example
          uri: http://localhost:8081
          predicates:
            - Path=/users/**
          filters:
            - StripPrefix=1
            # 使用自定义日志过滤器,记录请求体和响应体
            - name: Logging
              args:
                logRequestBody: true
                logResponseBody: true
            # 简化写法
            # - Logging=true,true
3.2.4 自定义 GlobalFilter

全局过滤器会应用于所有路由,适合实现认证授权、日志记录等全局功能。

package ***.example.gateway.filter;

import ***.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

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

/**
 * 全局过滤器配置
 *
 * @author ken
 */
@Configuration
@Slf4j
public class GlobalFilterConfig {
    
    /**
     * 认证过滤器,检查请求中的令牌
     *
     * @return 全局过滤器
     */
    @Bean
    @Order(-100)  // 优先级,数值越小优先级越高
    public GlobalFilter authFilter() {
        return (exchange, chain) -> {
            // 获取请求路径
            String path = exchange.getRequest().getPath().toString();
            
            // 不需要认证的路径
            if (path.startsWith("/public/") || path.startsWith("/swagger-ui/") || path.startsWith("/v3/api-docs/")) {
                return chain.filter(exchange);
            }
            
            // 从请求头中获取令牌
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            
            // 验证令牌
            if (token == null || !token.startsWith("Bearer ")) {
                log.warn("未授权访问: {}", path);
                // 设置401响应
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                
                // 设置响应体
                Map<String, Object> response = new HashMap<>(2);
                response.put("su***ess", false);
                response.put("message", "未授权访问,请先登录");
                
                byte[] bytes = JSON.toJSONString(response).getBytes();
                exchange.getResponse().getHeaders().add("Content-Type", "application/json");
                
                return exchange.getResponse().writeWith(Mono.just(
                    exchange.getResponse().bufferFactory().wrap(bytes)
                ));
            }
            
            // 令牌验证通过,继续执行
            log.info("令牌验证通过: {}", path);
            return chain.filter(exchange);
        };
    }
    
    /**
     * 全局日志过滤器,记录所有请求的处理时间
     *
     * @return 全局过滤器
     */
    @Bean
    @Order(-200)
    public GlobalFilter loggingFilter() {
        return (exchange, chain) -> {
            // 记录开始时间
            long startTime = System.currentTimeMillis();
            
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                // 计算处理时间
                long duration = System.currentTimeMillis() - startTime;
                log.info("请求路径: {}, 方法: {}, 状态码: {}, 处理时间: {}ms",
                        exchange.getRequest().getPath(),
                        exchange.getRequest().getMethod(),
                        exchange.getResponse().getStatusCode(),
                        duration);
            }));
        };
    }
}

3.3 与服务发现集成

在微服务架构中,服务地址通常是动态变化的,因此需要结合服务发现组件(如 Eureka、Consul、Nacos 等)使用。Spring Cloud Gateway 可以自动从服务发现组件中获取服务实例信息,并实现负载均衡。

3.3.1 与 Nacos 集成
  1. 添加 Nacos 服务发现依赖:
<dependency>
    <groupId>***.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2023.0.0.0</version>
</dependency>
  1. 配置 Nacos 地址:
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos服务地址
  application:
    name: api-gateway  # 网关服务名称
  1. 配置基于服务名的路由:
spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true  # 开启服务发现自动路由
          lower-case-service-id: true  # 服务名转为小写
      routes:
        # 用户服务路由,使用服务名
        - id: user-service-route
          uri: lb://user-service  # lb表示使用负载均衡,user-service是服务名
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            
        # 订单服务路由,使用服务名
        - id: order-service-route
          uri: lb://order-service  # lb表示使用负载均衡,order-service是服务名
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1

注意:lb://service-name中的lb表示启用负载均衡,Spring Cloud Gateway 默认使用 Spring Cloud LoadBalancer 作为负载均衡器。

3.3.2 负载均衡配置

可以自定义负载均衡策略:

package ***.example.gateway.config;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

/**
 * 负载均衡配置
 *
 * @author ken
 */
@Configuration
public class LoadBalancerConfig {
    
    /**
     * 为用户服务配置随机负载均衡策略
     *
     * @param environment 环境变量
     * @param loadBalancerClientFactory 负载均衡客户端工厂
     * @return 随机负载均衡器
     */
    @Bean
    public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
            Environment environment,
            LoadBalancerClientFactory loadBalancerClientFactory) {
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new RandomLoadBalancer(
                loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
                name);
    }
}

在启动类上指定负载均衡配置:

@SpringBootApplication
@EnableDiscoveryClient
@LoadBalancerClient(
    name = "user-service",
    configuration = LoadBalancerConfig.class
)
public class GatewayApplication {
    // ...
}

3.4 动态路由配置

在实际生产环境中,路由配置可能需要频繁变更,动态路由功能允许我们在不重启网关的情况下更新路由配置。Spring Cloud Gateway 支持多种动态路由配置方式,如基于数据库、配置中心等。

3.4.1 基于 Nacos 配置中心的动态路由
  1. 添加 Nacos 配置中心依赖:
<dependency>
    <groupId>***.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    <version>2023.0.0.0</version>
</dependency>
  1. 创建 bootstrap.yml 配置文件:
spring:
  application:
    name: api-gateway
  cloud:
    nacos:
      config:
        server-addr: localhost:8848  # Nacos配置中心地址
        file-extension: yaml  # 配置文件格式
        group: GATEWAY_GROUP  # 配置分组
  1. 在 Nacos 控制台创建配置:
    • Data ID: api-gateway.yaml
    • Group: GATEWAY_GROUP
    • 配置内容:
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
  1. 实现动态路由刷新:
package ***.example.gateway.config;

import ***.alibaba.cloud.nacos.NacosConfigProperties;
import ***.alibaba.fastjson2.JSON;
import ***.alibaba.nacos.api.NacosFactory;
import ***.alibaba.nacos.api.config.ConfigService;
import ***.alibaba.nacos.api.config.listener.Listener;
import ***.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.***ponent;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 基于Nacos的动态路由配置
 *
 * @author ken
 */
@***ponent
@Slf4j
public class NacosDynamicRouteConfig implements ApplicationEventPublisherAware {
    
    /**
     * 路由配置Data ID
     */
    private static final String DATA_ID = "api-gateway.yaml";
    
    /**
     * 路由配置Group
     */
    private static final String GROUP = "GATEWAY_GROUP";
    
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    
    @Autowired
    private NacosConfigProperties nacosConfigProperties;
    
    private ApplicationEventPublisher publisher;
    
    /**
     * 初始化时加载路由配置
     */
    @PostConstruct
    public void init() {
        log.info("初始化动态路由配置");
        try {
            ConfigService configService = NacosFactory.createConfigService(
                    nacosConfigProperties.getServerAddr());
            
            // 加载当前配置
            String configInfo = configService.getConfig(DATA_ID, GROUP, 5000);
            this.updateRoutes(configInfo);
            
            // 监听配置变化
            configService.addListener(DATA_ID, GROUP, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("收到路由配置变更: {}", configInfo);
                    updateRoutes(configInfo);
                }
                
                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
        } catch (NacosException e) {
            log.error("初始化动态路由失败", e);
        }
    }
    
    /**
     * 更新路由配置
     *
     * @param configInfo 配置信息
     */
    private void updateRoutes(String configInfo) {
        try {
            // 解析配置
            GatewayRouteConfig gatewayRouteConfig = JSON.parseObject(
                    configInfo, GatewayRouteConfig.class);
            
            // 先清除所有路由
            routeDefinitionWriter.delete(Mono.just("*")).block();
            
            // 添加新路由
            List<RouteDefinition> routeDefinitions = gatewayRouteConfig.getSpring().getCloud().getGateway().getRoutes();
            for (RouteDefinition routeDefinition : routeDefinitions) {
                routeDefinitionWriter.save(Mono.just(routeDefinition)).block();
            }
            
            // 发布路由刷新事件
            publisher.publishEvent(new RefreshRoutesEvent(this));
            log.info("路由配置更新成功,共 {} 条路由", routeDefinitions.size());
        } catch (Exception e) {
            log.error("更新路由配置失败", e);
        }
    }
    
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
    
    /**
     * 网关路由配置包装类
     */
    public static class GatewayRouteConfig {
        private Spring spring;
        
        public Spring getSpring() {
            return spring;
        }
        
        public void setSpring(Spring spring) {
            this.spring = spring;
        }
        
        public static class Spring {
            private Cloud cloud;
            
            public Cloud getCloud() {
                return cloud;
            }
            
            public void setCloud(Cloud cloud) {
                this.cloud = cloud;
            }
            
            public static class Cloud {
                private Gateway gateway;
                
                public Gateway getGateway() {
                    return gateway;
                }
                
                public void setGateway(Gateway gateway) {
                    this.gateway = gateway;
                }
                
                public static class Gateway {
                    private List<RouteDefinition> routes;
                    
                    public List<RouteDefinition> getRoutes() {
                        return routes;
                    }
                    
                    public void setRoutes(List<RouteDefinition> routes) {
                        this.routes = routes;
                    }
                }
            }
        }
    }
}

四、Spring Cloud Gateway 高级特性与实战

4.1 跨域资源共享(CORS)配置

在前后端分离的架构中,跨域问题是常见的挑战。Spring Cloud Gateway 可以统一配置 CORS,解决跨域问题。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':  # 匹配所有路径
            allowed-origins: "*"  # 允许所有来源
            allowed-methods:  # 允许的HTTP方法
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowed-headers: "*"  # 允许的请求头
            allow-credentials: true  # 允许携带凭证
            max-age: 3600  # 预检请求的有效期(秒)

也可以通过 Java 代码配置:

package ***.example.gateway.config;

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 org.springframework.web.util.pattern.PathPatternParser;

import java.util.Arrays;

/**
 * CORS配置
 *
 * @author ken
 */
@Configuration
public class CorsConfig {
    
    /**
     * 配置CORS过滤器
     *
     * @return CORS过滤器
     */
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedOrigins(Arrays.asList("*"));
        corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setMaxAge(3600L);
        
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", corsConfiguration);
        
        return new CorsWebFilter(source);
    }
}

4.2 集成 Swagger/OpenAPI

在微服务架构中,每个服务通常都有自己的 API 文档。通过 Spring Cloud Gateway 集成 Swagger/OpenAPI,可以实现 API 文档的聚合,方便前端开发者查看和测试所有服务的 API。

4.2.1 添加依赖
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.3.0</version>
</dependency>

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-api</artifactId>
    <version>2.3.0</version>
</dependency>
4.2.2 配置 Swagger 资源聚合
package ***.example.gateway.config;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.Contact;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.AbstractSwaggerUiConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigParameters;
import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springdoc.core.utils.SpringDocUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * Swagger配置
 *
 * @author ken
 */
@Configuration
public class SwaggerConfig {
    
    /**
     * 服务路由定位器
     */
    private final RouteDefinitionLocator routeDefinitionLocator;
    
    public SwaggerConfig(RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionLocator = routeDefinitionLocator;
    }
    
    /**
     * 配置OpenAPI信息
     *
     * @return OpenAPI配置
     */
    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
                .info(new Info()
                        .title("微服务API文档")
                        .version("1.0.0")
                        .description("通过网关聚合的所有微服务API文档")
                        .contact(new Contact()
                                .name("API Support")
                                .email("support@example.***")));
    }
    
    /**
     * 配置Swagger UI,聚合所有服务的API文档
     *
     * @return Swagger UI配置参数
     */
    @Bean
    public SwaggerUiConfigParameters swaggerUiConfigParameters() {
        SwaggerUiConfigParameters parameters = new SwaggerUiConfigParameters();
        
        // 获取所有路由定义
        List<RouteDefinition> definitions = routeDefinitionLocator.getRouteDefinitions().collectList().block();
        if (definitions != null) {
            List<AbstractSwaggerUiConfigProperties.SwaggerUrl> urls = new ArrayList<>();
            
            for (RouteDefinition definition : definitions) {
                // 跳过网关自身的路由
                if (definition.getId().equals("self-service-route")) {
                    continue;
                }
                
                // 服务名作为分组名
                String serviceName = definition.getId().replace("-route", "");
                // API文档地址
                String url = "/"+ serviceName +"/v3/api-docs";
                
                urls.add(new AbstractSwaggerUiConfigProperties.SwaggerUrl(
                        serviceName, url, serviceName + " API文档"));
            }
            
            parameters.setUrls(urls);
        }
        
        return parameters;
    }
    
    /**
     * 配置网关自身的API分组
     *
     * @return 分组的OpenAPI
     */
    @Bean
    public GroupedOpenApi gatewayApi() {
        return GroupedOpenApi.builder()
                .group("gateway")
                .pathsToMatch("/public/**", "/fallback/**")
                .build();
    }
}
4.2.3 添加网关自身的测试接口
package ***.example.gateway.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 网关测试控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/public")
@Slf4j
@Tag(name = "网关公共接口", description = "网关自身提供的公共接口")
public class Publi***ontroller {
    
    /**
     * 健康检查接口
     *
     * @return 健康状态
     */
    @GetMapping("/health")
    @Operation(summary = "健康检查", description = "检查网关是否正常运行")
    public Map<String, Object> healthCheck() {
        log.info("健康检查");
        Map<String, Object> result = new HashMap<>(2);
        result.put("status", "UP");
        result.put("service", "api-gateway");
        return result;
    }
    
    /**
     * 版本信息接口
     *
     * @return 版本信息
     */
    @GetMapping("/version")
    @Operation(summary = "版本信息", description = "获取网关版本信息")
    public Map<String, Object> versionInfo() {
        log.info("获取版本信息");
        Map<String, Object> result = new HashMap<>(2);
        result.put("version", "1.0.0");
        result.put("timestamp", System.currentTimeMillis());
        return result;
    }
}
4.2.4 添加网关自身的路由配置
spring:
  cloud:
    gateway:
      routes:
        # 网关自身的路由
        - id: self-service-route
          uri: lb://api-gateway
          predicates:
            - Path=/public/**,/swagger-ui/**,/v3/api-docs/**
4.2.5 访问聚合 API 文档

启动所有服务后,访问 http://localhost:8080/swagger-ui/index.html 即可看到聚合后的 API 文档,可以在不同服务的 API 文档之间切换。

4.3 限流与熔断降级实战

在高并发场景下,限流和熔断降级是保障系统稳定性的重要手段。Spring Cloud Gateway 结合 Resilience4j 和 Redis 可以实现强大的限流和熔断功能。

4.3.1 基于 Redis 的限流实现

前面已经介绍了基本的限流配置,这里我们实现一个更完善的限流方案,包括不同维度的限流(IP、用户、接口)。

package ***.example.gateway.config;

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;

/**
 * 限流配置
 *
 * @author ken
 */
@Configuration
public class RateLimiterConfig {
    
    /**
     * IP限流键解析器,基于客户端IP限流
     *
     * @return 限流键解析器
     */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()
        );
    }
    
    /**
     * 用户限流键解析器,基于用户ID限流
     *
     * @return 限流键解析器
     */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            // 从请求头中获取用户ID
            String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
            return Mono.justOrEmpty(userId)
                    .defaultIfEmpty("anonymous");  // 匿名用户
        };
    }
    
    /**
     * 接口限流键解析器,基于请求路径限流
     *
     * @return 限流键解析器
     */
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(
            exchange.getRequest().getPath().toString()
        );
    }
}

在路由中配置不同的限流策略:

spring:
  cloud:
    gateway:
      routes:
        # 用户服务路由 - 基于IP限流
        - id: user-service-ip-limit
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 5  # 每秒5个请求
                redis-rate-limiter.burstCapacity: 10  # 最多10个并发请求
                key-resolver: "#{@ipKeyResolver}"
                
        # 订单服务路由 - 基于用户限流
        - id: order-service-user-limit
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 2  # 每秒2个请求
                redis-rate-limiter.burstCapacity: 5  # 最多5个并发请求
                key-resolver: "#{@userKeyResolver}"
4.3.2 熔断降级实战

结合 Resilience4j 实现熔断降级功能,当服务出现故障时,快速失败并返回降级响应。

  1. 添加依赖:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
  1. 配置熔断策略:
resilience4j:
  circuitbreaker:
    instances:
      userServiceCircuitBreaker:
        slidingWindowSize: 10  # 滑动窗口大小
        failureRateThreshold: 50  # 失败率阈值,超过此值将打开熔断器
        waitDurationInOpenState: 10000  # 熔断器打开状态持续时间(毫秒)
        permittedNumberOfCallsInHalfOpenState: 3  # 半开状态允许的调用次数
        registerHealthIndicator: true  # 注册健康指示器
        
      orderServiceCircuitBreaker:
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 10000
        permittedNumberOfCallsInHalfOpenState: 3
        registerHealthIndicator: true
        
  timelimiter:
    instances:
      userServiceCircuitBreaker:
        timeoutDuration: 3000  # 超时时间(毫秒)
        
      orderServiceCircuitBreaker:
        timeoutDuration: 3000
  1. 在路由中配置熔断:
spring:
  cloud:
    gateway:
      routes:
        - id: user-service-circuit-breaker
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: userServiceCircuitBreaker
                fallbackUri: forward:/fallback/users
                
        - id: order-service-circuit-breaker
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: orderServiceCircuitBreaker
                fallbackUri: forward:/fallback/orders
  1. 实现降级回调控制器:
package ***.example.gateway.controller;

import lombok.extern.slf4j.Slf4j;
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;

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

/**
 * 熔断降级回调控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/fallback")
@Slf4j
public class FallbackController {
    
    /**
     * 用户服务降级处理
     *
     * @param id 用户ID
     * @return 降级响应
     */
    @GetMapping("/users/{id}")
    public Map<String, Object> userServiceFallback(@PathVariable(required = false) Long id) {
        log.warn("用户服务降级,用户ID: {}", id);
        Map<String, Object> result = new HashMap<>(3);
        result.put("su***ess", false);
        result.put("message", "用户服务暂时不可用,请稍后再试");
        if (id != null) {
            result.put("userId", id);
        }
        return result;
    }
    
    /**
     * 用户服务通用降级处理
     *
     * @return 降级响应
     */
    @GetMapping("/users/**")
    public Map<String, Object> userServiceGeneralFallback() {
        log.warn("用户服务通用降级");
        Map<String, Object> result = new HashMap<>(2);
        result.put("su***ess", false);
        result.put("message", "用户服务暂时不可用,请稍后再试");
        return result;
    }
    
    /**
     * 订单服务降级处理
     *
     * @param id 订单ID
     * @return 降级响应
     */
    @GetMapping("/orders/{id}")
    public Map<String, Object> orderServiceFallback(@PathVariable(required = false) Long id) {
        log.warn("订单服务降级,订单ID: {}", id);
        Map<String, Object> result = new HashMap<>(3);
        result.put("su***ess", false);
        result.put("message", "订单服务暂时不可用,请稍后再试");
        if (id != null) {
            result.put("orderId", id);
        }
        return result;
    }
    
    /**
     * 订单服务通用降级处理
     *
     * @return 降级响应
     */
    @GetMapping("/orders/**")
    public Map<String, Object> orderServiceGeneralFallback() {
        log.warn("订单服务通用降级");
        Map<String, Object> result = new HashMap<>(2);
        result.put("su***ess", false);
        result.put("message", "订单服务暂时不可用,请稍后再试");
        return result;
    }
}

4.4 网关安全认证

网关作为所有请求的入口,是实现统一认证授权的理想位置。我们可以在网关层实现基于 JWT 的认证机制。

4.4.1 添加依赖
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
4.4.2 实现 JWT 工具类
package ***.example.gateway.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.***ponent;
import org.springframework.util.StringUtils;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

/**
 * JWT工具类
 *
 * @author ken
 */
@***ponent
@Slf4j
public class JwtUtil {
    
    /**
     * JWT密钥
     */
    @Value("${jwt.secret:defaultSecretKeyMustBeLongEnoughToMeetTheRequirementsOfTheAlgorithm}")
    private String secret;
    
    /**
     * JWT过期时间(毫秒),默认2小时
     */
    @Value("${jwt.expiration:7200000}")
    private long expiration;
    
    /**
     * 生成JWT令牌
     *
     * @param username 用户名
     * @return JWT令牌
     */
    public String generateToken(String username) {
        return generateToken(username, new HashMap<>());
    }
    
    /**
     * 生成带自定义声明的JWT令牌
     *
     * @param username 用户名
     * @param claims 自定义声明
     * @return JWT令牌
     */
    public String generateToken(String username, Map<String, Object> claims) {
        Date now = new Date();
        Date expirationDate = new Date(now.getTime() + expiration);
        
        SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(key, SignatureAlgorithm.HS256)
                .***pact();
    }
    
    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        return getClaimFromToken(token, Claims::getSubject);
    }
    
    /**
     * 从令牌中获取过期时间
     *
     * @param token 令牌
     * @return 过期时间
     */
    public Date getExpirationDateFromToken(String token) {
        return getClaimFromToken(token, Claims::getExpiration);
    }
    
    /**
     * 从令牌中获取指定的声明
     *
     * @param token 令牌
     * @param claimsResolver 声明解析器
     * @param <T> 声明类型
     * @return 声明值
     */
    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }
    
    /**
     * 从令牌中获取所有声明
     *
     * @param token 令牌
     * @return 所有声明
     */
    private Claims getAllClaimsFromToken(String token) {
        SecretKey key = Keys.hmacShaKeyFor(secret.getBytes());
        return Jwts.parserBuilder()
                .setSigningKey(key)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    /**
     * 检查令牌是否过期
     *
     * @param token 令牌
     * @return 如果过期返回true,否则返回false
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    
    /**
     * 验证令牌
     *
     * @param token 令牌
     * @param username 用户名
     * @return 如果验证通过返回true,否则返回false
     */
    public Boolean validateToken(String token, String username) {
        final String tokenUsername = getUsernameFromToken(token);
        return (username.equals(tokenUsername) && !isTokenExpired(token));
    }
    
    /**
     * 提取令牌中的用户ID
     *
     * @param token 令牌
     * @return 用户ID
     */
    public Long getUserIdFromToken(String token) {
        Claims claims = getAllClaimsFromToken(token);
        Object userIdObj = claims.get("userId");
        if (userIdObj != null) {
            return Long.parseLong(userIdObj.toString());
        }
        return null;
    }
    
    /**
     * 从Authorization头中提取令牌
     *
     * @param authorizationHeader Authorization头
     * @return 令牌,如果提取失败返回null
     */
    public String extractTokenFromHeader(String authorizationHeader) {
        if (StringUtils.hasText(authorizationHeader) && authorizationHeader.startsWith("Bearer ")) {
            return authorizationHeader.substring(7);
        }
        return null;
    }
}
4.4.3 实现认证过滤器
package ***.example.gateway.filter;

import ***.alibaba.fastjson2.JSON;
import ***.example.gateway.util.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

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

/**
 * 认证授权过滤器配置
 *
 * @author ken
 */
@Configuration
@Slf4j
public class AuthFilterConfig {
    
    private final JwtUtil jwtUtil;
    
    public AuthFilterConfig(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }
    
    /**
     * 认证过滤器,验证请求中的JWT令牌
     *
     * @return 全局过滤器
     */
    @Bean
    @Order(-100)
    public GlobalFilter authFilter() {
        return (exchange, chain) -> {
            // 获取请求路径
            String path = exchange.getRequest().getPath().toString();
            
            // 不需要认证的路径
            if (isPublicPath(path)) {
                return chain.filter(exchange);
            }
            
            // 从请求头中获取令牌
            String token = jwtUtil.extractTokenFromHeader(
                    exchange.getRequest().getHeaders().getFirst("Authorization"));
            
            // 验证令牌
            if (token == null) {
                log.warn("未提供令牌,路径: {}", path);
                return unauthorizedResponse(exchange, "未提供令牌,请先登录");
            }
            
            try {
                // 从令牌中获取用户名
                String username = jwtUtil.getUsernameFromToken(token);
                if (username == null || jwtUtil.isTokenExpired(token)) {
                    log.warn("令牌无效或已过期,路径: {}", path);
                    return unauthorizedResponse(exchange, "令牌无效或已过期,请重新登录");
                }
                
                // 从令牌中获取用户ID
                Long userId = jwtUtil.getUserIdFromToken(token);
                
                // 将用户信息添加到请求头,供下游服务使用
                ServerWebExchange mutatedExchange = exchange.getRequest().mutate()
                        .header("X-User-Name", username)
                        .header("X-User-Id", userId != null ? userId.toString() : "")
                        .build()
                        .exchange();
                
                log.info("用户 {} 认证通过,路径: {}", username, path);
                return chain.filter(mutatedExchange);
            } catch (Exception e) {
                log.warn("令牌验证失败,路径: {}", path, e);
                return unauthorizedResponse(exchange, "令牌验证失败,请重新登录");
            }
        };
    }
    
    /**
     * 判断路径是否为公开路径(不需要认证)
     *
     * @param path 请求路径
     * @return 如果是公开路径返回true,否则返回false
     */
    private boolean isPublicPath(String path) {
        return path.startsWith("/public/") || 
               path.startsWith("/auth/") ||
               path.startsWith("/swagger-ui/") ||
               path.startsWith("/v3/api-docs/");
    }
    
    /**
     * 返回未授权响应
     *
     * @param exchange 交换对象
     * @param message 错误消息
     * @return 响应Mono
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        // 设置401响应
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        
        // 设置响应体
        Map<String, Object> response = new HashMap<>(2);
        response.put("su***ess", false);
        response.put("message", message);
        
        byte[] bytes = JSON.toJSONString(response).getBytes();
        exchange.getResponse().getHeaders().add("Content-Type", "application/json");
        
        return exchange.getResponse().writeWith(Mono.just(
                exchange.getResponse().bufferFactory().wrap(bytes)
        ));
    }
}
4.4.5 实现认证接口
package ***.example.gateway.controller;

import ***.example.gateway.util.JwtUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 认证控制器
 *
 * @author ken
 */
@RestController
@RequestMapping("/auth")
@Slf4j
@Tag(name = "认证接口", description = "提供用户认证和令牌相关操作的接口")
public class AuthController {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    /**
     * 用户登录,获取令牌
     *
     * @param username 用户名
     * @param password 密码
     * @return 包含令牌的响应
     */
    @PostMapping("/login")
    @Operation(summary = "用户登录", description = "用户登录并获取JWT令牌")
    public Map<String, Object> login(
            @Parameter(description = "用户名", required = true)
            @RequestParam String username,
            @Parameter(description = "密码", required = true)
            @RequestParam String password) {
        log.info("用户登录,用户名: {}", username);
        
        // 这里简化处理,实际项目中应该验证用户名和密码
        // 例如查询数据库或调用用户服务进行验证
        if (!"password123".equals(password)) {
            log.warn("用户登录失败,用户名: {}, 密码错误", username);
            Map<String, Object> result = new HashMap<>(2);
            result.put("su***ess", false);
            result.put("message", "用户名或密码错误");
            return result;
        }
        
        // 生成用户ID(实际项目中从数据库获取)
        Long userId = 1001L;
        if ("admin".equals(username)) {
            userId = 1L;
        }
        
        // 创建自定义声明
        Map<String, Object> claims = new HashMap<>(1);
        claims.put("userId", userId);
        
        // 生成JWT令牌
        String token = jwtUtil.generateToken(username, claims);
        
        log.info("用户登录成功,用户名: {}", username);
        Map<String, Object> result = new HashMap<>(3);
        result.put("su***ess", true);
        result.put("token", token);
        result.put("userId", userId);
        return result;
    }
}

五、Spring Cloud Gateway 性能优化与生产实践

5.1 性能优化建议

Spring Cloud Gateway 基于 ***ty 和 WebFlux,本身具有良好的性能,但在生产环境中仍需进行适当的优化以发挥最佳性能。

5.1.1 JVM 参数优化
# JVM参数建议
-Xms4g -Xmx4g -Xmn2g 
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
-XX:+UseG1GC -XX:G1HeapRegionSize=16m
-XX:G1ReservePercent=25
-XX:InitiatingHeapO***upancyPercent=30
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/log/gateway/heapdump.hprof
5.1.2 ***ty 参数优化
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000  # 连接超时时间(毫秒)
        response-timeout: 5s  # 响应超时时间
        pool:
          type: fixed  # 连接池类型
          max-connections: 500  # 最大连接数
          acquire-timeout: 2000  # 获取连接超时时间(毫秒)
      httpserver:
        max-initial-line-length: 4096  # 请求行最大长度
        max-header-size: 8192  # 请求头最大长度
        max-chunk-size: 8192  # 分块最大大小
5.1.3 路由缓存优化
spring:
  cloud:
    gateway:
      route-cache:
        enabled: true  # 启用路由缓存
        time-to-live: 30s  # 缓存存活时间
5.1.4 其他优化建议
  1. 减少不必要的过滤器:每个过滤器都会增加请求处理时间,只保留必要的过滤器。

  2. 异步处理:尽量使用异步处理,避免在过滤器中执行阻塞操作。

  3. 合理设置超时时间:根据服务响应时间合理设置超时时间,避免长时间等待。

  4. 启用压缩:对响应进行压缩,减少网络传输量。

server:
  ***pression:
    enabled: true
    mime-types: application/json,application/xml,text/html,text/plain,text/css,application/javascript
    min-response-size: 1024  # 最小响应大小,小于此值不压缩
  1. 使用连接池:配置合理的连接池参数,避免频繁创建和关闭连接。

5.2 高可用部署方案

在生产环境中,为了保证网关的高可用性,需要采用集群部署方案。

5.2.1 部署要点
  1. 多节点部署:至少部署 3 个 Gateway 节点,避免单点故障。

  2. 负载均衡:在 Gateway 集群前部署负载均衡器(如 Nginx、云服务商的 SLB 等)。

  3. 共享配置:使用配置中心(如 Nacos、Apollo)管理路由配置,确保所有节点配置一致。

  4. 服务发现:结合服务发现组件(如 Nacos、Eureka),实现服务地址的动态获取。

  5. 健康检查:配置负载均衡器的健康检查,自动剔除不健康的 Gateway 节点。

  6. 会话共享:如果需要会话状态,使用 Redis 等实现会话共享。

5.2.2 Nginx 负载均衡配置示例
upstream gateway_cluster {
    server 192.168.1.101:8080;
    server 192.168.1.102:8080;
    server 192.168.1.103:8080;
    
    # 健康检查
    health_check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    # 健康检查路径
    health_check_uri /public/health;
}

server {
    listen 80;
    server_name api.example.***;
    
    # 转发到网关集群
    location / {
        proxy_pass http://gateway_cluster;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 30s;
    }
}

5.3 监控与日志

为了及时发现和解决问题,需要对 Spring Cloud Gateway 进行全面的监控和日志收集。

5.3.1 监控配置

添加 Spring Boot Actuator 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置监控端点:

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus,gateway  # 暴露的端点
  endpoint:
    health:
      show-details: always  # 显示健康详情
      probes:
        enabled: true
    metrics:
      enabled: true
  metrics:
    export:
      prometheus:
        enabled: true  # 启用Prometheus导出
  server:
    port: 8081  # 监控端口,与应用端口分离
5.3.2 集成 Prometheus 和 Grafana
  1. 部署 Prometheus,配置抓取 Gateway 的监控指标:
scrape_configs:
  - job_name: 'gateway'
    scrape_interval: 5s
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['192.168.1.101:8081', '192.168.1.102:8081', '192.168.1.103:8081']
  1. 部署 Grafana,导入 Spring Cloud Gateway 的仪表盘(可在 Grafana 官网搜索合适的仪表盘模板)。
5.3.3 日志配置

配置日志输出格式和滚动策略:

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>
<dependency>
    <groupId>***.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>7.4</version>
</dependency>

创建 logback-spring.xml 配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
    
    <!-- 日志文件路径 -->
    <property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/>
    <property name="LOG_FILE_NAME" value="gateway"/>
    
    <!-- 滚动文件追加器 -->
    <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_FILE_PATH}/${LOG_FILE_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 按天滚动 -->
            <fileNamePattern>${LOG_FILE_PATH}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 保留30天的日志 -->
            <maxHistory>30</maxHistory>
            <!-- 总大小限制 -->
            <totalSizeCap>10GB</totalSizeCap>
        </rollingPolicy>
        <encoder class="***.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <message>message</message>
                <logger>logger</logger>
                <thread>thread</thread>
                <level>level</level>
            </fieldNames>
            <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
        </encoder>
    </appender>
    
    <!-- 控制台输出JSON格式日志 -->
    <appender name="CONSOLE_JSON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="***.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
            <fieldNames>
                <timestamp>timestamp</timestamp>
                <message>message</message>
                <logger>logger</logger>
                <thread>thread</thread>
                <level>level</level>
            </fieldNames>
            <timestampPattern>yyyy-MM-dd'T'HH:mm:ss.SSSZ</timestampPattern>
        </encoder>
    </appender>
    
    <!-- 根日志级别 -->
    <root level="INFO">
        <appender-ref ref="CONSOLE_JSON"/>
        <appender-ref ref="ROLLING_FILE"/>
    </root>
    
    <!-- Spring Cloud Gateway相关日志级别 -->
    <logger name="org.springframework.cloud.gateway" level="INFO"/>
    <logger name="reactor.***ty" level="INFO"/>
</configuration>

六、常见问题与解决方案

6.1 路由不生效问题

问题现象:配置了路由规则,但请求没有被正确路由到目标服务。

可能原因及解决方案

  1. 路由顺序问题

    • Spring Cloud Gateway 按照路由配置的顺序匹配路由,前面的路由可能会优先匹配。
    • 解决方案:调整路由顺序,将更具体的路由放在前面。
  2. 断言配置错误

    • 检查断言配置是否正确,特别是路径匹配是否准确。
    • 解决方案:使用更精确的断言,或通过日志查看断言匹配情况。
  3. 服务地址错误

    • 检查路由的 uri 配置是否正确,特别是使用服务名时,确保服务已注册到服务发现组件。
    • 解决方案:验证服务地址的正确性,确保服务正常运行。
  4. 过滤器问题

    • 某些过滤器可能会修改请求路径或阻止请求继续处理。
    • 解决方案:暂时移除可疑的过滤器,逐步排查问题。

6.2 跨域问题

问题现象:前端调用 API 时出现跨域错误(CORS error)。

可能原因及解决方案

  1. CORS 配置不正确

    • 检查 CORS 配置是否覆盖了所有需要的路径、方法和请求头。
    • 解决方案:调整 CORS 配置,确保允许前端的来源、方法和请求头。
  2. 过滤器顺序问题

    • CORS 过滤器需要在其他过滤器之前执行。
    • 解决方案:确保 CORS 过滤器的优先级高于其他过滤器。
  3. 服务端也配置了 CORS

    • 如果后端服务也配置了 CORS,可能会与网关的 CORS 配置冲突。
    • 解决方案:统一在网关层处理 CORS,后端服务不再配置 CORS。

6.3 性能问题

问题现象:网关响应缓慢,吞吐量低。

可能原因及解决方案

  1. JVM 参数不合理

    • 内存设置过小可能导致频繁 GC,影响性能。
    • 解决方案:调整 JVM 参数,特别是堆内存大小和 GC 策略。
  2. 连接池配置不当

    • 连接池大小不合理可能导致连接等待或资源浪费。
    • 解决方案:根据实际负载调整连接池参数。
  3. 过滤器过多或执行耗时操作

    • 过多的过滤器或在过滤器中执行耗时操作会增加响应时间。
    • 解决方案:减少不必要的过滤器,避免在过滤器中执行阻塞操作。
  4. 超时设置不合理

    • 超时设置过短可能导致正常请求被中断,过长则可能导致资源占用过久。
    • 解决方案:根据服务响应时间合理设置超时参数。
  5. 未启用压缩

    • 未启用响应压缩会增加网络传输时间。
    • 解决方案:启用压缩功能,减少传输数据量。

七、总结与展望

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关组件,提供了强大而灵活的功能,是构建微服务架构的理想选择。通过本文的介绍,我们全面了解了 Spring Cloud Gateway 的核心概念、工作原理、配置方式和高级特性。

Spring Cloud Gateway 的主要优势包括:

  1. 基于响应式编程:采用 ***ty 和 WebFlux,实现非阻塞 IO,性能优异。
  2. 功能丰富:提供路由、断言、过滤器等核心功能,支持限流、熔断、认证等高级特性。
  3. 灵活配置:支持配置文件、Java 代码、配置中心等多种配置方式,支持动态路由。
  4. 易于扩展:提供丰富的扩展点,可以自定义断言和过滤器。
  5. 生态集成:与 Spring Cloud 生态中的服务发现、配置中心等组件无缝集成。

参考资料

  1. Spring Cloud Gateway 官方文档
  2. Spring Cloud 官方文档
  3. Spring Boot 官方文档
  4. Resilience4j 官方文档
  5. Spring Cloud Alibaba 官方文档
转载请说明出处内容投诉
CSS教程网 » 深入浅出 Spring Cloud Gateway:从入门到精通的网关实战指南

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买