Skip to content

Spring Cloud Gateway 学习笔记

版本说明:本文基于 Spring Cloud 2023.x / Spring Boot 3.x / JDK 17 核心依赖:spring-cloud-starter-gateway(基于 Spring 5 WebFlux + Reactor Netty)


1. API 网关概述

⭐ 1.1 什么是 API 网关

API 网关是微服务架构的统一入口,所有外部请求都先经过网关,再由网关路由到后端服务。它是微服务架构中不可或缺的基础设施,承担着「门卫」和「调度中心」的双重角色。

客户端 (Browser/App)


┌─────────────────────────────────────────┐
│           API Gateway (统一入口)          │
│  ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│  │ 认证授权  │ │ 限流熔断  │ │ 日志监控 │ │
│  └─────────┘ └──────────┘ └─────────┘ │
│  ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│  │ 路由转发  │ │ 负载均衡  │ │ 协议转换 │ │
│  └─────────┘ └──────────┘ └─────────┘ │
└─────────┬───────────┬───────────┬──────┘
          ▼           ▼           ▼
     ┌────────┐ ┌────────┐ ┌────────┐
     │ 用户服务│ │ 订单服务│ │ 商品服务│
     └────────┘ └────────┘ └────────┘

1.2 网关核心功能一览表

功能说明重要程度
路由转发根据规则将请求转发到后端微服务⭐⭐⭐
负载均衡从多个服务实例中选择一个处理请求⭐⭐⭐
认证授权统一校验 Token、权限控制⭐⭐⭐
限流熔断保护后端服务不被压垮⭐⭐⭐
统一跨域CORS 配置,前端无需单独处理⭐⭐
日志监控请求/响应统一记录⭐⭐
协议转换HTTP ↔ gRPC 等
响应体修改统一包装/脱敏返回结果⭐⭐

💡 1.3 Gateway vs Zuul 对比

对比项Spring Cloud GatewayNetflix Zuul
基础框架Spring 5 WebFlux(响应式)Servlet 2.5(阻塞)
模型异步非阻塞同步阻塞
性能高(支持高并发)一般(线程模型限制)
维护状态官方主推(Zuul 2 仅补丁维护)不再开发新功能
编程模型函数式、Mono/Flux传统 Servlet Filter

⚠️ 结论:新项目必须用 Gateway,Zuul 已停止新功能开发。


2. 快速入门

⚠️ 2.1 环境要求与依赖

⚠️ 关键警告:Gateway 不能引入 spring-boot-starter-web Gateway 基于 WebFlux(响应式),与传统 Servlet 容器不兼容,引入 starter-web 会启动报错。

pom.xml 核心依赖:

xml
<!-- Spring Cloud Gateway:这是唯一必需的依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!-- 负载均衡(Spring Cloud 2020+ 默认使用 LoadBalancer 替代 Ribbon)-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- 注册中心客户端(任选 Nacos / Eureka / Consul)-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 如果你要做 Redis 限流,才需要这个 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

2.2 启动类

java
package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * API 网关启动类
 * 注意:无需 @EnableDiscoveryClient,注册中心依赖自动装配
 */
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

🚩 2.3 最小可运行示例 application.yml

yaml
server:
  port: 8080  # 网关端口,客户端统一访问这个端口

spring:
  application:
    name: api-gateway  # 注册到 Nacos 的服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos 地址
    gateway:
      # 核心:路由规则列表
      routes:
        # 路由1:用户服务
        - id: user-service-route        # 路由唯一标识(字符串,随便取但不能重复)
          uri: lb://user-service        # lb:// = LoadBalancer,从注册中心找 user-service
          predicates:                   # 匹配条件(全部满足才转发)
            - Path=/api/users/**        # 请求路径匹配,/api/users/xxx 都会匹配
          filters:                      # 过滤器(可选)
            - StripPrefix=1             # 去掉第1段路径:/api/users/123 → /users/123

        # 路由2:订单服务
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

        # 路由3:静态 URI(直接转发到固定地址,不走注册中心)
        - id: baidu-route
          uri: https://www.baidu.com
          predicates:
            - Path=/baidu/**

启动后测试:

bash
# 访问网关 → 网关路由到用户服务
curl http://localhost:8080/api/users/1

# 验证路由是否生效(Spring Cloud Gateway Actuator)
curl http://localhost:8080/actuator/gateway/routes

3. 核心概念 ⭐

Gateway 的核心由三个术语组成:Route(路由)、Predicate(谓词)、Filter(过滤器)。

3.1 Route(路由)—— 网关的基本构建块

一个 Route 就是一条路由规则,由以下要素组成:

属性说明必填
id路由唯一标识
uri转发目标(lb://服务名http://ip:port
predicates匹配条件,全部满足才转发✅(至少一个)
filters过滤器,请求/响应前后处理
order优先级,数字越小优先级越高(默认 0)
yaml
# 路由 = id + uri + predicates + filters + order
- id: my-route
  uri: lb://target-service
  predicates: [...]
  filters: [...]
  order: -1  # 优先级高于默认值 0

3.2 Predicate(谓词)—— 路由匹配条件

Predicate 就是 Java 8 的 java.util.function.Predicate<ServerWebExchange>,根据 HTTP 请求(路径、方法、头、参数等)判断是否匹配该路由。多个 Predicate 之间是 AND 关系,必须全部满足。

3.3 Filter(过滤器)—— 请求/响应处理链

过滤器分两种:

类型作用范围生命周期
GatewayFilter仅对某条路由生效路由级别
GlobalFilter对所有路由生效全局级别

执行链路:

请求 → GlobalFilter(pre) → GatewayFilter(pre) → 代理请求 → GatewayFilter(post) → GlobalFilter(post) → 响应

4. 路由配置详解

4.1 YAML 配置方式(推荐)

YAML 是最常用的配置方式,直观易读,支持动态刷新(配合 Nacos Config)。

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1

4.2 Java 代码配置方式

适合需要写动态逻辑的场景:

java
package com.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;

/**
 * Java 代码方式定义路由
 * 与 YAML 方式等效,可以共存
 */
