万字长文,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+

🔄 两种写法对比

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

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;
    }
}