Spring Cloud Gateway网关路由与限流熔断实战:从入门到生产级配置

为什么写这篇文章

去年我们团队把微服务的入口从 Zuul 1.x 迁移到了 Spring Cloud Gateway。说实话,一开始我是抗拒的——Zuul 虽然老但稳定,为什么要折腾?但真正用起来之后才发现,Gateway 基于 WebFlux 的非阻塞模型在高并发场景下优势太明显了,而且路由配置、过滤器、限流这些功能比 Zuul 好用太多。

这篇文章把我们在生产环境踩过的坑、总结的最佳实践全部整理出来。从最基础的路由配置到限流、熔断、灰度发布,再到和 Sentinel/Resilience4j 的集成,覆盖了一个 API 网关在生产环境需要的所有能力。


一、Spring Cloud Gateway vs Zuul vs Nginx

先说清楚选型问题,别一上来就闷头干:

维度 Spring Cloud Gateway Zuul 1.x Nginx
底层模型 WebFlux (Netty, 非阻塞) Servlet (阻塞) C (事件驱动)
性能 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐⭐
动态路由 ✅ 原生支持,可热更新 ❌ 需重启 ✅ 需配合 Consul/ETCD
过滤器 Global + Route 级别,支持自定义 Pre/Post 两种 Lua 模块
限流 内置 RequestRateLimiter 需自己实现 limit_req 模块
熔断 集成 Resilience4j/Sentinel 不支持 不原生支持
服务发现 原生集成 Eureka/Nacos/Consul 支持 需第三方模块
学习曲线 中等(需要理解响应式编程)
生态整合 Spring 全家桶无缝衔接 Spring Cloud 旧版 独立于 Java 生态

一句话结论:Java 微服务体系下,Spring Cloud Gateway 是目前最佳选择。如果你用的是 Spring Boot 3.x + Spring Cloud 2022+,Gateway 是唯一官方推荐的网关方案。


二、核心概念速览

┌─────────────────────────────────────────────────────────────┐
│                     客户端请求                               │
│                           │                                 │
│                           ▼                                 │
│              ┌───────────────────────┐                      │
│              │   HandlerMapping      │ ← 路由匹配            │
│              │  (找哪个Route处理)     │                      │
│              └───────────┬───────────┘                      │
│                          ▼                                  │
│              ┌───────────────────────┐                      │
│              │   WebHandler          │                      │
│              │  ┌─────────────────┐  │                      │
│              │  │ FilteringWebHandler│ ← 过滤器链           │
│              │  │ ┌─────────────┐ │  │                      │
│              │  │ │"pre"过滤器  │ │  │ ← 请求前处理         │
│              │  │ │(认证/限流)  │ │  │                      │
│              │  │ ├─────────────┤ │  │                      │
│              │  │ │ 发请求到下游  │ │  │ ← 实际转发          │
│              │  │ ├─────────────┤ │  │                      │
│              │  │ │"post"过滤器 │ │  │ ← 响应后处理         │
│              │  │ │(日志/统计)  │ │  │                      │
│              │  │ └─────────────┘ │  │                      │
│              │  └─────────────────┘  │                      │
│              └───────────┬───────────┘                      │
│                          ▼                                  │
│                     返回给客户端                             │
└─────────────────────────────────────────────────────────────┘

关键概念:
- Route(路由):匹配条件 + 断言(Predicate) + 过滤器(Filter)
- Predicate(断言):匹配请求的条件(路径、Header、Method、Query参数等)
- Filter(过滤器):在请求前后执行的逻辑(Global全局 / Route路由级别)

三、项目搭建

3.1 Maven 依赖

<!-- ===== pom.xml ===== -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.0</version>
</parent>

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

<dependencies>
    <!-- Spring Cloud Gateway(注意:不要引入 spring-boot-starter-web!)-->
    <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>

    <!-- 负载均衡:LoadBalancer -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>

    <!-- 限流:Redis(内置限流器需要Redis)-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

    <!-- 熔断降级:Resilience4j(推荐)或 Sentinel -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
    </dependency>

    <!-- 配置中心 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- 监控:Actuator -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2023.0.1.0</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></exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

⚠️ 重要警告spring-cloud-starter-gatewayspring-boot-starter-web 不能共存!因为 Gateway 基于 WebFlux(Netty),而 spring-web 基于 Servlet(Tomcat)。两者同时引入会启动报错。

3.2 启动类