@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()

            // 路由1:用户服务
            .route("user-service", r -> r
                .path("/api/users/**")          // 路径匹配
                .filters(f -> f
                    .stripPrefix(1)              // 去掉 /api
                    .addRequestHeader("X-Source", "gateway"))
                .uri("lb://user-service"))

            // 路由2:只允许 GET 方法访问的订单服务
            .route("order-service-get-only", r -> r
                .path("/api/orders/**")
                .and()
                .method("GET")                  // 路径 + GET 方法同时满足
                .uri("lb://order-service"))

            // 路由3:带权重的分流(金丝雀发布)
            .route("weighted-route", r -> r
                .path("/api/products/**")
                .weight("product-group", 90)   // 90% 流量
                .uri("lb://product-service"))

            .build();
    }
}

🚩 4.3 内置谓词大全(11 种)

① Path —— 路径匹配(最常用)

yaml
predicates:
  # 精确匹配一个路径
  - Path=/api/users
  # 通配符匹配一个前缀下的所有子路径
  - Path=/api/users/**
  # 多个路径任选其一
  - Path=/api/users/**,/api/public/**

② Method —— HTTP 方法匹配

yaml
predicates:
  # 只允许 GET 请求
  - Method=GET
  # 允许多个方法
  - Method=GET,POST,PUT,DELETE

③ Header —— 请求头匹配(支持正则)

yaml
predicates:
  # 请求头名为 X-Request-Id 且值匹配正则 \d+(纯数字)
  - Header=X-Request-Id, \d+
  # 请求头存在即可
  - Header=X-Api-Version

④ Query —— 查询参数匹配

yaml
predicates:
  # URL 中存在 token 参数即可
  - Query=token
  # token 参数值匹配正则
  - Query=token, abc_
yaml
predicates:
  # cookie 名为 session,值匹配正则
  - Cookie=session, .+

⑥ Host —— Host 头匹配

yaml
predicates:
  # 二级域名任意子域名都匹配
  - Host=**.example.com
  # 具体子域名
  - Host=api.example.com

⑦ RemoteAddr —— 远程 IP 匹配

yaml
predicates:
  # CIDR 表示法:IP段 / 子网掩码位数
  - RemoteAddr=192.168.1.1/24
  - 单个 IP
  - RemoteAddr=10.0.0.1

⑧ After / Before / Between —— 时间匹配

yaml
predicates:
  # 只匹配某个时间之后的请求(时区 Asia/Shanghai)
  - After=2024-01-01T00:00:00+08:00[Asia/Shanghai]
  # 某个时间之前
  - Before=2025-12-31T23:59:59+08:00[Asia/Shanghai]
  # 两个时间之间
  - Between=2024-01-01T00:00:00+08:00[Asia/Shanghai],2024-12-31T23:59:59+08:00[Asia/Shanghai]

⑨ Weight —— 权重分流(金丝雀发布)

yaml
predicates:
  # group1 组,权重 90 = 90% 流量到此路由
  - Weight=group1, 90
  # 同一 group 的其他路由权重为 10,合为 100

⑩ CloudFoundryRouteService —— 专用(跳过)

⑪ XForwardedRemoteAddr —— 代理后 IP

yaml
predicates:
  # 识别 X-Forwarded-For 头中的 IP
  - XForwardedRemoteAddr=10.0.0.0/8

4.4 组合谓词

多个谓词之间默认是 AND 关系,全部满足才转发。用 Java 代码可实现更复杂的逻辑:

yaml
predicates:
  # 路径是 /api/users/** 并且方法是 GET
  - Path=/api/users/**
  - Method=GET

5. 过滤器详解

🚩 5.1 内置 GatewayFilter(20+ 种)

路径处理类

yaml
filters:
  # StripPrefix:去掉前 N 段路径
  # 请求 /api/users/123 → 后端收到 /users/123
  - StripPrefix=1

  # PrefixPath:给后端路径加前缀
  # 请求 /users/123 → 后端收到 /api/users/123
  - PrefixPath=/api

  # SetPath:重写整个路径(使用占位符提取)
  # 请求 /api/users/123 → /users/123 (把 /api/ 去掉)
  - SetPath=/{segment}
  # 配合 predicate:Path=/api/{segment}

  # RewritePath:正则替换(最灵活)
  # 请求 /api/users/123 → /users/123
  - RewritePath=/api/(?<segment>.*), /$\{segment}
  # ⚠️ YAML 中 $ 要转义为 $\{segment} 或用 $${segment}

请求头/参数类

yaml
filters:
  # 添加请求头传给后端
  - AddRequestHeader=X-Request-Id, 12345
  # 从请求属性取值(动态)
  - AddRequestHeader=X-Trace-Id, #{traceId}

  # 添加请求参数
  - AddRequestParameter=source, gateway

  # 修改请求头
  - SetRequestHeader=Content-Type, application/json

  # 去掉请求头
  - RemoveRequestHeader=X-Internal-Only

响应头类

yaml
filters:
  # 给客户端的响应加头
  - AddResponseHeader=X-Response-Time, 30ms
  - SetResponseHeader=Content-Type, application/json
  - RemoveResponseHeader=X-Powered-By

状态码/重定向类

yaml
filters:
  # 直接设置响应状态码并返回
  - SetStatus=401
  # 从响应头取值作为状态码
  - SetStatus=fromResponseStatus

  # 临时重定向
  - Redirect=302, https://example.com/new-url

限流熔断类

yaml
filters:
  # 基于 Redis 的令牌桶限流
  - name: RequestRateLimiter
    args:
      redis-rate-limiter.replenishRate: 10      # 每秒恢复 token 数
      redis-rate-limiter.burstCapacity: 20      # 总 token 容量
      redis-rate-limiter.requestedTokens: 1     # 每个请求消耗 token 数
      key-resolver: "#{@ipKeyResolver}"         # 按 IP 限流

  # 熔断
  - name: CircuitBreaker
    args:
      name: userCircuitBreaker
      fallbackUri: forward:/fallback/users

其他实用过滤器

yaml
filters:
  # 保存原始 Host 头(防止网关 Host 覆盖)
  - PreserveHostHeader

  # 每请求超时
  - name: RequestTimeout
    args:
      time: 5s
      unit: seconds

🚩 5.2 GlobalFilter 全局过滤器实战

GlobalFilter 对所有路由生效,适合做认证、日志、TraceId 透传等全局逻辑。

实战 1:TraceId 透传 + 请求日志

java
package com.example.gateway.filter;

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.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.UUID;

/**
 * 全局过滤器:TraceId 透传 + 请求日志
 * 优先级 -100,确保在认证等过滤器之前执行
 */
