Gateway 路由转发与网关治理实战

Gateway是什么

Spring Cloud Gateway 是基于 Spring WebFlux(响应式编程)的微服务网关,替代了已停更的 Zuul 1.x。它是微服务流量的统一入口,负责路由转发、认证鉴权、跨域处理、限流熔断和协议转换。

一句话定位: Nginx 承担边缘网关(SSL/WAF/全局限流),Gateway 承担业务网关(路由/鉴权/灰度/细粒度限流),两层的架构是目前生产的主流方案。

为什么需要

问题 没有网关的后果 Gateway 怎么做
服务散落 前端需要知道每个微服务的地址,扩缩容改前端代码 统一入口 /order/** → order-server
鉴权分散 每个服务自己写一遍认证逻辑,重复且容易漏 全局过滤器统一校验 Token
跨域 前端调多个域名需要多处配置 CORS 网关层一次性配置
限流 每个服务单独接入限流组件 网关层统配 Sentinel 限流
灰度发布 难以控制流量按规则切分到新版本 自定义断言按 Header/参数分流

核心架构:请求流转全流程

graph LR C[客户端请求<br>/order/create] --> GW[Gateway<br>WebFlux 响应式] GW --> PRED{断言 Predicate<br>路由匹配?} PRED -- 否 --> 404[404 Not Found] PRED -- 是 --> FILT[过滤器链 Filter<br>GlobalFilter → GatewayFilter] FILT --> LB[负载均衡<br>lb://order-server] LB --> SVC[目标服务<br>order-service] subgraph 断言种类 P1[Path] P2[Method] P3[Header] P4[Query] P5[RemoteAddr] P6[自定义] end subgraph 过滤器作用 F1[鉴权 GlobalFilter] F2[路径重写 StripPrefix] F3[限流 Sentinel] F4[跨域 CORS] F5[灰度路由] end style GW fill:#c8e6c9 style FILT fill:#e1f5fe style LB fill:#fff9c4

三核心组件:

组件 解释 例子
Route(路由) 网关的基本单元,包含 id + uri + predicates + filters /order/** 转到 lb://order-server
Predicate(断言) 匹配条件,满足才转发 Path=/order/** + Method=GET
Filter(过滤器) 对请求/响应做前置或后置处理 鉴权、路径剥离、加请求头

新人上手指南

第一步:添加依赖

<!-- Gateway 核心(基于 WebFlux,不要加 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>
<!-- Sentinel 网关限流(可选,按需) -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>

⚠️ 重要: Gateway 基于 WebFlux,不能同时引入 spring-boot-starter-web(即 Spring MVC),否则启动冲突。如果你从旧项目迁移,务必排除 Spring MVC 依赖。

第二步:基础路由配置

# application.yml
spring:
  application:
    name: gateway-server
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

    gateway:
      routes:
        # 路由一:动态路由(下游服务注册在 Nacos)
        - id: order-service-route
          uri: lb://order-server          # lb=从 Nacos 负载均衡
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1               # /order/xx → /xx

        # 路由二:静态路由(指向固定地址)
        - id: baidu-route
          uri: http://www.baidu.com
          predicates:
            - Path=/baidu/**
          filters:
            - RewritePath=/baidu/(?<seg>.*), /$\{seg}

      # 自动从 Nacos 发现服务(路径 = 服务名小写)
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

server:
  port: 8080

第三步:启动类

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

启动后访问 http://localhost:8080/order/create?userId=1 → Gateway 将路径剥离为 /create → 负载均衡转发到 order-server 的 /create 接口。

核心功能实战

1. 全局鉴权过滤器

统一校验 Token,不合规直接返回 401,不转发到下游。

@Configuration
public class GlobalAuthConfig {

    /** 白名单路径:不用 Token */
    private static final List<String> WHITELIST = Arrays.asList(
        "/user/login", "/user/register"
    );

    @Bean
    @Order(-100)           // 数值越小优先级越高,在默认过滤器之前执行
    public GlobalFilter authFilter() {
        return (exchange, chain) -> {
            String path = exchange.getRequest().getURI().getPath();

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

            // 校验 Token
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            if (token == null || token.isBlank()) {
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();  // 响应式写法
            }

            // 可选:校验 Token 合法性(调认证服务 / Redis 校验)
            // 将解析后的用户信息放入请求头,透传给下游
            exchange = exchange.mutate()
                .request(r -> r.header("X-User-Id", parseUserId(token)))
                .build();

            return chain.filter(exchange);
        };
    }
}