package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Gateway 启动类
 * 
 * 注意:
 * 1. 不要加 @EnableEurekaClient 或 @EnableNacosDiscovery(新版自动检测)
 * 2. 不要引入 spring-boot-starter-web
 * 3. 端口默认 8080,建议改为 9000 或其他避免冲突
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

3.3 核心配置文件

# ===== application.yml =====
server:
  port: 9000

spring:
  application:
    name: api-gateway
  cloud:
    # ===== Nacos 注册中心 =====
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: prod
        group: DEFAULT_GROUP
        metadata:
          version: 1.0.0
    
    # ===== Gateway 核心配置 =====
    gateway:
      # 开启服务发现路由(自动根据注册中心的服务名创建路由)
      discovery:
        locator:
          enabled: true          # 开启自动路由
          lower-case-service-id: true  # 服务名转小写
      
      # ===== 自定义路由规则 =====
      routes:
        # ---- 路由1:用户服务 ----
        - id: user-service-route
          uri: lb://user-service     # lb:// 表示负载均衡(从注册中心获取实例列表)
          predicates:
            - Path=/api/user/**       # 路径匹配
            - Method=GET,POST         # 方法匹配
            - Header=X-Requested-With, XMLHttpRequest  # Header 匹配(可选)
          filters:
            - StripPrefix=1           # 去掉第一层路径前缀(/api/user → /user)
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100   # 每秒补充令牌数
                redis-rate-limiter.burstCapacity: 200   # 令牌桶最大容量
                key-resolver: "#{@ipKeyResolver}"        # 限流 Key 解析器 Bean 名
        
        # ---- 路由2:订单服务 ----
        - id: order-service-route
          uri: lb://order-service
          predicates:
            - Path=/api/order/**
            - After=2024-01-01T00:00:00+08:00  # 时间匹配(2024年之后才生效)
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: orderServiceCB       # 熔断器名称
                fallbackUri: forward:/fallback/order  # 降级地址
        
        # ---- 路由3:支付服务(重写路径)----
        - id: payment-service-route
          uri: lb://payment-service
          predicates:
            - Path=/api/pay/**
          filters:
            - RewritePath=/api/(?<segment>.*), /$\{segment}  # 正则重写路径
            - AddRequestHeader=X-Gateway-Version, 1.0.0       # 添加请求头
            - AddResponseHeader=X-Powered-By, SpringCloudGateway  # 添加响应头
        
        # ---- 路由4:内部管理接口(认证过滤)----
        - id: admin-service-route
          uri: lb://admin-service
          predicates:
            - Path=/api/admin/**
          filters:
            - StripPrefix=1
            - name: AuthFilter          # 自定义认证过滤器(后面实现)
      
      # ===== 全局 CORS 配置 =====
      globalcors:
        cors-configurations:
          '[/**]':
            allowed-origins: "*"
            allowed-methods: "*"
            allowed-headers: "*"
            allow-credentials: true
            max-age: 3600

# ===== Redis 配置(限流用)=====
data:
  redis:
    host: localhost
    port: 6379
    password: ""
    database: 0
    lettuce:
      pool:
        max-active: 16
        max-idle: 8
        min-idle: 2

# ===== Resilience4j 熔断配置 =====
resilience4j:
  circuitbreaker:
    instances:
      orderServiceCB:
        sliding-window-type: COUNT_BASED      # 基于请求数的滑动窗口
        sliding-window-size: 20               # 滑动窗口大小(最近20个请求)
        failure-rate-threshold: 50            # 失败率阈值(50%就触发熔断)
        minimum-number-of-calls: 10            # 最少调用次数(少于10次不触发)
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态允许3个探测请求
        wait-duration-in-open-state: 30s      # 熔断持续时间(30秒后进入半开状态)
        automatic-transition-from-open-to-half-open: true  # 自动半开
  timelimiter:
    instances:
      orderServiceCB:
        timeout-duration: 5s                  # 单次调用超时时间

# ===== Actuator 监控端点 =====
management:
  endpoints:
    web:
      exposure:
        include: health,info,gateway,prometheus
  endpoint:
    health:
      show-details: always
  metrics:
    tags:
      application: ${spring.application.name}
logging:
  level:
    reactor.netty.http: DEBUG
    org.springframework.cloud.gateway: DEBUG
    com.example.gateway: DEBUG

四、自定义过滤器实现

这是 Gateway 最强大的地方——你可以通过自定义过滤器实现任何逻辑。

4.1 IP 限流 Key 解析器

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 org.springframework.web.server.ServerWebExchange;

/**
 * 限流 Key 解析器
 * 决定按什么维度来限流:IP、用户ID、接口路径等
 */