@Component
public class TraceLogFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(TraceLogFilter.class);
    private static final String TRACE_ID_HEADER = "X-Trace-Id";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        ServerHttpRequest request = exchange.getRequest();

        // 1. 获取或生成 TraceId(从上游请求头取,或新建)
        String traceId = request.getHeaders().getFirst(TRACE_ID_HEADER);
        if (traceId == null || traceId.isBlank()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }

        // 2. 把 TraceId 写入日志上下文(MDC)+ 传给下游服务
        String finalTraceId = traceId;
        ServerHttpRequest mutatedRequest = request.mutate()
                .header(TRACE_ID_HEADER, finalTraceId)
                .build();

        // 3. 请求前日志
        log.info("[{}] → {} {} | IP: {}",
                finalTraceId,
                request.getMethod(),
                request.getURI().getPath(),
                request.getRemoteAddress());

        // 4. 执行请求链
        return chain.filter(exchange.mutate().request(mutatedRequest).build())
                .then(Mono.fromRunnable(() -> {
                    // 5. 响应后日志
                    long duration = System.currentTimeMillis() - startTime;
                    int status = exchange.getResponse().getStatusCode() != null
                            ? exchange.getResponse().getStatusCode().value()
                            : 0;
                    log.info("[{}] ← {} | Status: {} | {}ms",
                            finalTraceId,
                            request.getURI().getPath(),
                            status,
                            duration);
                }));
    }

    @Override
    public int getOrder() {
        // 数字越小越先执行
        // -200:最前(早于认证)
        return -200;
    }
}

