一、核心概念

1.1 什么是 API 网关?

API 网关是微服务架构中的入口点 (Entry Point),所有客户端请求都首先经过网关。它负责路由、认证、限流、监控、协议转换等横切关注点(Cross-Cutting Concerns),将非业务逻辑从微服务中剥离。

1.2 Spring Cloud Gateway 是什么?

Spring Cloud Gateway 是 Spring 官方基于 Spring 5, Spring Boot 2 和 Project Reactor 构建的响应式 API 网关。它旨在提供一种简单而有效的方式来路由到 API,并为它们提供横切关注点。

1.3 核心组件

组件 说明
Route (路由) 网关的基本构建块。它由一个 ID、一个目标 URI、一组断言(Predicates)和一组过滤器(Filters)组成。网关根据断言匹配请求,匹配成功则将请求路由到目标 URI。
Predicate (断言) Java 8 的 Predicate。输入类型是 Spring Framework 的 ServerWebExchange。网关根据断言来匹配 HTTP 请求中的某些属性(如路径、方法、Header、参数等),决定该路由是否适用。
Filter (过滤器) 用于修改请求和响应的机制。分为 GatewayFilter(作用于特定路由)和 GlobalFilter(作用于所有路由)。可以在请求被路由前(pre)或后(post)执行逻辑。
Handler Mapping 负责将请求匹配到相应的路由。

1.4 关键特性

  • 基于 Spring Framework 5, Project Reactor 和 Spring Boot 2:响应式、非阻塞。
  • 动态路由:支持多种方式定义路由规则(配置文件、代码、注册中心)。
  • 谓词和过滤器:强大的匹配和修改能力。
  • 集成 Hystrix 断路器(已维护)或 Resilience4j。
  • 集成 Spring Cloud DiscoveryClient:自动从注册中心(如 Eureka, Nacos)发现服务并创建路由。
  • 易于扩展:支持自定义 Predicate 和 Filter。
  • 限流 (Rate Limiting):支持基于 Redis 的限流。
  • 路径重写 (Rewrite Path):修改请求路径。
  • CORS 配置:跨域资源共享支持。

1.5 与 Zuul 1.x 的对比

特性 Spring Cloud Gateway Zuul 1.x
编程模型 响应式 (Reactive),非阻塞 阻塞式 (Blocking),基于 Servlet 2.5
性能 (得益于非阻塞 I/O) 相对较低
依赖 Spring 5, WebFlux, Reactor Servlet API, Spring MVC
长连接支持 更好 较差
状态 推荐 已进入维护模式

结论新项目必须使用 Spring Cloud Gateway,Zuul 1.x 仅用于维护旧项目。


二、详细操作步骤

步骤 1:创建 Spring Cloud Gateway 项目

  1. 使用 Spring Initializr 创建项目

    • Group: com.example
    • Artifact: gateway-service
    • Dependencies:
      • Spring WebFlux (或 Reactive Web)
      • Spring Cloud Gateway
      • Spring Cloud Discovery Client (如果需要服务发现)
      • Spring Boot Actuator (可选,用于监控)
  2. 检查 pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- 如果需要从 Nacos/Eureka 自动发现服务 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- 或 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- Actuator (可选) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>
    
  3. 主启动类

    // GatewayApplication.java
    @SpringBootApplication
    public class GatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
        }
    }
    // 注意:**不需要** `@Enable*` 注解,Gateway 会自动配置。
    

步骤 2:配置静态路由(基于配置文件)

这是最常见的方式,通过 application.yml 定义路由规则。

