万字长文,Spring Cloud Gateway 最佳实践:配置详解与调优指南

文章目录

  • 一、创建网关
  • 二、断言
    • [1. 长短写法](#1. 长短写法)
      • [✍️ 简短写法(Shortcut Configuration)](#✍️ 简短写法(Shortcut Configuration))
      • [📝 完整写法(Fully Expanded Configuration)](#📝 完整写法(Fully Expanded Configuration))
      • [🔄 两种写法对比](#🔄 两种写法对比)
    • [2. 核心配置规则详情](#2. 核心配置规则详情)
        • [1. 最常用的基础规则](#1. 最常用的基础规则)
        • [2. 基于时间的规则](#2. 基于时间的规则)
        • [3. 流量控制与灰度规则](#3. 流量控制与灰度规则)
        • [4. 高级匹配规则](#4. 高级匹配规则)
  • 三、过滤器
      • [📊 过滤器全景概览](#📊 过滤器全景概览)
      • [🔧 1. GatewayFilter:路由级别的过滤器](#🔧 1. GatewayFilter:路由级别的过滤器)
      • [📦 2. default-filters:默认过滤器](#📦 2. default-filters:默认过滤器)
      • [🧬 3. GlobalFilter:全局过滤器(代码方式)](#🧬 3. GlobalFilter:全局过滤器(代码方式))

网关(Gateway)在系统架构中,尤其是在微服务环境下,充当着 所有流量的"总入口"和"守门员"。它的核心职能是 "路由转发" 与 "通用问题处理"**的分离,让后端服务可以专注于业务逻辑本身。

具体来说,网关的核心功能主要分为以下几大类:

🧭 路由与转发

这是网关最基础的功能,负责将请求精确地导向正确的后端服务。

  • 动态路由 :根据请求的路径(如 /user)、域名、Header等信息,将请求转发到对应的微服务。

  • 负载均衡:当后端服务有多个实例时,网关能通过轮询、随机、IP Hash等算法分发请求,实现负载均衡。

  • 协议转换:能在不同网络协议间"翻译",例如将外部的HTTP请求转换为内部服务使用的Dubbo、gRPC等RPC协议,或是支持WebSocket。

安全与认证(守门员职责)

网关是执行安全策略的理想位置,避免每个服务都重复实现安全逻辑。

  • 身份认证:在网关层统一验证请求携带的Token、API Key、OAuth 2.0凭证等,确认"你是谁"。

  • 授权与访问控制:配合认证结果,判断"你能做什么",例如通过IP黑白名单限制访问。

统一监控与运维(可观测性)

网关是流经所有请求的必经之路,提供了系统监控的黄金观测点。

  • 日志与监控:集中记录所有请求的耗时、状态码、来源等信息,方便进行全链路追踪、性能分析和问题排查。

  • 灰度发布与版本管理:通过路由规则,可以按比例将少量请求导向新版本服务,实现金丝雀发布(灰度发布),待验证稳定后再全量切换,降低上线风险。


一、创建网关

使用 Spring Cloud Gateway 创建网关,核心就是通过 路由(Route) 、断言(Predicate) 和 过滤器(Filter) 这三个概念,将请求灵活地转发给后端服务。

1. 添加依赖:

在 pom.xml 文件中,添加 Spring Cloud Gateway 的起步依赖。如果需要与服务注册中心(如 Nacos)集成,也一并引入。

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 以 Nacos 服务发现为例 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2.编写启动类:

创建一个标准的 Spring Boot 启动类,并启用服务发现功能(如果使用了注册中心)。

java 复制代码
@SpringBootApplication
@EnableDiscoveryClient // 启用服务发现
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

3.配置路由规则:

在 application.yml 文件中进行核心配置。下面的例子中,网关启动在 18012 端口,并将所有 /provider1/** 开头的请求,负载均衡地转发到注册中心名为 service-provider 的服务上。

yaml 复制代码
server:
  port: 18012
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # Nacos 注册中心地址
    gateway:
      routes:
        - id: service-provider-route    # 路由ID,唯一即可
          uri: lb://service-provider   # lb:// 表示启用负载均衡,后面跟服务名
          predicates:
            - Path=/provider1/**        # 断言:路径匹配
          filters:
            - StripPrefix=1             # 过滤器:去掉第一层路径前缀
配置项 说明 示例
id 路由的唯一标识,自定义即可。 user-service
uri 请求最终被转发到的目标地址。可以直接写 http:// 开头的固定地址,也可以使用 lb:// 前缀实现负载均衡。 lb://userservice (从注册中心根据服务名发现地址)
predicates 断言条件数组。所有条件都满足时,请求才会被该路由处理。 - Path=/user/** (路径匹配)
filters 过滤器数组,在请求转发前后对请求/响应进行处理。 - StripPrefix=1 (去掉路径中的第一层前缀)

注意 :lb:// 是负载均衡的缩写,使用前需要确保项目中已经集成了服务发现和负载均衡组件(如 Spring Cloud LoadBalancer)。

二、断言

断言就是路由的"准入条件"或"匹配规则"。 它决定了一个请求有没有资格被当前这个路由处理。

1. 长短写法

在 Spring Cloud Gateway 中,断言的"长短写法"指的是配置断言(Predicate)的两种不同语法风格。简单来说,就是**"简短写法"(Shortcut)和"完整写法"**(Fully Expanded)。它们最终实现的效果完全一样,只是配置方式不同。

✍️ 简短写法(Shortcut Configuration)

这是最常用、最简洁的方式,适合大多数场景。它通过键值对 或参数序列来配置,Spring Cloud Gateway 会按照约定自动解析。

  • 格式 :- PredicateName=参数1, 参数2, ...

  • 特点:代码量少,一目了然。

示例:

yaml 复制代码
predicates:
  # 1. 简单参数:路径匹配
  - Path=/api/user/**

  # 2. 多个参数,用逗号分隔:Cookie 名和值的正则
  - Cookie=user_type, vip

  # 3. 多个参数,用逗号分隔:Header 名和值的正则
  - Header=X-Request-Id, \d+

  # 4. 参数包含逗号的情况,用分号分隔:在指定时间之后
  - After=2025-01-01T00:00:00+08:00[Asia/Shanghai]

📝 完整写法(Fully Expanded Configuration)

当断言需要配置的参数较多,或参数本身带有特殊字符(如逗号)时,使用完整写法会更清晰。它使用 name + args 的 Map 结构来明确定义。

  • 格式 :用 name 指定断言类型,用 args 以键值对形式指定参数。

  • 特点:结构清晰,可读性强,适合复杂配置。

示例:

复制代码
predicates:
  # 1. 等同于:- Path=/api/user/**
  - name: Path
    args:
      patterns: /api/user/**
      
  # 2. 等同于:- Cookie=user_type, vip
  - name: Cookie
    args:
      name: user_type
      regexp: vip
      
  # 3. 等同于:- Header=X-Request-Id, \d+
  - name: Header
    args:
      header: X-Request-Id
      regexp: \d+

🔄 两种写法对比

特性 简短写法 完整写法
可读性 简洁紧凑,但参数较多时可能不够直观 结构化,参数含义一目了然
配置量 更少 更多,需要写 name 和 args
适用场景 日常开发,参数少且逻辑简单 复杂配置,或参数需精确指定键名时
本质 是完整写法的语法糖,最终会被转换为完整写法 网关底层解析的原生格式

2. 核心配置规则详情

Spring Cloud Gateway 内置了丰富的断言工厂,我按使用场景把它们分成了几类,方便你查阅。

1. 最常用的基础规则
断言类型 作用 简短写法示例 说明
Path 匹配请求路径 - Path=/user/**, /order/** 支持多个路径,用逗号分隔。这是最核心的规则。
Method 匹配请求方法 - Method=GET, POST 匹配指定的 HTTP 方法。
Header 匹配请求头 - Header=X-Request-Id, \d+ 必须包含指定的请求头,且值匹配正则(\d+ 表示数字)。
Cookie 匹配 Cookie - Cookie=user_type, vip 必须包含指定的 Cookie,且值匹配正则。
Query 匹配查询参数 - Query=page, \d+ 必须包含指定的查询参数,且值匹配正则。
2. 基于时间的规则
断言类型 作用 简短写法示例 说明
After 在某个时间之后 - After=2025-01-01T00:00:00+08:00[Asia/Shanghai] 用于控制路由的生效时间。
Before 在某个时间之前 - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai] 用于控制路由的失效时间。
Between 在两个时间之间 - Between=2025-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai] 时间区间,常用于定时任务路由。
TimeRange 指定时间段内 - TimeRange=2025-01-01T00:00:00+08:00[Asia/Shanghai], 2025-12-31T23:59:59+08:00[Asia/Shanghai] 功能与 Between 类似。
3. 流量控制与灰度规则
断言类型 作用 简短写法示例 说明
Weight 按权重分配流量 - Weight=group1, 80 常用于灰度发布。例如,将 80% 的流量分配给稳定版本,20% 分配给新版本。同一个组(group)内所有路由的权重之和应为 100。
RemoteAddr 按 IP 地址匹配 - RemoteAddr=192.168.1.0/24 支持 CIDR 格式。可以用于内部网络访问控制。
4. 高级匹配规则
断言类型 作用 简短写法示例 说明
Host 按主机名匹配 - Host=**.example.com, **.example.org 常用于多租户场景,根据域名路由。
XForwardedRemoteAddr 按代理后的真实 IP 匹配 - XForwardedRemoteAddr=10.0.0.0/8 当网关前面还有代理(如 Nginx)时,使用此断言获取真实 IP。

三、过滤器

📊 过滤器全景概览

分类 作用范围 配置方式 典型场景
GatewayFilter 特定路由 routes[].filters 路径前缀剥离、添加请求头、重写路径
default-filters 所有路由(配置方式) spring.cloud.gateway.default-filters 统一添加响应头、全局跨域处理
GlobalFilter 所有路由(代码方式) 实现 GlobalFilter 接口 统一鉴权、全局日志、分布式限流
下面我分别展开讲这三类,并给出典型代码和配置示例。

🔧 1. GatewayFilter:路由级别的过滤器

这是配置在具体路由下的过滤器,只对匹配该路由的请求生效。

配置示例:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix=1                    # 去掉第一层路径前缀
            - AddRequestHeader=X-Request-From, Gateway  # 添加请求头
            - AddResponseHeader=X-Response-Time, 2025   # 添加响应头
            - SetResponseStatus=200            # 强制设置响应状态码
            - RequestRateLimiter=              # 限流(需要配合 Redis)
                key-resolver: '#{@userKeyResolver}'
                redis-rate-limiter.replenishRate: 10
                redis-rate-limiter.burstCapacity: 20

常用内置 GatewayFilter 速查表:

过滤器名称 作用 示例
StripPrefix 去掉路径中的前 N 段 StripPrefix=1:/user/info → /info
AddRequestHeader 添加请求头 AddRequestHeader=X-User, admin
AddResponseHeader 添加响应头 AddResponseHeader=X-Version, 1.0
RemoveRequestHeader 删除请求头 RemoveRequestHeader=Cookie
SetPath 重写路径 SetPath=/new/{segment}
RewritePath 用正则重写路径 RewritePath=/user/(?<seg>.*), /$\{seg}
RequestRateLimiter 限流 见上面示例
Retry 请求失败时重试 Retry=3, statuses=500
SetResponseStatus 强制设置响应状态码 SetResponseStatus=200

注意 :RequestRateLimiter 需要配合 Redis 以及自定义的 KeyResolver(用于区分不同用户/IP)才能生效。


📦 2. default-filters:默认过滤器

配置在 default-filters 下的过滤器,会对所有路由生效,相当于用配置方式定义的"全局过滤器",适用于一些通用场景。

配置示例:

yaml 复制代码
spring:
  cloud:
    gateway:
      default-filters:
        - AddResponseHeader=X-Response-Default, true
        - DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
        - AddRequestHeader=X-Request-Id, ${random.value}

典型用途:

用途 配置
统一添加响应头 AddResponseHeader=X-App-Name, gateway
去重跨域响应头(避免浏览器报错) DedupeResponseHeader=Access-Control-Allow-Origin, RETAIN_UNIQUE
生成全链路追踪 ID AddRequestHeader=X-Trace-Id, ${random.value}

🧬 3. GlobalFilter:全局过滤器(代码方式)

这是最灵活、最强大的方式。通过实现 GlobalFilter 接口,你可以编写 Java 代码来处理所有经过网关的请求,适合实现复杂的业务逻辑。

核心接口:

java 复制代码
public interface GlobalFilter {
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

实战示例:统一 JWT 鉴权

下面的代码实现了一个简单的全局鉴权过滤器,除了白名单路径外,所有请求都必须携带有效的 Token。

java 复制代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    // 白名单路径(从配置文件注入)
    @Value("${sa-token.exclude-auth-paths:}")
    private List<String> excludePaths;

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

        // 1. 白名单放行
        if (excludePaths.stream().anyMatch(path::startsWith)) {
            return chain.filter(exchange);
        }

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

        // 3. 校验 Token(调用 Sa-Token 或自定义逻辑)
        String jwt = token.substring(7);
        if (!validateToken(jwt)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }

        // 4. 放行
        return chain.filter(exchange);
    }

    private boolean validateToken(String token) {
        // 调用 Sa-Token 或 JWT 工具类校验
        return StpUtil.getLoginIdByToken(token) != null;
    }

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

实战示例:记录请求耗时(Pre + Post 逻辑)

java 复制代码
@Component
public class LoggingGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        
        // 前置逻辑:记录开始
        log.info("请求开始:{} {}", exchange.getRequest().getMethod(), exchange.getRequest().getPath());
        
        return chain.filter(exchange).then(
            // 后置逻辑:记录耗时
            Mono.fromRunnable(() -> {
                long duration = System.currentTimeMillis() - startTime;
                log.info("请求完成,耗时:{}ms", duration);
            })
        );
    }

    @Override
    public int getOrder() {
        return -1;
    }
}