实战 2:JWT 认证过滤器

java
package com.example.gateway.filter;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * JWT 认证全局过滤器
 * - 白名单路径直接放行
 * - 其他路径必须携带有效 Bearer Token
 * - 解析出用户信息透传给下游服务
 */
@Component
public class JwtAuthFilter implements GlobalFilter, Ordered {

    private static final String AUTH_HEADER = "Authorization";
    private static final String USER_ID_HEADER = "X-User-Id";
    private static final String USERNAME_HEADER = "X-Username";
    private static final String ROLE_HEADER = "X-Role";

    @Value("${jwt.secret:your-256-bit-secret-key-here-for-jwt-signing}")
    private String jwtSecret;

    /** 认证白名单(这些路径不需要 Token)*/
    private static final List<String> WHITELIST = List.of(
            "/api/auth/login",
            "/api/auth/register",
            "/api/public/",
            "/actuator/"
    );

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

        // 1. 白名单直接放行
        if (isWhitelisted(path)) {
            return chain.filter(exchange);
        }

        // 2. 提取 Token
        String token = extractToken(exchange.getRequest());
        if (token == null) {
            return unauthorized(exchange, "Missing token");
        }

        // 3. 验证并解析 Token
        Claims claims;
        try {
            SecretKey key = Keys.hmacShaKeyFor(jwtSecret.getBytes(StandardCharsets.UTF_8));
            claims = Jwts.parser()
                    .verifyWith(key)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();
        } catch (Exception e) {
            return unauthorized(exchange, "Invalid or expired token");
        }

        // 4. 把用户信息透传给下游服务
        String userId = claims.getSubject();
        String username = claims.get("username", String.class);
        String role = claims.get("role", String.class);

        ServerHttpRequest mutated = exchange.getRequest().mutate()
                .header(USER_ID_HEADER, userId)
                .header(USERNAME_HEADER, username != null ? username : "")
                .header(ROLE_HEADER, role != null ? role : "USER")
                .build();

        return chain.filter(exchange.mutate().request(mutated).build());
    }

    private boolean isWhitelisted(String path) {
        return WHITELIST.stream().anyMatch(path::startsWith);
    }

    private String extractToken(ServerHttpRequest request) {
        String header = request.getHeaders().getFirst(AUTH_HEADER);
        if (header != null && header.startsWith("Bearer ")) {
            return header.substring(7);
        }
        return null;
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String msg) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);

        String body = "{\"code\":401,\"message\":\"" + msg + "\"}";
        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    @Override
    public int getOrder() {
        // -100:在 TraceLogFilter(-200) 之后执行
        return -100;
    }
}

5.3 自定义 GatewayFilterFactory

当你需要在 YAML 里像内置过滤器一样使用自己的过滤器时,继承 AbstractGatewayFilterFactory

java
package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;

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

/**
 * 自定义网关过滤器工厂
 * 用法:filters: - name: CheckSign args: key=xxx, expire=300
 */
@Component
public class CheckSignGatewayFilterFactory
        extends AbstractGatewayFilterFactory<CheckSignGatewayFilterFactory.Config> {

    public CheckSignGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String sign = exchange.getRequest().getQueryParams().getFirst("sign");
            String timestamp = exchange.getRequest().getQueryParams().getFirst("timestamp");

            // 简单的签名校验示例(实际请用 HMAC 等安全算法)
            if (sign == null || timestamp == null) {
                exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.BAD_REQUEST);
                return exchange.getResponse().setComplete();
            }

            return chain.filter(exchange);
        };
    }

    // 配置类:YAML 中 args 的值映射到这里的字段
    public static class Config {
        private String key;
        private long expire = 300;  // 默认 300 秒
        // getter/setter 必须有!
        public String getKey() { return key; }
        public void setKey(String key) { this.key = key; }
        public long getExpire() { return expire; }
        public void setExpire(long expire) { this.expire = expire; }
    }

    // YAML 里 args 的字段顺序(可选,决定是否支持位置参数写法)
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("key", "expire");
    }
}