# application.yml
server:
  port: 8080 # 网关端口

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      routes:
        # 路由 1: 路由到 order-service
        - id: order-service-route # 路由唯一标识
          uri: http://localhost:8081 # 目标服务地址 (静态)
          predicates:
            - Path=/api/orders/** # 断言:匹配 /api/orders 开头的路径
          filters:
            - StripPrefix=1 # 过滤器:去掉路径前缀 /api (请求 /api/orders/1 -> /orders/1)
            - AddRequestHeader=X-Request-From, Gateway # 添加 Header
            - AddResponseHeader=X-Response-From, Gateway # 添加响应 Header
            # - name: RequestRateLimiter # 限流过滤器 (需配合 Redis)
            #   args:
            #     redis-rate-limiter.replenishRate: 1 # 每秒补充1个令牌
            #     redis-rate-limiter.burstCapacity: 3 # 令牌桶容量

        # 路由 2: 路由到 product-service
        - id: product-service-route
          uri: lb://product-service # 使用 lb (loadbalancer) scheme,结合服务发现
          predicates:
            - Path=/api/products/**
            - Method=GET,POST # 仅匹配 GET 和 POST 请求
          filters:
            - StripPrefix=1
            - name: Hystrix # 断路器 (Hystrix 已维护,推荐 Resilience4j)
              args:
                name: fallbackCmd
                fallbackUri: forward:/fallback/product # 降级处理

        # 路由 3: 路由到外部服务
        - id: external-api-route
          uri: https://api.external.com
          predicates:
            - Host=**.myapp.com # 匹配 Host 头
            - Query=apiKey, [a-zA-Z0-9]+ # 匹配名为 apiKey 的参数,且值为字母数字

      # 全局 CORS 配置
      globalcors:
        cors-configurations:
          '[/**]': # 对所有路径生效
            allowedOrigins: "https://allowed-domain.com", "http://localhost:3000"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

  # 如果使用服务发现 (Nacos/Eureka)
  # cloud:
  #   nacos:
  #     discovery:
  #       server-addr: localhost:8848
  #   # 或 eureka:
  #   #   client:
  #   #     service-url:
  #   #       defaultZone: http://localhost:8761/eureka/

# Actuator 配置 (可选)
management:
  endpoints:
    web:
      exposure:
        include: health, info, gateway # 暴露 gateway 端点

步骤 3:配置基于服务发现的动态路由

利用注册中心(如 Nacos)自动发现服务,无需手动指定 uri

  1. 确保服务已注册order-service, product-service 等已成功注册到 Nacos 或 Eureka。

  2. 修改网关配置

    spring:
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true # 开启服务发现路由功能
              lower-case-service-id: true # 将服务名转为小写 (如 PRODUCT-SERVICE -> product-service)
          routes:
            # 方式一:显式定义,使用 lb://service-name
            - id: order-route
              uri: lb://ORDER-SERVICE # lb scheme + 服务名 (Nacos 注册名)
              predicates:
                - Path=/api/orders/**
              filters:
                - StripPrefix=1
    
            # 方式二:更简洁 - 使用 discovery.locator.enabled 后,会自动生成路由
            # 默认规则:/service-name/** -> service-name (无需手动定义路由!)
            # 例如:访问 /order-service/orders/1 会自动路由到 ORDER-SERVICE 的 /orders/1
            # 可通过 predicates 和 filters 覆盖或增强自动生成的路由。
    
  3. 访问

    • 直接访问 http://localhost:8080/order-service/orders/1 (利用自动生成路由)。
    • 或访问 http://localhost:8080/api/orders/1 (利用手动定义路由)。

步骤 4:编写降级 (Fallback) 处理

当服务不可用或断路器打开时,返回友好提示。

// FallbackController.java
@RestController
public class FallbackController {

    @GetMapping("/fallback/product")
    public Mono<String> productFallback() {
        return Mono.just("商品服务暂时不可用,请稍后再试。");
    }

    @GetMapping("/fallback/**")
    public Mono<String> defaultFallback() {
        return Mono.just("服务暂时不可用,请稍后再试。");
    }
}

步骤 5:使用 Java 代码配置路由 (可选)

// GatewayConfig.java
@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("code_route", r -> r.path("/api/users/**")
                .filters(f -> f.stripPrefix(1)
                            .addRequestHeader("X-Code-Route", "true"))
                .uri("http://user-service:8082")) // 或 lb://user-service
            .build();
    }
}

步骤 6:测试网关

  1. 启动 gateway-service
  2. 启动后端微服务(如 order-service on 8081)。
  3. 发送请求:
    curl http://localhost:8080/api/orders/123
    # 应该被路由到 http://localhost:8081/orders/123 (StripPrefix 生效)
    
  4. 查看日志,确认请求经过网关。

三、常见错误与解决方案

错误现象 原因分析 解决方案
404 Not Found 1. 请求路径未匹配任何路由的 predicates
2. 目标服务未启动
3. StripPrefix 配置错误
1. 检查 Path 断言
2. 确认后端服务可用
3. 调整 StripPrefix 参数
500 Internal Server Error 1. 目标服务返回 5xx
2. 网关配置错误 (如无效 URI)
3. 过滤器抛出异常
1. 检查后端服务日志
2. 检查 uri 配置
3. 检查自定义 Filter 逻辑
Connection Refused / Timeout 1. 目标服务地址/端口错误
2. 网络问题
3. 服务未注册 (lb://)
1. 检查 uri 或服务名拼写
2. telnet 测试
3. 确认服务已注册到注册中心
网关启动失败 1. 依赖冲突 (如同时引入 Web MVC 和 WebFlux)
2. 端口占用
3. 配置语法错误
1. 确保项目是 Reactive 的,排除 spring-boot-starter-web (MVC),使用 spring-boot-starter-webflux
2. 更改 server.port
3. 检查 YAML/JSON 格式
服务发现路由不生效 1. spring.cloud.gateway.discovery.locator.enabled=false
2. 服务名大小写不匹配
3. 未添加 Discovery Client 依赖
1. 确保 enabled: true
2. 设置 lower-case-service-id: true
3. 检查依赖

四、注意事项

  1. Reactive 编程模型
    • Gateway 基于 WebFlux,是非阻塞、响应式的。
    • 不要在 Gateway 中执行阻塞操作(如 Thread.sleep(), 同步数据库调用)。
    • 返回值通常是 Mono<T>Flux<T>
  2. 排除 MVC 依赖
    • 绝对不能在 Gateway 项目中引入 spring-boot-starter-web (基于 Servlet 的 MVC)。
    • 必须使用 spring-boot-starter-webfluxspring-boot-starter-web (Reactive)。
  3. lb Scheme
    • lb://service-name 依赖于 Spring Cloud LoadBalancer 进行客户端负载均衡。
  4. 过滤器执行顺序
    • GlobalFilter 执行在 GatewayFilter 之前。
    • 同类型过滤器可通过 Ordered 接口或 @Order 注解控制顺序。
  5. Actuator 端点
    • /actuator/gateway/routes:查看当前路由。
    • /actuator/gateway/globalfilters:查看全局过滤器。
    • /actuator/gateway/routefilters:查看路由过滤器。
  6. 线程模型
    • Netty (Reactor) 线程处理 I/O,应避免在这些线程上执行耗时任务。

五、使用技巧

5.1 自定义全局过滤器 (GlobalFilter)

实现 GlobalFilterOrdered

@Component
@Order(-1) // 优先级最高
public class AuthGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String token = request.getHeaders().getFirst("Authorization");

        if (token == null || !isValidToken(token)) {
            // 返回 401
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 继续执行链
        return chain.filter(exchange);
    }

    private boolean isValidToken(String token) {
        // 实现 token 验证逻辑
        return true;
    }
}

5.2 自定义断言工厂 (Predicate Factory)

@Component
public class CheckHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckHeaderRoutePredicateFactory.Config> {

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String headerValue = exchange.getRequest().getHeaders().getFirst(config.headerName);
            return headerValue != null && headerValue.equals(config.headerValue);
        };
    }

    public static class Config {
        private String headerName;
        private String headerValue;
        // getter/setter
    }
}

在配置中使用:

predicates:
  - name: CheckHeader
    args:
      headerName: X-Custom-Auth
      headerValue: secret

5.3 重试机制 (Retry)

filters:
  - name: Retry
    args:
      retries: 3
      statuses: BAD_GATEWAY, SERVICE_UNAVAILABLE
      methods: GET, POST
      backoff:
        firstBackoff: 100ms
        maxBackoff: 500ms
        factor: 2

5.4 限流 (Rate Limiting with Redis)

  1. 添加依赖

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

    spring:
      redis:
        host: localhost
        port: 6379
    
    # 在路由 filters 中
    filters:
      - name: RequestRateLimiter
        args:
          redis-rate-limiter.replenishRate: 10 # 每秒补充10个令牌
          redis-rate-limiter.burstCapacity: 20 # 令牌桶最大容量20
          # key-resolver: "#{@userKeyResolver}" # 自定义 Key 解析器 Bean 名
    
  3. 自定义 Key 解析器 (如按用户/IP):

    @Component
    public class UserKeyResolver implements KeyResolver {
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
            // 或从 Header/Token 获取用户ID
        }
    }
    

六、最佳实践

  1. 单一入口:所有外部请求都应通过网关。
  2. 安全前置:在网关层处理认证、鉴权、防重放、防刷。
  3. 统一日志:在网关记录访问日志(请求、响应、耗时、IP)。
  4. 监控与告警:集成 Actuator, Prometheus, Grafana 监控网关健康、流量、延迟。
  5. 灰度发布:利用 Predicate(如 Header、Cookie)将特定流量路由到新版本服务。
  6. 优雅降级:为关键服务配置降级策略。
  7. 配置管理:使用 Nacos/Config Server 管理网关路由配置,实现动态更新。
  8. 高可用部署:网关本身也需要集群部署,前面加负载均衡器(如 Nginx, SLB)。
  9. 文档化:明确网关的路由规则、过滤器作用。

七、性能优化

  1. JVM 调优
    • 合理设置堆内存 (-Xms, -Xmx)。
    • 选择合适的 GC 算法(如 G1GC)。
  2. Netty 线程池
    • 默认使用 Reactor 的 EventLoopGroup。
    • 通常无需调整,除非有特殊需求。
  3. 减少阻塞
    • 绝对避免filter 中执行 Thread.sleep() 或同步 I/O。
    • 如需调用外部服务,使用 WebClient (Reactive) 而非 RestTemplate
  4. 缓存
    • 对频繁访问且不常变的数据,可在网关层做简单缓存(注意一致性)。
  5. 连接池
    • 配置合理的 HTTP 客户端连接池(如 HttpClient 配置)。
  6. 压缩
    • 启用 GZIP 压缩 (server.compression.enabled=true)。
  7. 限流
    • 防止突发流量压垮后端服务。
  8. 监控驱动优化
    • 使用监控数据定位瓶颈(CPU、内存、GC、网络、响应时间)。

总结

Spring Cloud Gateway 是现代微服务架构中不可或缺的组件。

核心要点

  1. 响应式非阻塞:理解并适应 WebFlux 模型。
  2. 路由、断言、过滤器:掌握三大核心概念。
  3. 服务发现集成:利用 lb:// scheme 简化路由配置。
  4. 安全与治理:在网关层实现认证、限流、熔断、日志。
  5. 动态配置:结合 Nacos 等实现路由规则动态更新。

通过本指南,你已具备快速搭建和使用 Spring Cloud Gateway 的能力。务必注意避免阻塞操作排除 MVC 依赖这两个最常见的陷阱。