和 Spring MVC 拦截器的区别: Gateway 是响应式架构,返回 Mono<Void>,不是 boolean。用 exchange.getResponse().setComplete() 终止请求,而不是 return false

2. 跨域配置(CORS)

前后端分离项目中前端跨域调用的标准解决方案——在网关层统一配置,下游服务不需要任何 CORS 处理。

@Configuration
public class CorsGlobalConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*");        // 允许所有源(生产环境改成具体域名)
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        config.setAllowCredentials(true);           // 允许携带 Cookie
        config.setMaxAge(3600L);                    // 预检缓存 1 小时

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

注意: addAllowedOriginPattern("*") 配合 allowCredentials(true) 在 Spring Cloud Gateway 中必须用 Pattern 而非 Origin("*"),因为后者在与 Credentials 同时使用时会被浏览器拒绝。

3. 整合 Sentinel 网关限流

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
      filter:
        enabled: true
    gateway:
      sentinel:
        enabled: true          # 开启 Gateway 的 Sentinel 整合

Sentinel 控制台 → 网关路由限流 → 新增规则,选择路由 ID(如 order-service-route)并设置 QPS 阈值。超限请求返回 Blocked by Sentinel: FlowException,可通过 BlockRequestHandler 自定义返回格式。

4. 灰度发布实现