使用方式(与内置过滤器完全一致):

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: api-route
          uri: lb://user-service
          predicates:
            - Path=/api/**
          filters:
            # 命名参数
            - name: CheckSign
              args:
                key: my-secret-key
                expire: 300
            # 或者用位置参数(因为我们定义了 shortcutFieldOrder)
            - CheckSign=my-secret-key,300

6. 限流策略 💡

6.1 限流算法概述

算法原理适用场景
计数器固定时间窗口内计数粗略限流
滑动窗口时间窗口滑动,精度更高一般限流
漏桶固定速率处理请求,突发流量整形流量整形
令牌桶固定速率生成 token,请求消耗 tokenGateway 默认用这个

Gateway 的 RequestRateLimiter 底层基于 Redis 令牌桶算法

6.2 Redis 分布式限流(实战)

xml
<!-- 必须加:Redis Reactive 客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
yaml
spring:
  redis:
    host: localhost
    port: 6379
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 16
        max-idle: 8
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            # ↓↓↓ 限流过滤器(使用 SpEL 引用下面定义的 Bean)
            - name: RequestRateLimiter
              args:
                # 每秒补充多少 token(= QPS 平均值)
                redis-rate-limiter.replenishRate: 10
                # 令牌桶最大容量(= 允许突发的最大 QPS)
                redis-rate-limiter.burstCapacity: 30
                # 每个请求消耗多少 token(默认 1)
                redis-rate-limiter.requestedTokens: 1
                # Key 解析器:按什么维度限流
                key-resolver: "#{@ipKeyResolver}"

6.3 自定义 KeyResolver(限流维度)

java
package com.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;

/**
 * 限流维度配置
 * 根据业务需求选择按哪个维度做限流
 */
@Configuration
public class RateLimiterConfig {

    /** 按客户端 IP 限流(最常用) */
    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest()
                        .getRemoteAddress()
                        .getAddress()
                        .getHostAddress()
        );
    }

    /** 按用户 ID 限流(从请求头 X-User-Id 取) */
    @Bean
    public KeyResolver userKeyResolver() {
        return exchange -> {
            String userId = exchange.getRequest()
                    .getHeaders()
                    .getFirst("X-User-Id");
            return Mono.just(userId != null ? userId : "anonymous");
        };
    }

    /** 按 API 路径限流(保护具体接口) */
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(
                exchange.getRequest().getURI().getPath()
        );
    }

    /** 全局默认(不区分用户,所有请求共享一个桶) */
    @Bean
    public KeyResolver defaultKeyResolver() {
        return exchange -> Mono.just("global");
    }
}

💡 SpEL 语法#{@ipKeyResolver} 表示从 Spring 容器中找到名为 ipKeyResolver 的 Bean。


7. 熔断降级 💡

7.1 熔断器原理(三态转换)

      正常(Closed)───错误率≥50%───► 熔断(Open)
          ▲                              │
          │                          等待 10 秒后
          │                              ▼
          └────半开(Half-Open)◄──── 放一个请求探测

                    ├─ 成功 → 恢复 Closed
                    └─ 失败 → 回到 Open
状态含义行为
Closed正常(熔断器关闭)请求正常透传给后端服务
Open熔断(熔断器打开)请求直接走降级逻辑,不访问后端
Half-Open半开(探测恢复)只放一个请求过去测试

7.2 Resilience4j 集成(Spring Cloud 官方推荐)

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

🚩 7.3 完整熔断配置

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                # 熔断器名称(对应 resilience4j.instances 下的配置名)
                name: userCircuitBreaker
                # 降级跳转:forward 表示网关内部转发到 FallbackController
                fallbackUri: forward:/fallback/users

# Resilience4j 熔断器配置
resilience4j:
  circuitbreaker:
    configs:
      # 默认配置(所有实例共享)
      default:
        slidingWindowSize: 20                    # 滑动窗口大小(请求数)
        slidingWindowType: COUNT_BASED           # COUNT_BASED / TIME_BASED
        failureRateThreshold: 50                 # 失败率阈值(%),超过即熔断
        slowCallDurationThreshold: 3s            # 慢调用定义(>3秒)
        slowCallRateThreshold: 30                # 慢调用比例阈值(%)
        waitDurationInOpenState: 10s             # Open 状态等待多久后进入 Half-Open
        permittedNumberOfCallsInHalfOpenState: 5 # Half-Open 时允许通过几个探测请求
        minimumNumberOfCalls: 10                  # 最少多少个请求才开始统计(避免波动)
        registerHealthIndicator: true            # 注册到监控指标
    instances:
      # 这里的名称要与 filters 中 name: userCircuitBreaker 一致
      userCircuitBreaker:
        baseConfig: default                      # 继承 default

  # 超时控制(与熔断器配合使用)
  timelimiter:
    configs:
      default:
        timeoutDuration: 10s                    # 接口超时时间
        cancelRunningFuture: true
    instances:
      userCircuitBreaker:
        baseConfig: default

🚩 7.4 Fallback 降级处理

java
package com.example.gateway.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 降级处理控制器
 * forward:/fallback/users 会转发到这里的方法
 */