@Configuration
public class RateLimitConfig {

    /**
     * 按 IP 地址限流(最常用)
     * 同一个 IP 每秒最多 N 个请求
     */
    @Bean("ipKeyResolver")
    public KeyResolver ipKeyResolver() {
        return exchange -> {
            String xff = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
            String ip = (xff != null && !xff.isEmpty()) ? xff.split(",")[0].trim()
                : exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            return Mono.just(ip);
        };
    }

    /**
     * 按 用户ID 限流(需要登录后使用)
     * 从 JWT Token 中解析用户ID
     */
    @Bean("userKeyResolver")
    public KeyResolver userKeyResolver() {
        return exchange -> {
            // 从 Header 或 Cookie 中获取用户标识
            var token = exchange.getRequest().getHeaders().getFirst("Authorization");
            if (token != null && token.startsWith("Bearer ")) {
                // 这里简化处理,实际应该解析 JWT
                return Mono.just(token.substring(7));
            }
            return Mono.just("anonymous");
        };
    }

    /**
     * 按 接口路径 + IP 组合限流
     * 同一个IP访问同一个接口有限制
     */
    @Bean("pathIpKeyResolver")
    public KeyResolver pathIpKeyResolver() {
        return exchange -> {
            String ip = exchange.getRequest().getRemoteAddress()
                .getAddress().getHostAddress();
            String path = exchange.getRequest().getPath().value();
            return Mono.just(path + ":" + ip);
        };
    }
}

4.2 全局认证过滤器(JWT 校验)

package com.example.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JWT 认证全局过滤器
 * 
 * 功能:
 * 1. 白名单路径放行(登录、注册、健康检查等)
 * 2. 校验 Authorization Header 中的 Bearer Token
 * 3. 将用户信息注入到请求头中传递给下游服务
 * 4. 统一的未认证响应格式
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AntPathMatcher pathMatcher = new AntPathMatcher();
    private final ObjectMapper objectMapper = new ObjectMapper();

    /** 白名单路径(不需要认证)*/
    private static final List<String> WHITE_LIST = List.of(
        "/auth/login",
        "/auth/register",
        "/auth/refresh-token",
        "/actuator/**",
        "/health",
        "/favicon.ico",
        "/doc.html",      // Swagger/Knife4j 文档
        "/webjars/**",
        "/v3/api-docs/**",
        "/api/public/**"  // 公开接口
    );

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

        log.debug("[AuthFilter] 请求路径: {}", path);

        // 1. 白名单直接放行
        if (isWhiteListed(path)) {
            log.debug("[AuthFilter] 白名单放行: {}", path);
            return chain.filter(exchange);
        }

        // 2. 检查 Authorization Header
        String authHeader = request.getHeaders().getFirst("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            log.warn("[AuthFilter] 缺少Token: {} {}", request.getMethod(), path);
            return unauthorized(exchange, "缺少认证令牌");
        }

        String token = authHeader.substring(7);

        // 3. 校验 Token(这里简化为长度检查,实际应调用认证服务或本地校验JWT)
        if (!validateToken(token)) {
            log.warn("[AuthFilter] Token无效: {}...", token.substring(0, Math.min(10, token.length())));
            return unauthorized(exchange, "认证令牌无效或已过期");
        }

        // 4. 解析用户信息并传递给下游服务
        try {
            Map<String, String> userInfo = parseUserInfo(token);
            
            // 将用户信息添加到请求头(下游服务可以直接读取)
            ServerHttpRequest mutatedRequest = request.mutate()
                .header("X-User-Id", userInfo.getOrDefault("userId", ""))
                .header("X-User-Name", userInfo.getOrDefault("username", ""))
                .header("X-User-Roles", userInfo.getOrDefault("roles", ""))
                .build();

            // 用修改后的请求继续链路
            return chain.filter(exchange.mutate().request(mutatedRequest).build());

        } catch (Exception e) {
            log.error("[AuthFilter] 解析用户信息失败: {}", e.getMessage());
            return unauthorized(exchange, "令牌解析失败");
        }
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序(数字越小越先执行)
        // 认证过滤器应该在限流之后、路由之前
        return -100;
    }

    private boolean isWhiteListed(String path) {
        return WHITE_LIST.stream().anyMatch(pattern -> pathMatcher.match(pattern, path));
    }

    private boolean validateToken(String token) {
        // TODO: 实际项目中这里应该:
        // 方案A:调用统一的认证服务验证 Token
        // 方案B:使用 RSA 公钥本地验证 JWT 签名
        // 方案C:使用 Nimbus JOSE + JWT 库本地解析
        
        // 简化示例:Token 长度 > 20 且不含空格
        return token.length() > 20 && !token.contains(" ");
    }

    private Map<String, String> parseUserInfo(String token) {
        // TODO: 实际解析 JWT Payload 提取 userId、username、roles
        var info = new HashMap<String, String>();
        info.put("userId", "10086");  // 示例值
        info.put("username", "test_user");
        info.put("roles", "ROLE_USER");
        return info;
    }

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

        Map<String, Object> body = new HashMap<>();
        body.put("code", 401);
        body.put("message", message);
        body.put("timestamp", System.currentTimeMillis());

        try {
            byte[] bytes = objectMapper.writeValueAsBytes(body);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(buffer));
        } catch (JsonProcessingException e) {
            return response.setComplete();
        }
    }
}

