基于:Spring Cloud Gateway 5.0.x(最新稳定版 5.0.1,对应 Spring Boot 4.0.x / Spring Cloud 2025.1 Oakwood);4.2.x/4.3.x 用法兼容(Spring Boot 3.4/3.5)
Spring Cloud Gateway 的定位与核心概念(Route / Predicate / Filter)、响应式架构设计思想、请求处理链路源码级剖析、完整使用实战(配置式/编程式/动态路由/限流熔断),以及与 Zuul / Nginx 的选型对比。
目录
- 核心理论:网关是什么
- [1.1 从痛点到定位](#1.1 从痛点到定位)
- [1.2 三大核心概念:Route / Predicate / Filter](#1.2 三大核心概念:Route / Predicate / Filter)
- [1.3 与 Zuul / Nginx 对比选型](#1.3 与 Zuul / Nginx 对比选型)
- [1.4 版本体系与兼容矩阵](#1.4 版本体系与兼容矩阵)
- 设计思想与核心机制
- [2.1 基于 WebFlux 的响应式架构](#2.1 基于 WebFlux 的响应式架构)
- [2.2 请求处理链路总览](#2.2 请求处理链路总览)
- [2.3 路由定位机制](#2.3 路由定位机制)
- [2.4 过滤器链执行机制](#2.4 过滤器链执行机制)
- [2.5 内置断言工厂与过滤器工厂](#2.5 内置断言工厂与过滤器工厂)
- [2.6 负载均衡与动态路由](#2.6 负载均衡与动态路由)
- [2.7 限流机制:Redis 令牌桶](#2.7 限流机制:Redis 令牌桶)
- [2.8 熔断降级集成(Resilience4j)](#2.8 熔断降级集成(Resilience4j))
- [2.9 Gateway 5.0 新特性](#2.9 Gateway 5.0 新特性)
- 使用实战
- [3.1 快速接入:第一个路由](#3.1 快速接入:第一个路由)
- [3.2 配置式路由详解](#3.2 配置式路由详解)
- [3.3 Java DSL 编程式路由](#3.3 Java DSL 编程式路由)
- [3.4 动态路由(Nacos 配置中心)](#3.4 动态路由(Nacos 配置中心))
- [3.5 自定义全局过滤器:统一鉴权](#3.5 自定义全局过滤器:统一鉴权)
- [3.6 限流实战(Redis RateLimiter)](#3.6 限流实战(Redis RateLimiter))
- [3.7 熔断与重试配置](#3.7 熔断与重试配置)
- 主线流程剖析
- 关键类关系图
- 扩展点与常见问题
- [6.1 扩展点(SPI 一览)](#6.1 扩展点(SPI 一览))
- [6.2 常见问题 FAQ](#6.2 常见问题 FAQ)
- [6.3 参考资料](#6.3 参考资料)
1. 核心理论:网关是什么
Spring Cloud Gateway 是 Spring 官方推出的微服务 API 网关 ,基于 Spring WebFlux(Project Reactor + Netty) 构建,采用非阻塞响应式 模型,是 Spring Cloud 生态中替代 Zuul 的下一代网关,也是当前微服务架构中东西向流量的事实标准入口。
1.1 从痛点到定位
| 微服务痛点 | Gateway 的解法 | 对应能力 |
|---|---|---|
| 前端/外部调用散落在几十个服务地址 | 统一入口,按路由规则转发到目标服务 | 路由转发 |
| 每个服务都要自己写鉴权、日志、跨域 | 网关层统一处理,服务端专注业务 | 过滤器链 |
| 服务地址变化影响调用方 | 路由目标用服务名(lb://)+ 负载均衡 |
服务发现集成 |
| 恶意流量打垮单点服务 | 网关层限流、熔断、降级 | 流量防护 |
| 上线新版本需要灰度 | 按 Header/权重等断言分流 | 灰度发布 |
| 传统网关(Zuul1)高并发下线程堆积 | Netty 事件循环 + 非阻塞转发,少量线程扛大量连接 | 高性能 |
一句话概括 :Gateway = 统一入口 + 路由转发 + 过滤器链,把横切关注点(鉴权、限流、日志、灰度)从每个微服务收口到网关一个地方。
1.2 三大核心概念:Route / Predicate / Filter
#mermaid-svg-HX9Vu4IdXLpFwcC1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .error-icon{fill:#552222;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .marker.cross{stroke:#333333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 p{margin:0;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster-label text{fill:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster-label span{color:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster-label span p{background-color:transparent;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .label text,#mermaid-svg-HX9Vu4IdXLpFwcC1 span{fill:#333;color:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .node rect,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node circle,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node ellipse,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node polygon,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .rough-node .label text,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node .label text,#mermaid-svg-HX9Vu4IdXLpFwcC1 .image-shape .label,#mermaid-svg-HX9Vu4IdXLpFwcC1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .rough-node .label,#mermaid-svg-HX9Vu4IdXLpFwcC1 .node .label,#mermaid-svg-HX9Vu4IdXLpFwcC1 .image-shape .label,#mermaid-svg-HX9Vu4IdXLpFwcC1 .icon-shape .label{text-align:center;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .node.clickable{cursor:pointer;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .arrowheadPath{fill:#333333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HX9Vu4IdXLpFwcC1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HX9Vu4IdXLpFwcC1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster text{fill:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .cluster span{color:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HX9Vu4IdXLpFwcC1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .icon-shape,#mermaid-svg-HX9Vu4IdXLpFwcC1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .icon-shape p,#mermaid-svg-HX9Vu4IdXLpFwcC1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .icon-shape .label rect,#mermaid-svg-HX9Vu4IdXLpFwcC1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HX9Vu4IdXLpFwcC1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HX9Vu4IdXLpFwcC1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HX9Vu4IdXLpFwcC1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 匹配成功
请求
RoutePredicateHandlerMapping
遍历路由,用 Predicate 匹配
Route 路由
id + uri + predicates + filters
过滤器链
前置 filters → 转发 → 后置 filters
目标服务
lb://service / http://ip:port
| 概念 | 作用 | 示例 |
|---|---|---|
| Route(路由) | 网关的基础构件,由 ID、目标 URI、一组断言和一组过滤器组成 | id: user-route, uri: lb://user-service |
| Predicate(断言) | 匹配规则(Java 8 Predicate<ServerWebExchange>),全部匹配才走该路由 |
Path=/user/**、Header=X-Token |
| Filter(过滤器) | 对请求/响应做处理,分全局过滤器 (GlobalFilter)与路由过滤器(GatewayFilter) | StripPrefix=1、AddRequestHeader |
关键认知 :理解 Gateway 只需要记住一句话------请求进来 → 按 Predicate 找到 Route → 依次过 Filter 链 → 转发到 Route 指定的 URI。所有配置(yml/DSL)最终都转化为这三个对象。
1.3 与 Zuul / Nginx 对比选型
| 维度 | Spring Cloud Gateway | Zuul 1.x | Zuul 2.x | Nginx |
|---|---|---|---|---|
| 技术底座 | WebFlux + Reactor + Netty | Servlet(阻塞) | Netty(异步) | C + epoll |
| 线程模型 | 非阻塞事件循环 | 每请求一线程(线程池) | 事件驱动 | 事件驱动 |
| 动态路由 | ✅ 支持(配置中心+事件刷新) | ❌ 需重启 | ⚠️ 有限 | 需 reload/openresty 脚本 |
| 编程扩展 | ✅ GlobalFilter/断言工厂,Java 生态 | Filter 有限 | Filter | Lua(OpenResty) |
| 与微服务生态集成 | ⭐ Spring Cloud 原生(Nacos/Eureka 服务发现) | Ribbon 时代产物 | 一般 | 需自建服务发现 |
| 性能(纯转发) | 高(略低于 Nginx) | 低 | 高 | ⭐ 最高 |
| 维护状态 | ⭐ 官方活跃 | 停止维护 | 停止维护 | 活跃 |
关键认知 :南北向流量 (公网入口)通常用 Nginx/K8s Ingress,东西向流量(微服务间/内部 API)用 Gateway------两者可叠加(Nginx 在最外层做负载与 SSL,Gateway 做业务路由与治理)。若团队强 Java 技术栈且需要动态路由、灰度、统一鉴权,则 Gateway 一站式。
1.4 版本体系与兼容矩阵
| Spring Cloud 发布列车 | Spring Boot | Gateway 版本 | 说明 |
|---|---|---|---|
| 2025.1(Oakwood) | 4.0.x / 4.1.x | 5.0.x(最新稳定 5.0.1) | 基于 Spring Framework 7,当前主力线 |
| 2025.0(Northfields) | 3.5.x | 4.3.x | 稳定线 |
| 2024.0(Moorgate) | 3.4.x | 4.2.x | 稳定线 |
| 2023.0(Leyton) | 3.2.x / 3.3.x | 4.1.x | 存量较多 |
⚠️ 版本强校验:Spring Cloud 内置兼容性验证器,Boot 与 Cloud 版本不匹配会直接拒绝启动 (报
Spring Boot [x.x.x] is not compatible with this Spring Cloud release train)。升级顺序建议:JDK → Spring Boot → Spring Cloud → Spring Cloud Alibaba。
2. 设计思想与核心机制 ★★★
2.1 基于 WebFlux 的响应式架构
这是 Gateway 与 Zuul 1.x 的本质区别:
| 维度 | Gateway(WebFlux) | Zuul 1.x(Servlet) |
|---|---|---|
| 服务器 | Netty(NIO 事件循环) | Tomcat(BIO,每请求一线程) |
| 线程使用 | 少量 EventLoop 线程处理海量连接 | 200 线程池 → 200 并发上限 |
| 阻塞点 | 全链路非阻塞(转发用 Netty Client) | 请求转发阻塞线程 |
| 高并发表现 | 线程不随连接数增长 | 线程堆积、OOM 风险 |
- 底层基于 Spring WebFlux + Reactor :请求以
ServerWebExchange封装,链路全程Mono/Flux响应式流; - 转发由 Reactor Netty 的 HttpClient 完成,请求进来 → 转发出去 → 响应写回,全程无阻塞 I/O;
- ⚠️ 约束 :不能与
spring-boot-starter-web(Spring MVC)共存,否则启动报Spring MVC found on classpath;引入 Redis 限流时也要用响应式客户端(spring-boot-starter-data-redis-reactive)。
2.2 请求处理链路总览
#mermaid-svg-2IgfNtoPrUGWDxH2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2IgfNtoPrUGWDxH2 .error-icon{fill:#552222;}#mermaid-svg-2IgfNtoPrUGWDxH2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2IgfNtoPrUGWDxH2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .marker.cross{stroke:#333333;}#mermaid-svg-2IgfNtoPrUGWDxH2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2IgfNtoPrUGWDxH2 p{margin:0;}#mermaid-svg-2IgfNtoPrUGWDxH2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster-label text{fill:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster-label span{color:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster-label span p{background-color:transparent;}#mermaid-svg-2IgfNtoPrUGWDxH2 .label text,#mermaid-svg-2IgfNtoPrUGWDxH2 span{fill:#333;color:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .node rect,#mermaid-svg-2IgfNtoPrUGWDxH2 .node circle,#mermaid-svg-2IgfNtoPrUGWDxH2 .node ellipse,#mermaid-svg-2IgfNtoPrUGWDxH2 .node polygon,#mermaid-svg-2IgfNtoPrUGWDxH2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .rough-node .label text,#mermaid-svg-2IgfNtoPrUGWDxH2 .node .label text,#mermaid-svg-2IgfNtoPrUGWDxH2 .image-shape .label,#mermaid-svg-2IgfNtoPrUGWDxH2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-2IgfNtoPrUGWDxH2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .rough-node .label,#mermaid-svg-2IgfNtoPrUGWDxH2 .node .label,#mermaid-svg-2IgfNtoPrUGWDxH2 .image-shape .label,#mermaid-svg-2IgfNtoPrUGWDxH2 .icon-shape .label{text-align:center;}#mermaid-svg-2IgfNtoPrUGWDxH2 .node.clickable{cursor:pointer;}#mermaid-svg-2IgfNtoPrUGWDxH2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .arrowheadPath{fill:#333333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2IgfNtoPrUGWDxH2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2IgfNtoPrUGWDxH2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2IgfNtoPrUGWDxH2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster text{fill:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 .cluster span{color:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2IgfNtoPrUGWDxH2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2IgfNtoPrUGWDxH2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-2IgfNtoPrUGWDxH2 .icon-shape,#mermaid-svg-2IgfNtoPrUGWDxH2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2IgfNtoPrUGWDxH2 .icon-shape p,#mermaid-svg-2IgfNtoPrUGWDxH2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2IgfNtoPrUGWDxH2 .icon-shape .label rect,#mermaid-svg-2IgfNtoPrUGWDxH2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2IgfNtoPrUGWDxH2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2IgfNtoPrUGWDxH2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2IgfNtoPrUGWDxH2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 响应逆序经过
后置 post filter
写回响应
客户端请求
HTTP
HttpWebHandlerAdapter
(WebFlux 入口)
DispatcherHandler
(分派)
RoutePredicateHandlerMapping
(路由定位)
FilteringWebHandler
(组装全局过滤器链)
GlobalFilter 链
(按 Order 排序执行)
NettyRoutingFilter
(Reactor Netty 转发)
目标服务
2.3 路由定位机制
- 核心类
RoutePredicateHandlerMapping:启动时从RouteLocator拿到全部路由,请求到达后按路由定义顺序遍历 ,依次用路由的Predicate组合(and逻辑)匹配ServerWebExchange,第一个全部匹配的路由生效; - 匹配失败返回 404;所以路由顺序很重要(更具体的路由放前面);
- 路由来源(
RouteLocator实现):RouteDefinitionRouteLocator:读 yml 配置/GatewayProperties的路由定义;DiscoveryClientRouteLocator:结合服务发现(Nacos/Eureka),lb://自动展开为实例地址;- 多个
RouteLocator由CompositeRouteLocator合并,外层套CachingRouteLocator缓存。
2.4 过滤器链执行机制
- GatewayFilter(路由级) :只作用于其所属路由,由
GatewayFilterAdapter包装成 GlobalFilter 后统一执行; - GlobalFilter(全局级) :作用于所有请求,按
Ordered接口的 order 值从小到大排列执行; - 前置与后置 :每个过滤器在
chain.filter(exchange)之前 的逻辑是前置(pre),之后 的是后置(post);响应返回时后置逻辑按逆序执行(类似责任链 + 栈); - 核心转发过滤器:
NettyRoutingFilter(http/https 转发)、ForwardRoutingFilter(forward:内部转发)等。
关键认知 :Filter 链 = 排序好的责任链。前置逻辑顺序执行、后置逻辑逆序执行,理解这一点才能正确写日志、计时、改写响应的自定义过滤器。
2.5 内置断言工厂与过滤器工厂
常用断言工厂(Predicate Factory):
| 断言 | 作用 | 示例 |
|---|---|---|
Path |
按路径匹配(Ant 风格) | Path=/user/** |
Query |
按查询参数匹配 | Query=version, v\d+ |
Method |
按 HTTP 方法匹配 | Method=GET,POST |
Header |
按请求头匹配 | Header=X-Request-Id, \d+ |
Cookie |
按 Cookie 匹配 | Cookie=sessionId, .+ |
Host |
按 Host 匹配 | Host=**.example.com |
RemoteAddr |
按来源 IP 段匹配 | RemoteAddr=192.168.1.0/24 |
After/Before/Between |
按时间窗口匹配(定时发布) | After=2026-08-25T00:00:00+08:00[Asia/Shanghai] |
Weight |
按权重分流(灰度,配合 group) | Weight=groupA, 80 |
常用过滤器工厂(GatewayFilter Factory):
| 过滤器 | 作用 |
|---|---|
StripPrefix=1 |
转发前剥掉 1 段路径(/user/a → /a) |
PrefixPath=/api |
转发前加前缀 |
SetPath / RewritePath |
重写路径(RewritePath 支持正则捕获组) |
AddRequestHeader / AddRequestParameter |
加请求头/参数(如透传用户信息) |
AddResponseHeader / RemoveRequestHeader |
改响应头/删请求头 |
RequestSize |
限制请求体大小 |
Retry |
转发失败重试 |
RequestRateLimiter |
限流(需 Redis) |
CircuitBreaker |
熔断(需 Resilience4j) |
2.6 负载均衡与动态路由
uri: lb://service-name触发负载均衡:由spring-cloud-starter-loadbalancer的ReactiveLoadBalancer从服务发现中心取实例并选择(轮询等策略);- ⚠️ 自 Spring Cloud 2020 起 Ribbon 已移除 ,必须用 loadbalancer starter,否则
lb://不可用; - 动态路由 原理:修改路由配置后发布
RefreshRoutesEvent,CachingRouteLocator监听事件清空缓存重新构建路由,无需重启; - 实践中常配合 Nacos:Gateway 接入 Nacos 配置中心,路由 yml 放配置中心,改动后触发刷新(详见 3.4)。
2.7 限流机制:Redis 令牌桶
RequestRateLimiter过滤器基于 Redis + Lua 脚本实现令牌桶算法(原子操作);- 参数:
redis-rate-limiter.replenishRate(每秒补充令牌数)、redis-rate-limiter.burstCapacity(桶容量,允许的突发流量)、requestedTokens(每次消耗令牌数); - 限流维度 由
KeyResolverBean 决定:可按 IP、用户 ID、接口路径等取 key,每个 key 独立一个桶; - 超限默认返回 HTTP 429 ,可通过
denyEmptyKey/自定义处理器定制返回。
2.8 熔断降级集成(Resilience4j)
CircuitBreaker过滤器 +spring-cloud-starter-circuitbreaker-reactor-resilience4j:下游超时/异常率过高时熔断,配合fallbackUri: forward:/fallback走降级接口;- 断路器状态(CLOSED/OPEN/HALF_OPEN)由 Resilience4j 管理,熔断期间直接短路返回降级,保护下游。
2.9 Gateway 5.0 新特性
| 特性 | 说明 |
|---|---|
| 异步响应支持 | 支持异步响应处理(如 SSE/流式场景的增强) |
| WebSocket 路由 | 原生 WebSocket 路由与转发支持 |
| Proxy Exchange 变体 | 除完整网关 Server 变体外,提供轻量 Proxy Exchange,可直接用于基于注解的 WebFlux/MVC 应用做代理 |
| 基于 Spring Framework 7 | 与 Spring Boot 4.0 时代对齐 |
3. 使用实战 ★★★
3.1 快速接入:第一个路由
xml
<!-- pom.xml(Boot 3.4+/4.0+ 示例,版本随 1.4 兼容矩阵选择) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- 服务发现 + 负载均衡(lb:// 必需) -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yaml
# application.yml
server:
port: 8080 # 网关默认 8080
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes:
- id: user-service # 路由唯一 ID
uri: lb://user-service # lb:// 服务名,走负载均衡
predicates:
- Path=/user/** # 匹配 /user/ 开头的请求
filters:
- StripPrefix=1 # 转发前去掉第一段 /user
启动后访问 http://localhost:8080/user/list → 转发到 user-service 的 /list。
3.2 配置式路由详解
yaml
spring:
cloud:
gateway:
routes:
# 1) 展开写法:多个断言是 AND 关系
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
- Method=GET,POST
- Header=X-Tenant, \d+ # 带合法租户头才放行
filters:
- AddRequestHeader=X-Gateway, api-gateway # 透传网关标识
- Retry=3 # 失败重试 3 次
# 2) 短路写法:逗号分隔同一断言的多值
- id: demo-route
uri: http://httpbin.org:80
predicates:
- Path=/api/**,/demo/** # 短路:两个路径都匹配
filters:
- PrefixPath=/anything
# 3) 限流(Redis 令牌桶)
- id: rate-limit-route
uri: lb://demo-service
predicates:
- Path=/api/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒补充 10 个令牌
redis-rate-limiter.burstCapacity: 20 # 桶容量 20(允许突发)
key-resolver: "#{@ipKeyResolver}" # 按 IP 限流
globalcors: # 全局跨域(收口到网关)
add-to-simple-url-handler-mapping: true
cors-configurations:
'[/**]':
allowedOrigins: "https://example.com"
allowedMethods: "*"
allowedHeaders: "*"
3.3 Java DSL 编程式路由
java
@Configuration
public class GatewayRoutes {
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder) {
return builder.routes()
// 编程式路由:按 Host + Path 匹配
.route("user-route", r -> r
.host("*.user.example.com")
.and().path("/user/**")
.filters(f -> f.stripPrefix(1)
.addRequestHeader("X-Gateway", "dsl"))
.uri("lb://user-service"))
// 按时间窗口发布
.route("activity-route", r -> r
.after(ZonedDateTime.parse("2026-09-01T00:00:00+08:00[Asia/Shanghai]"))
.uri("lb://activity-service"))
.build();
}
}
3.4 动态路由(Nacos 配置中心)
xml
<!-- 接入 Nacos 配置中心,路由 yml 上云 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
yaml
# bootstrap.yml(或 spring.config.import 新写法)
spring:
application:
name: api-gateway
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
shared-configs:
- data-id: gateway-routes.yaml # 路由配置单独放一个 dataId
group: DEFAULT_GROUP
refresh: true # ⭐ 开启刷新
java
// 监听配置变更,重建路由(RefreshRoutesEvent 驱动)
@Component
public class DynamicRouteListener {
@EventListener(RefreshRoutesEvent.class)
public void onRefresh(RefreshRoutesEvent event) {
// CachingRouteLocator 收到事件后自动重建路由,这里可追加自定义逻辑(如打日志)
log.info("routes refreshed: {}", event.getSource());
}
}
关键认知 :改 Nacos 里的
gateway-routes.yaml→ 网关收到配置刷新 → 发布RefreshRoutesEvent→ 路由热生效,全程零重启。这是 Gateway 区别于 Nginx 静态配置的核心竞争力。
3.5 自定义全局过滤器:统一鉴权
java
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getHeaders().getFirst("X-Token");
// 前置逻辑:鉴权失败直接短路,不进入后续链
if (token == null || !token.startsWith("Bearer ")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete(); // 不调用 chain.filter,请求终止
}
// 通过:把用户信息透传给下游服务
ServerHttpRequest mutated = request.mutate()
.header("X-User-Id", parseUserId(token))
.build();
return chain.filter(exchange.mutate().request(mutated).build());
}
@Override
public int getOrder() {
return -100; // 越小越先执行,保证鉴权在最前
}
}
3.6 限流实战(Redis RateLimiter)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId> <!-- ⚠️ 必须响应式客户端 -->
</dependency>
java
@Configuration
public class RateLimitConfig {
// 按客户端 IP 限流(也可按 userId/接口维度)
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
.getAddress().getHostAddress());
}
}
⚠️ 踩坑提示:
key-resolver: "#{@ipKeyResolver}"中的 Bean 名必须与@Bean方法名一致;限流不生效先查 Redis 连接与 Bean 命名。超限返回 429,可自定义RateLimiter响应处理器返回业务友好提示。
3.7 熔断与重试配置
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>
yaml
spring:
cloud:
gateway:
routes:
- id: payment-service
uri: lb://payment-service
predicates:
- Path=/pay/**
filters:
- name: CircuitBreaker
args:
name: paymentCB # 断路器实例名
fallbackUri: forward:/fallback # 熔断后转内部降级接口
java
// 网关自身提供降级接口
@RestController
public class FallbackController {
@GetMapping("/fallback")
public Mono<String> fallback() {
return Mono.just("{\"code\": 503, \"msg\": \"服务暂时不可用\"}");
}
}
4. 主线流程剖析 ★★★
目标服务 Nacos 服务发现 Gateway(Netty) 客户端 目标服务 Nacos 服务发现 Gateway(Netty) 客户端 #mermaid-svg-cYSoen9Yy8FSjEtM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cYSoen9Yy8FSjEtM .error-icon{fill:#552222;}#mermaid-svg-cYSoen9Yy8FSjEtM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cYSoen9Yy8FSjEtM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cYSoen9Yy8FSjEtM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cYSoen9Yy8FSjEtM .marker.cross{stroke:#333333;}#mermaid-svg-cYSoen9Yy8FSjEtM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cYSoen9Yy8FSjEtM p{margin:0;}#mermaid-svg-cYSoen9Yy8FSjEtM .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cYSoen9Yy8FSjEtM text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cYSoen9Yy8FSjEtM .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-cYSoen9Yy8FSjEtM .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-cYSoen9Yy8FSjEtM #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-cYSoen9Yy8FSjEtM .sequenceNumber{fill:white;}#mermaid-svg-cYSoen9Yy8FSjEtM #sequencenumber{fill:#333;}#mermaid-svg-cYSoen9Yy8FSjEtM #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-cYSoen9Yy8FSjEtM .messageText{fill:#333;stroke:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cYSoen9Yy8FSjEtM .labelText,#mermaid-svg-cYSoen9Yy8FSjEtM .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .loopText,#mermaid-svg-cYSoen9Yy8FSjEtM .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-cYSoen9Yy8FSjEtM .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cYSoen9Yy8FSjEtM .noteText,#mermaid-svg-cYSoen9Yy8FSjEtM .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-cYSoen9Yy8FSjEtM .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cYSoen9Yy8FSjEtM .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cYSoen9Yy8FSjEtM .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-cYSoen9Yy8FSjEtM .actorPopupMenu{position:absolute;}#mermaid-svg-cYSoen9Yy8FSjEtM .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-cYSoen9Yy8FSjEtM .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-cYSoen9Yy8FSjEtM .actor-man circle,#mermaid-svg-cYSoen9Yy8FSjEtM line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-cYSoen9Yy8FSjEtM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} GET /user/list WebFlux 入口 → DispatcherHandler RoutePredicateHandlerMapping 按序匹配 Predicate(Path=/user/** 命中) FilteringWebHandler 组装过滤器链 (Order 排序 + 路由级过滤器包装) 前置过滤器依次执行 (鉴权/限流/改请求头...) lb:// 查询 user-service 实例列表 返回可用实例(轮询选一个) NettyRoutingFilter 异步转发 /list 200 响应 后置过滤器逆序执行 (改响应头/日志/计时...) 写回响应
关键认知 :Gateway 的请求生命周期 = 匹配路由(同步快速)→ 前置过滤链 → 异步非阻塞转发 → 后置过滤链(逆序)→ 写回。全程无阻塞 I/O,线程不随请求数线性增长。
5. 关键类关系图
#mermaid-svg-0cbAXWwJhQd6kwcV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0cbAXWwJhQd6kwcV .error-icon{fill:#552222;}#mermaid-svg-0cbAXWwJhQd6kwcV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0cbAXWwJhQd6kwcV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0cbAXWwJhQd6kwcV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0cbAXWwJhQd6kwcV .marker.cross{stroke:#333333;}#mermaid-svg-0cbAXWwJhQd6kwcV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0cbAXWwJhQd6kwcV p{margin:0;}#mermaid-svg-0cbAXWwJhQd6kwcV g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-0cbAXWwJhQd6kwcV g.classGroup text .title{font-weight:bolder;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster-label text{fill:#333;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster-label span{color:#333;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster-label span p{background-color:transparent;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster text{fill:#333;}#mermaid-svg-0cbAXWwJhQd6kwcV .cluster span{color:#333;}#mermaid-svg-0cbAXWwJhQd6kwcV .nodeLabel,#mermaid-svg-0cbAXWwJhQd6kwcV .edgeLabel{color:#131300;}#mermaid-svg-0cbAXWwJhQd6kwcV .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-0cbAXWwJhQd6kwcV .label text{fill:#131300;}#mermaid-svg-0cbAXWwJhQd6kwcV .labelBkg{background:#ECECFF;}#mermaid-svg-0cbAXWwJhQd6kwcV .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-0cbAXWwJhQd6kwcV .classTitle{font-weight:bolder;}#mermaid-svg-0cbAXWwJhQd6kwcV .node rect,#mermaid-svg-0cbAXWwJhQd6kwcV .node circle,#mermaid-svg-0cbAXWwJhQd6kwcV .node ellipse,#mermaid-svg-0cbAXWwJhQd6kwcV .node polygon,#mermaid-svg-0cbAXWwJhQd6kwcV .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0cbAXWwJhQd6kwcV .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV g.clickable{cursor:pointer;}#mermaid-svg-0cbAXWwJhQd6kwcV g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-0cbAXWwJhQd6kwcV g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-0cbAXWwJhQd6kwcV .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-0cbAXWwJhQd6kwcV .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-0cbAXWwJhQd6kwcV .dashed-line{stroke-dasharray:3;}#mermaid-svg-0cbAXWwJhQd6kwcV .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-0cbAXWwJhQd6kwcV #compositionStart,#mermaid-svg-0cbAXWwJhQd6kwcV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #compositionEnd,#mermaid-svg-0cbAXWwJhQd6kwcV .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #dependencyStart,#mermaid-svg-0cbAXWwJhQd6kwcV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #dependencyStart,#mermaid-svg-0cbAXWwJhQd6kwcV .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #extensionStart,#mermaid-svg-0cbAXWwJhQd6kwcV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #extensionEnd,#mermaid-svg-0cbAXWwJhQd6kwcV .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #aggregationStart,#mermaid-svg-0cbAXWwJhQd6kwcV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #aggregationEnd,#mermaid-svg-0cbAXWwJhQd6kwcV .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #lollipopStart,#mermaid-svg-0cbAXWwJhQd6kwcV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV #lollipopEnd,#mermaid-svg-0cbAXWwJhQd6kwcV .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-0cbAXWwJhQd6kwcV .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-0cbAXWwJhQd6kwcV .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0cbAXWwJhQd6kwcV .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0cbAXWwJhQd6kwcV .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0cbAXWwJhQd6kwcV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 获取路由
yml 配置路由
服务发现路由
命中后委托执行
链式执行
路由级过滤适配
<<interface>>
RouteLocator
+getRoutes() : Flux<Route>
CachingRouteLocator
CompositeRouteLocator
RouteDefinitionRouteLocator
DiscoveryClientRouteLocator
RoutePredicateHandlerMapping
+getHandler()
FilteringWebHandler
+handle()
<<interface>>
GlobalFilter
+filter()
NettyRoutingFilter
+getOrder()
ForwardRoutingFilter
GatewayFilterAdapter
+filter() : 将路由级过滤包装为全局过滤
<<interface>>
GatewayFilterFactory
+apply()
StripPrefixGatewayFilterFactory
<<interface>>
RoutePredicateFactory
+apply()
PathRoutePredicateFactory
6. 扩展点与常见问题
6.1 扩展点(SPI 一览)
| 扩展点 | 用途 | 示例 |
|---|---|---|
GlobalFilter |
全局前置/后置处理 | 统一鉴权、日志、灰度头透传 |
GatewayFilterFactory |
自定义路由级过滤器(yml 可引用) | 自定义加解密、签名校验 |
RoutePredicateFactory |
自定义断言规则 | 按业务字段路由 |
RouteLocator |
自定义路由来源 | 从数据库/配置中心加载路由 |
RefreshRoutesEvent |
动态路由刷新 | 配合 Nacos/Apollo 热更新 |
KeyResolver |
限流维度自定义 | 按用户/租户/IP 限流 |
| 自定义异常处理器 | 网关统一错误响应 | ErrorWebExceptionHandler 定制返回体 |
6.2 常见问题 FAQ
Q1:启动报错 Spring MVC found on classpath?
spring-cloud-starter-gateway 基于 WebFlux,与 spring-boot-starter-web(Spring MVC)互斥。检查依赖树排除 web starter,或确认项目确实要用 MVC 时改走 Gateway 的 Proxy Exchange 变体(5.0+,可用于 MVC 应用做代理)。
Q2:路由配置了但不生效 / 404?
按顺序排查:① 断言是否全匹配(多个 Predicate 是 AND 关系);② 路由顺序------Gateway 按定义顺序取第一个匹配 的路由,更具体的路由要放前面;③ Path 大小写与路径段数是否与 StripPrefix 搭配正确;④ 短路写法(逗号分隔)与展开写法(列表)混用导致语法错误。
Q3:转发 502 / 503 连不上目标服务?
lb:// 需要 loadbalancer starter + 服务发现组件(Nacos/Eureka)缺一不可 ;检查目标服务是否已注册、实例是否健康(可先换 http://ip:port 直连验证网络);超时问题检查 connect-timeout/response-timeout 配置。
Q4:高并发下网关线程数暴涨 / 响应慢?
排查是否误引了阻塞组件(JDBC 驱动、阻塞 Redis 客户端、Thread.sleep 类自定义过滤器)------WebFlux 链路中任何阻塞调用 都会拖垮 Netty 事件循环;Redis 限流必须用 -data-redis-reactive。
Q5:怎么做到路由不重启动态生效?
路由配置放 Nacos(shared-configs 开 refresh: true)→ 配置变更触发 RefreshRoutesEvent → 网关重建路由。如需更完整的路由管理(增删改查+审计),可在 RouteLocator 上自研 DB 版路由源(见 6.1)。
Q6:限流配置了但没生效?
① key-resolver 引用的 Bean 名与 @Bean 方法名不一致;② 未用 spring-boot-starter-data-redis-reactive(阻塞客户端在 WebFlux 下异常);③ Redis 连不上(限流静默失败,看日志);④ 理解参数语义:replenishRate=10 是每秒补充 10 令牌 ,burstCapacity 是桶容量,实际 TPS 上限受两者共同影响。
Q7:Gateway 和 Nginx 如何分工?
常见架构:公网 → Nginx(SSL 卸载、四层/七层负载、静态资源)→ Gateway(业务路由、鉴权、限流、灰度)→ 微服务。中小规模也可只保留 Gateway(配合 K8s Ingress),取决于团队对 Lua vs Java 的掌控力。
Q8:升级 Spring Boot 4 时 Gateway 要注意什么?
必须同时升级 Spring Cloud 到 2025.1(Oakwood) 、Gateway 到 5.0.x(基于 Framework 7),否则兼容性验证器阻止启动;留意 5.0 的破坏性变更(旧 API 清理),以及 Proxy Exchange / WebSocket 路由等新能力是否适用你的场景。