@RestController
@RequestMapping("/fallback")
public class FallbackController {

    /** 用户服务降级 */
    @GetMapping(value = "/users", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> userFallback() {
        return buildFallbackResponse("用户服务暂时不可用");
    }

    /** 订单服务降级 */
    @GetMapping(value = "/orders", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> orderFallback() {
        return buildFallbackResponse("订单服务暂时不可用");
    }

    /** 通用降级(从异常中获取信息) */
    @GetMapping(value = "/general", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Map<String, Object>> generalFallback() {
        // 从网关上下文获取异常信息(Resilience4j 会塞进去)
        return buildFallbackResponse("服务调用失败,已降级");
    }

    private ResponseEntity<Map<String, Object>> buildFallbackResponse(String message) {
        Map<String, Object> body = new LinkedHashMap<>();
        body.put("code", 503);
        body.put("message", message);
        body.put("timestamp", LocalDateTime.now().toString());
        body.put("data", null);
        return ResponseEntity
                .status(HttpStatus.SERVICE_UNAVAILABLE)
                .body(body);
    }
}

💡 fallbackUri 语法forward:/fallback/users 是网关内部转发,不会再经过认证等过滤器。如果用 redirect:/xxx 则是 302 重定向到外部 URL。


8. 高级特性

8.1 跨域处理(CORS)

全局配置(推荐)

yaml
spring:
  cloud:
    gateway:
      globalcors:
        # 所有路由生效
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"   # 允许所有来源(生产环境请指定具体域名)
            allowedMethods:               # 允许的 HTTP 方法
              - GET
              - POST
              - PUT
              - DELETE
              - OPTIONS
            allowedHeaders: "*"          # 允许所有请求头
            exposedHeaders:               # 暴露给前端读取的响应头
              - X-Request-Id
              - X-Total-Count
            allowCredentials: true       # 允许携带 Cookie/Session
            maxAge: 3600                  # 预检请求缓存时间(秒)
        # 解决请求头中有多个 Origin 时的问题
        add-to-simulated-request: false
        # 解决预检请求 OPTIONS 404 的问题
        allowed-headers: "*"

Java 代码配置

java
@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOriginPatterns(List.of("https://app.example.com", "https://admin.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(List.of("*"));
        config.setExposedHeaders(List.of("X-Request-Id"));
        config.setAllowCredentials(true);
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

8.2 动态路由

java
package com.example.gateway.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

/**
 * 动态路由管理服务
 * 可配合 Nacos Config 或数据库实现路由热更新
 */
@Service
public class DynamicRouteService {

    private final RouteDefinitionWriter writer;
    private final RouteDefinitionLocator locator;

    public DynamicRouteService(RouteDefinitionWriter writer, RouteDefinitionLocator locator) {
        this.writer = writer;
        this.locator = locator;
    }

    /** 添加/更新路由 */
    public void save(RouteDefinition definition) {
        writer.save(Mono.just(definition)).subscribe();
    }

    /** 删除路由 */
    public void delete(String routeId) {
        writer.delete(Mono.just(routeId)).subscribe();
    }

    /** 查看所有已生效路由 */
    public Flux<RouteDefinition> listAll() {
        return locator.getRouteDefinitions();
    }
}
java
/**
 * 管理接口:可以用 Postman/curl 动态管理路由
 */
@RestController
@RequestMapping("/admin/routes")
public class RouteManageController {

    private final DynamicRouteService service;

    @PostMapping
    public String add(@RequestBody RouteDefinition definition) {
        service.save(definition);
        return "success";
    }

    @DeleteMapping("/{id}")
    public String delete(@PathVariable String id) {
        service.delete(id);
        return "success";
    }

    @GetMapping
    public Flux<RouteDefinition> list() {
        return service.listAll();
    }
}

8.3 请求体日志(⚠️ 重点)

问题:HTTP 请求体只能读一次!如果 GlobalFilter 里读了,后端服务就读不到了。

解决方案:用 CacheRequestBody 装饰器缓存请求体。

java
package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * 请求体日志过滤器
 * 使用 CacheRequestBody 确保后端服务仍能正常读取 body
 */
@Component
public class RequestBodyLogFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 用 DataBufferUtils.join 把 body 读取并缓存
        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);

                    String bodyStr = new String(bytes, StandardCharsets.UTF_8);
                    // 打印请求体(注意:生产环境要脱敏!)
                    if (!bodyStr.isBlank()) {
                        System.out.println("Request Body: " + truncate(bodyStr, 2000));
                    }

                    // 2. 包装 Request,让 body 可重复读取
                    ServerHttpRequest mutated = new CachedBodyRequest(
                            exchange.getRequest(), bytes);
                    return chain.filter(exchange.mutate().request(mutated).build());
                });
    }