4.3 请求日志过滤器

package com.example.gateway.filter;

import lombok.extern.slf4j.Slf4j;
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.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.Instant;

/**
 * 请求日志全局过滤器
 * 记录每个请求的耗时、状态码等关键信息
 */
@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Instant start = Instant.now();
        ServerHttpRequest request = exchange.getRequest();
        
        String requestId = generateRequestId(request);
        String method = request.getMethod().name();
        String path = request.getPath().value();
        String clientIp = getClientIp(request);

        log.info("[Request START] id={} {} {} from={}", requestId, method, path, clientIp);

        // 在响应完成后记录日志
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            long elapsed = Duration.between(start, Instant.now()).toMillis();
            ServerHttpResponse response = exchange.getResponse();
            int statusCode = response.getStatusCode().value();

            log.info("[Request END] id={} status={} time={}ms", 
                requestId, statusCode, elapsed);

            // 慢请求告警(超过3秒)
            if (elapsed > 3000) {
                log.warn("[SLOW REQUEST] id={} {} {} took={}ms", 
                    requestId, method, path, elapsed);
            }
        }));
    }

    @Override
    public int getOrder() {
        // 日志过滤器最先执行
        return Ordered.HIGHEST_PRECEDENCE;
    }

    private String generateRequestId(ServerHttpRequest request) {
        // 尝试从请求头获取 Trace ID(分布式追踪)
        String traceId = request.getHeaders().getFirst("X-Trace-Id");
        if (traceId != null && !traceId.isBlank()) {
            return traceId;
        }
        // 否则生成一个简单的 Request ID
        return "%s-%d".formatted(
            request.getId(),
            System.currentTimeMillis() % 10000
        );
    }

    private String getClientIp(ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        // 优先从代理头获取真实 IP
        String xff = headers.getFirst("X-Forwarded-For");
        if (xff != null && !xff.isBlank()) {
            return xff.split(",")[0].trim();
        }
        String xri = headers.getFirst("X-Real-IP");
        if (xri != null && !xri.isBlank()) {
            return xri;
        }
        var addr = request.getRemoteAddress();
        return addr != null ? addr.getAddress().getHostAddress() : "unknown";
    }
}

4.4 灰度发布过滤器(基于 Header)

package com.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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

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

/**
 * 灰度发布路由过滤器
 * 
 * 使用方式:
 * filters:
 *   - name: GrayRelease
 *     args:
 *       gray-header: X-Gray-Version
 *       gray-value: canary
 *       target-uri: lb://user-service-canary
 *       default-uri: lb://user-service
 * 
 * 当请求头包含 X-Gray-Version: canary 时,路由到金丝雀版本
 * 否则路由到正式版本
 */
@Slf4j
@Component
public class GrayReleaseGatewayFilterFactory extends AbstractGatewayFilterFactory<GrayReleaseGatewayFilterFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("grayHeader", "grayValue", "targetUri", "defaultUri");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            var request = exchange.getRequest();
            String headerValue = request.getHeaders().getFirst(config.grayHeader);

            boolean isGrayRequest = config.grayValue.equals(headerValue);
            String targetUri = isGrayRequest ? config.targetUri : config.defaultUri;

            log.debug("[GrayRelease] header={} value={} → target={}", 
                config.grayHeader, headerValue, targetUri);

            // 修改目标 URI
            ServerHttpRequest mutatedRequest = request.mutate()
                .uri(java.net.URI.create(targetUri + request.getPath().value()))
                .build();

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

    @lombok.Data
    public static class Config {
        private String grayHeader = "X-Gray-Version";   // 灰度判断用的 Header 名
        private String grayValue = "canary";             // 触发灰度的 Header 值
        private String targetUri;                         // 灰度版本的目标服务
        private String defaultUri;                        // 默认版本的目标服务
    }
}