灰度发布(金丝雀发布):让一小部分用户先访问新版本服务,验证无误后全量切换。Gateway 通过自定义断言实现——按请求 Header 分流。

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

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            String tag = exchange.getRequest().getHeaders().getFirst("X-Gray-Tag");
            return config.getTag().equals(tag);
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("tag");
    }

    public static class Config {
        private String tag;
        public String getTag() { return tag; }
        public void setTag(String tag) { this.tag = tag; }
    }
}
# 灰度路由配置
spring:
  cloud:
    gateway:
      routes:
        # 灰度版本:请求头 X-Gray-Tag=beta 时走新服务
        - id: order-service-gray
          uri: lb://order-server-beta
          predicates:
            - Path=/order/**
            - Gray=beta
          filters:
            - StripPrefix=1

        # 稳定版本:其余请求走老服务
        - id: order-service-stable
          uri: lb://order-server
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1

测试:X-Gray-Tag: beta 请求头 → 走到 order-server-beta;不带 → 走到 order-server。验证灰度版本无问题后,调整 Nacos 权重或切换 DNS 做全量发布。

5. 动态路由(基于 Nacos 配置中心)

静态路由写在 application.yml 中,改路由要重启网关。生产环境需要动态路由——在 Nacos 中修改配置,Gateway 实时生效。

# bootstrap.yml(必须用 bootstrap,优先级高于 application)
spring:
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yaml
  application:
    name: gateway-server

在 Nacos 控制台创建配置 DataId = gateway-server.yaml,填入路由规则:

spring:
  cloud:
    gateway:
      routes:
        - id: order-service-route
          uri: lb://order-server
          predicates:
            - Path=/order/**
          filters:
            - StripPrefix=1
        - id: new-service-route     # 新增路由无需重启
          uri: lb://new-service
          predicates:
            - Path=/new/**

修改 Nacos 中的配置 → 保存 → 几秒后网关自动加载新路由。配置文件路径由 spring.cloud.nacos.config.server-addr 和 DataId 决定。

常见问题

1. Gateway 启动报错 "Failed to configure a data source"

最常见的原因是引入了 spring-boot-starter-web。Gateway 基于 WebFlux(反应式),与 Spring MVC(阻塞式)冲突。检查:

  • pom.xml 中没有 spring-boot-starter-webspring-boot-starter-webflux 混用
  • 如果有,排除:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

2. 路由 404 / 路由不生效

graph TD A[路由 404] --> B{断言匹配?} B -- 否 --> C[检查 Path 模式<br>/order/** 和 /order 不同] B -- 是 --> D{服务名正确?} D -- 否 --> E[lb://order-server<br>和 Nacos 注册名一致] D -- 是 --> F{发现路由开启?} F -- 否 --> G[discovery.locator.enabled=true] F -- 是 --> H[检查目标服务是否已注册]

3. 跨域配置不生效

  • 检查是否同时有多个 CorsWebFilter Bean(Gateway 默认带一个,自定义会覆盖)
  • 检查 AllowCredentials(true) 是否和 AllowedOrigin("*") 冲突——必须用 addAllowedOriginPattern("*") 替代
  • 注意:跨域配置应只放在网关层,下游服务不要自己配 CORS,否则重复 Header 导致浏览器报错

4. 动态路由更新不及时

  • 确认 bootstrap.yml 中 Nacos Config 配置正确(不是 application.yml)
  • 确认 spring.cloud.nacos.config.file-extension 和 DataId 后缀一致
  • Nacos 配置需要是 DataId = {spring.application.name}.{file-extension} 的完整格式

面试题

Q1:Gateway 和 Zuul 的核心区别是什么?为什么选 Gateway?

参考答案:

维度 Spring Cloud Gateway Zuul 1.x(已停更)
编程模型 WebFlux 响应式(Netty + Reactor) Servlet 阻塞式(Tomcat)
线程模型 事件驱动,单线程处理大量连接 每个请求一个线程,连接多时线程数飙升
性能 高(无阻塞 IO) 低(阻塞 IO,连接数上去后恶化)
长连接 原生支持 WebSocket 需要额外处理
集成 Nacos 原生支持 需额外适配

两代网关的选择本质是编程模型的选择。Gateway 的响应式模型让它能在更少的线程下承载更多的并发连接,适合网关这种 IO 密集型场景。Zuul 1.x 在 Spring Cloud 体系中已被标记为停更维护状态,新项目不应该再使用。Netflix 推出的 Zuul 2.x 虽然也是响应式,但 Spring 官方选择了自己的 Gateway。

另外需要注意:Gateway 基于 WebFlux,如果项目强依赖 Spring MVC 的某些特性(如 @ControllerAdvice 的异常处理、spring-session 的 Servlet 集成),迁移到 Gateway 时需要调整实现方式。

Q2:全局过滤器和路由级过滤器的执行顺序是怎样的?同类型过滤器按什么排序?

参考答案:

一个请求到达 Gateway 后经过的过滤器链路顺序:

  1. 所有全局过滤器(GlobalFilter)@Order 值升序排列 进入匹配的路由
  2. 该路由的所有路由级过滤器(GatewayFilter) 按配置顺序执行(从上到下) 转发到下游
  3. 下游返回响应后,路由级过滤器的后置逻辑按逆序执行 全局过滤器的后置逻辑按逆序执行

排序规则详解:

  • 全局过滤器通过 @Order 或实现 Ordered 接口排序,数值越小优先级越高,-100 会在 0 之前执行。
  • 路由级过滤器按 yaml 中配置的书写顺序执行,没有独立的 Order 控制。
  • 自定义的全局过滤器如果需要在内置过滤器之前/之后执行,通过调整 @Order 值来控制。内置过滤器的 Order 值通常是 0 或更大的正数。

常见应用:

  • @Order(-100) 的鉴权过滤器先执行,未登录的直接拦截,不走后续链路
  • @Order(1000) 的日志过滤器后执行,记录整个请求的处理结果

Q3:网关层限流和服务层限流有什么区别?什么场景下该用网关层限流?

参考答案:

对比维度 网关层限流 服务层限流
保护范围 所有请求在进入内部前就拦截 只保护当前服务自身
响应速度 在网关直接 reject,不消耗内部资源 请求已经穿透到服务,占用了连接
粒度 粗粒度(按路由、API、IP) 细粒度(按方法、用户、来源)
依赖 不依赖下游服务,网关独立运行即可 依赖服务自身接入限流组件
统一管理 一处配置,所有服务生效 每个服务各自配置

网关层限流最适合的场景:

  1. 全局限流: 对某个 API 或某个来源 IP 做整体限流,不管它打到哪个服务。例如对未认证的匿名请求做全局 QPS 限制。
  2. 入口保护: 防止突发流量冲垮整个集群。在网关层限制后,下游服务永远不会看到超过阈值的流量,这是最安全的一层防线。
  3. 廉价拒绝: 网关 reject 的开销远低于内部服务 reject 的开销(不需要走 RPC、数据库、业务逻辑)。

但网关层限流无法替代服务层限流——网关层只能看到经过它的流量,无法感知到服务内部的消息队列、定时任务、异步调用等流量。实践中通常两层都做:网关层做入口粗粒度限流,服务层做业务细粒度限流。


延伸阅读: Spring Cloud Gateway 官方文档 https://docs.spring.io/spring-cloud-gateway/reference/ ,重点是 Filter 的响应式编程模式和自定义 GatewayFilter 的开发方法。

posted @ 2026-07-27 21:29  念笙  阅读(28)  评论(0)    收藏  举报