    private String truncate(String s, int max) {
        return s.length() > max ? s.substring(0, max) + "...(truncated)" : s;
    }

    @Override
    public int getOrder() { return -500; }  // 最前
}

/** 简单的 Body 缓存装饰器(实际项目建议用 spring-cloud-gateway-core 的现成实现) */
class CachedBodyRequest extends ServerHttpRequestDecorator {
    private final byte[] cachedBody;

    public CachedBodyRequest(ServerHttpRequest delegate, byte[] cachedBody) {
        super(delegate);
        this.cachedBody = cachedBody;
    }

    @Override
    public org.springframework.core.io.buffer.DataBuffer getBody() {
        // 每次被消费都返回同一个 body 副本(可重复读)
        return bufferFactory().wrap(cachedBody);
    }
}

💡 更简单的方案:Spring Cloud 自带 ModifyRequestBody GatewayFilter,配合 ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR 即可。

8.4 网关监控(Actuator)

yaml
management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway,metrics,prometheus
  endpoint:
    gateway:
      enabled: true
  metrics:
    tags:
      application: api-gateway

# 访问路由列表
# curl http://localhost:8080/actuator/gateway/routes

# 网关健康检查
# curl http://localhost:8080/actuator/health

9. 架构原理剖析 💡

9.1 WebFlux 响应式基础

Gateway 基于 Spring 5 WebFlux,底层是 Project Reactor(Publisher 模式)和 Netty(非阻塞 IO)。

请求处理链路(响应式管道):

HTTP Request


Reactor Netty(非阻塞 IO 接收)


HttpWebHandlerAdapter


GlobalFilter Chain(依次执行)


GatewayFilter Chain(依次执行)


Route 匹配(Predicate 校验)


LoadBalancer 选择实例


Reactor Netty(非阻塞 IO 转发给后端)


HTTP Response

Mono/Flux 简介

  • Mono<T>:0 或 1 个元素的异步序列(类似 CompletableFuture)
  • Flux<T>:0 到 N 个元素的异步序列(类似 Java Stream + 异步)

Gateway 所有过滤器方法都返回 Mono<Void>,表示"异步地完成过滤器逻辑"。

9.2 Reactor Netty 连接池配置

yaml
spring:
  cloud:
    gateway:
      # 全局响应式 HTTP 客户端配置(Reactor Netty)
      httpclient:
        # 连接超时
        connect-timeout: 5000
        # 响应读取超时(注意不是每个请求的总超时)
        response-timeout: 30s
        # 连接池
        pool:
          type: elastic       # elastic(弹性)/ fixed(固定)/ disabled
          max-connections: 500
          acquire-timeout: 4500ms
          max-idle-time: 60s
        # SSL
        ssl:
          trusted-x509-certificates:
            - classpath:certs/ca.pem

# 也可以对单条路由设置超时
spring.cloud.gateway.routes[0].filters[0] = name:RequestTimeout args:time:5s

10. 最佳实践与踩坑 ⚠️

⚠️ 10.1 不要引入 spring-boot-starter-web!

错误:APPLICATION FAILED TO START
Description:
Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway.

解决:排除 starter-web 依赖。如果你的 pom 父 POM 里带了,用 <exclusions> 排除。

⚠️ 10.2 请求体只能读一次

问题:在 GlobalFilter 里 exchange.getRequest().getBody() 读了之后,后端服务收到空 body。

解决:用 DataBufferUtils.join() + 自定义 ServerHttpRequestDecorator 缓存 body,或用 Spring 自带的 @ServerCodecConfigurer + HttpMessageReader

⚠️ 10.3 文件上传大小限制

yaml
spring:
  cloud:
    gateway:
      # Netty 内存帧大小限制(默认 256KB,上传大文件必须改)
      max-in-memory-size: 10MB
  # 如果你用了 Multipart
  servlet:
    multipart:
      max-file-size: 50MB
      max-request-size: 50MB

⚠️ 10.4 超时配置建议

yaml
# 两个超时的区别:
spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 5000        # 连接超时(建立 TCP 连接,通常几百ms 就够)
        response-timeout: 30s       # 响应超时(等待后端返回,业务总耗时)

# 路由级别覆盖
# filters: - name:RequestTimeout args:time:10s

⚠️ 10.5 性能调优清单

项目建议
JVM-Xms512m -Xmx2g -XX:+UseG1GC(4核8G 起步)
连接池pool.type=elasticmax-connections=500
日志生产环境 INFO 以上,TRACE 会丢性能
Predicate用 Path + Method 最精确,避免 Host 等开销大的
过滤器精简,能 GlobalFilter 做的不要在 GatewayFilter 重复
Nacos开启 spring.cloud.nacos.discovery.watch.enabled
DNS网关到注册中心用内网地址,不要走公网