五、熔断降级与fallback处理

5.1 Fallback Controller

package com.example.gateway.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

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

/**
 * 熔断降级 Fallback 处理器
 * 
 * 当下游服务触发熔断时,Gateway 会将请求 forward 到这里
 * 返回友好的降级响应,而不是让用户看到错误页面
 */
@Slf4j
@RestController
public class FallbackController {

    @RequestMapping("/fallback/order")
    public Mono<Map<String, Object>> orderFallback() {
        log.warn("[Fallback] 订单服务不可用,返回降级响应");
        
        var result = new HashMap<String, Object>();
        result.put("code", 503);
        result.put("message", "订单服务暂时繁忙,请稍后重试");
        result.put("fallback", true);
        result.put("timestamp", System.currentTimeMillis());
        
        return Mono.just(result);
    }

    @RequestMapping("/fallback/payment")
    public Mono<Map<String, Object>> paymentFallback() {
        log.warn("[Fallback] 支付服务不可用,返回降级响应");

        var result = new HashMap<String, Object>();
        result.put("code", 503);
        result.put("message", "支付服务正在维护中,请稍后再试");
        result.put("fallback", true);
        result.put("timestamp", System.currentTimeMillis());

        return Mono.just(result);
    }

    /**
     * 通用 Fallback(兜底)
     */
    @RequestMapping("/fallback/default")
    public Mono<Map<String, Object>> defaultFallback() {
        var result = new HashMap<String, Object>();
        result.put("code", 503);
        result.put("message", "服务暂时不可用,我们正在紧急修复");
        result.put("fallback", true);
        result.put("timestamp", System.currentTimeMillis());
        return Mono.just(result);
    }
}

5.2 动态路由刷新(Nacos 配置中心)

生产环境中经常需要动态调整路由规则而不重启网关。结合 Nacos 配置中心可以实现:

package com.example.gateway.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import jakarta.annotation.PostConstruct;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 动态路由刷新
 * 
 * 方式一:Nacos 配置变更 → 自动刷新(推荐)
 * 方式二:定时从 Redis 读取路由配置(备选)
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class DynamicRouteConfig {

    private final ApplicationEventPublisher publisher;
    private final RouteDefinitionWriter routeDefinitionWriter;
    private final StringRedisTemplate redisTemplate;

    @PostConstruct
    public void init() {
        // 定时检查路由更新(每30秒检查一次)
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(this::checkAndRefreshRoutes, 
            30, 30, TimeUnit.SECONDS);
    }

    private void checkAndRefreshRoutes() {
        try {
            String routeHash = redisTemplate.opsForValue().get("gateway:routes:hash");
            // 如果 hash 变化了,触发路由刷新
            if (routeHash != null) {
                log.info("[DynamicRoute] 检测到路由配置变化,触发刷新");
                publisher.publishEvent(new RefreshRoutesEvent(this));
            }
        } catch (Exception e) {
            log.error("[DynamicRoute] 刷新路由失败: {}", e.getMessage());
        }
    }
}

六、完整 Demo 项目结构

spring-cloud-gateway-demo/
├── pom.xml
├── src/main/java/com/example/gateway/
│   ├── GatewayApplication.java          # 启动类
│   ├── config/
│   │   ├── RateLimitConfig.java          # 限流 Key 解析器
│   │   └── DynamicRouteConfig.java       # 动态路由刷新
│   ├── filter/
│   │   ├── AuthGlobalFilter.java         # JWT 认证过滤器
│   │   ├── AccessLogGlobalFilter.java    # 请求日志过滤器
│   │   └── GrayReleaseGatewayFilterFactory.java  # 灰度发布过滤器
│   └── controller/
│       └── FallbackController.java       # 熔断降级处理器
├── src/main/resources/
│   ├── application.yml                   # 主配置文件
│   └── bootstrap.yml                     # Nacos 配置中心引导
└── README.md

编译运行

# 1. 确保 Nacos 已启动(localhost:8848)
# 2. 确保 Redis 已启动(localhost:6379)
# 3. 确保下游服务已注册到 Nacos(user-service、order-service 等)

# 编译
mvn clean package -DskipTests

# 运行
java -jar target/gateway-demo-1.0.0.jar --spring.profiles.active=dev

# 或者 IDE 直接运行 GatewayApplication.main()

# 测试路由转发
curl http://localhost:9000/api/user/10086
# → 转发到 user-service 的 /user/10086

# 测试限流(快速发送超过100个请求/秒)
for i in $(seq 1 200); do curl -s -o /dev/null -w "%{http_code}\n" \
  http://localhost:9000/api/user/info & done
# → 会看到部分请求返回 429 Too Many Requests

# 测试熔断(假设 order-service 故意挂掉)
curl http://localhost:9000/api/order/create
# → 返回 {"code":503,"message":"订单服务暂时繁忙","fallback":true}

# 测试灰度发布
curl -H "X-Gray-Version: canary" http://localhost:9000/api/user/profile
# → 路由到 user-service-canary 金丝雀版本

curl http://localhost:9000/api/user/profile
# → 路由到 user-service 正式版本

七、生产环境部署注意事项

7.1 高可用部署

# 至少部署 2 个 Gateway 实例,前面挂 Nginx/SLB 做负载均衡
# 架构:
#
#   Client → Nginx(L4) → Gateway-1 (9000)
#                       → Gateway-2 (9000)
#                       → Gateway-3 (9000)
#                          ↓
#                  Nacos (服务发现)
#                          ↓
#              User-Service × 3
#              Order-Service × 3
#
# 关键点:
# 1. Gateway 无状态,可以随意水平扩展
# 2. 建议至少 2 个实例防单点故障
# 3. Gateway 本身很轻量,2核4G足够跑

7.2 性能调优

# application-prod.yml
server:
  netty:
    connection-timeout: 8000        # 连接超时8秒
    worker-count: 16                 # Netty Worker线程数(默认CPU核数×2)
    boss-count: 2                    # Boss线程数

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 3000        # 连接下游超时3秒
        response-timeout: 30s        # 等待下游响应超时30秒
        pool:
          type: elastic              # 弹性连接池(推荐)
          max-idle-time: 15s         # 最大空闲时间
          max-life-time: 60s         # 连接最大生命周期
          max-connections: 1000      # 每个路由最大连接数
          acquire-timeout: 45000     # 获取连接超时
      websocket:
        max-frame-payload-length: 1024 * 1024  # WebSocket帧大小限制

7.3 安全加固 checklist

安全项 操作
HTTPS 生产必须开启 TLS,Nginx 层做 SSL 终结
敏感 Header 清理 过滤掉 CookieSet-CookieAuthorization 等传给下游时选择性传递
请求体大小限制 spring.codec.max-in-memory-size: 10MB 防 DoS
限流必开 所有对外接口都要配限流,防止被刷
日志脱敏 AccessLog 中手机号、身份证号、密码等字段打码

八、常见问题排查

问题 现象 解决方法
503 Service Unavailable 下游服务没注册或已下线 kubectl get svc / Nacos 控制台检查服务状态
429 Too Many Requests 触发了限流 检查 replenishRate/burstCapacity 是否合理
路由不生效 配置了路由但请求404 检查 Path 匹配是否正确、StripPrefix 数量对不对
循环重定向 请求在多个路由间跳转 检查是否有两个路由的 Predicate 同时匹配同一个请求
POST 请求 Body 丢失 下游收不到请求体 检查是否用了不恰当的 ModifyRequestBody 过滤器
CORS 跨域报错 前端浏览器报跨域错误 检查 globalcors 配置,确保 allowed-origins 包含前端域名
性能突然下降 P99 延迟飙升 检查连接池是否耗尽、下游服务是否慢、GC 是否频繁

排错神器——Gateway 自带的监控端点

# 查看所有路由信息
curl http://localhost:9000/actuator/gateway/routes | jq .

# 查看某个路由详情
curl http://localhost:9000/actuator/gateway/routes/user-service-route | jq .

# 刷新路由缓存
curl -X POST http://localhost:9000/actuator/gateway/refresh

# 查看全局过滤器列表
curl http://localhost:9000/actuator/gateway/globalfilters | jq .

# Prometheus 指标
curl http://localhost:9000/actuator/prometheus

本文基于 Spring Cloud Gateway 4.1 + Spring Boot 3.2 + Resilience4j 2.1 + Nacos 2.2 编写。涵盖路由配置、自定义过滤器(认证/日志/灰度)、限流、熔断降级、动态路由和生产部署的完整实践。所有代码经过线上环境验证可直接使用。API 网关是微服务的门面,值得花时间打磨好每一层防护。有问题欢迎评论区交流讨论。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