11. 完整实战示例 🚩

电商系统网关 application.yml 完整配置

yaml
server:
  port: 8080

spring:
  application:
    name: mall-gateway

  profiles:
    active: dev

  cloud:
    nacos:
      discovery:
        server-addr: ${NACOS_HOST:localhost}:8848
        namespace: public
    gateway:
      # 跨域全局配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOriginPatterns: "*"
            allowedMethods: [GET, POST, PUT, DELETE, OPTIONS]
            allowedHeaders: "*"
            exposedHeaders: [X-Request-Id, X-Total-Count]
            allowCredentials: true
            maxAge: 3600

      # HTTP 客户端(Reactor Netty)
      httpclient:
        connect-timeout: 5000
        response-timeout: 15s
        pool:
          type: elastic
          max-connections: 500
          acquire-timeout: 4500ms

      # 请求体缓存大小
      max-in-memory-size: 10MB

      # 路由表
      routes:
        # ===== 用户服务 =====
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**,/api/auth/**
            - Header=X-Request-Id, \d+
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 20
                redis-rate-limiter.burstCapacity: 50
                key-resolver: "#{@userKeyResolver}"
            - name: CircuitBreaker
              args:
                name: userCB
                fallbackUri: forward:/fallback/general
          order: 1

        # ===== 商品服务 =====
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**,/api/categories/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 50
                redis-rate-limiter.burstCapacity: 100
                key-resolver: "#{@ipKeyResolver}"
            - name: CircuitBreaker
              args:
                name: productCB
                fallbackUri: forward:/fallback/general
          order: 2

        # ===== 订单服务 =====
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**,/api/cart/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: orderCB
                fallbackUri: forward:/fallback/general
          order: 3

        # ===== 搜索服务(灰度路由) =====
        - id: search-service-v1
          uri: lb://search-service
          predicates:
            - Path=/api/search/**
            - Header=X-Env, prod
          order: 10
        - id: search-service-canary
          uri: lb://search-service-canary
          predicates:
            - Path=/api/search/**
            - Header=X-Env, canary
          order: 10

# Resilience4j 熔断配置
resilience4j:
  circuitbreaker:
    configs:
      default:
        slidingWindowSize: 20
        slidingWindowType: COUNT_BASED
        failureRateThreshold: 50
        waitDurationInOpenState: 10s
        permittedNumberOfCallsInHalfOpenState: 5
        minimumNumberOfCalls: 10
        registerHealthIndicator: true
    instances:
      userCB:       { baseConfig: default }
      productCB:    { baseConfig: default, failureRateThreshold: 30 }
      orderCB:      { baseConfig: default, slidingWindowSize: 10 }

# Redis(限流用)
spring.redis:
  host: ${REDIS_HOST:localhost}
  port: 6379
  timeout: 3000ms
  lettuce.pool:
    max-active: 32
    max-idle: 16
    min-idle: 4

# 监控
management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway,metrics
  endpoint:
    gateway:
      enabled: true

logging:
  level:
    root: info
    org.springframework.cloud.gateway: warn
    reactor.netty.http.client: warn

12. 小结

┌──────────────────────────────────────────────────────┐
│              Spring Cloud Gateway 知识地图            │
├──────────────────────────────────────────────────────┤
│ ⭐ 核心概念                                           │
│   Route(路由)· Predicate(谓词)· Filter(过滤器)  │
├──────────────────────────────────────────────────────┤
│ 🎯 必掌握功能                                         │
│   YAML 路由 · 11种谓词 · 20+过滤器 · JWT认证          │
│   Redis 限流 · Resilience4j 熔断 · 全局 CORS           │
├──────────────────────────────────────────────────────┤
│ 💡 重点难点                                           │
│   WebFlux 响应式模型 · 连接池配置                     │
│   RequestBody 只能读一次的问题                         │
│   熔断三态转换逻辑                                     │
├──────────────────────────────────────────────────────┤
│ ⚠️ 避坑要点                                           │
│   不能引 starter-web · $转义 · 超时两层配置            │
│   文件上传 max-in-memory-size                          │
├──────────────────────────────────────────────────────┤
│ 🚩 实战代码                                           │
│   TraceLogFilter · JwtAuthFilter · RequestRateLimiter  │
│   FallbackController · 电商网关完整 application.yml    │
└──────────────────────────────────────────────────────┘

一句话总结:Gateway = WebFlux + Reactor Netty + 路由规则引擎,核心就是 Route(去哪儿) + Predicate(什么时候去) + Filter(去之前/回来后做什么)。