Spring Cloud 服务网关 Gateway 详解:微服务的 “统一入口” 实战(我们的学习指南)

        结合我们之前学的 Nacos 服务注册、LoadBalancer 负载均衡、OpenFeign 服务调用,今天我们要学的Gateway是微服务架构的 “最后一块拼图”—— 它就像微服务的 “前台接待员”,所有客户端请求都先经过它,由它统一处理路由、鉴权、限流,解决我们之前直接调用服务时的 “地址难记、跨域麻烦、认证分散” 等问题。下面我们从 “为什么需要”“是什么”“怎么实现” 一步步拆解,全程结合我们熟悉的 ServiceOne、ServiceThree 等案例,帮我们建立完整的微服务链路认知。

1. 先想清楚:我们为什么需要服务网关?

在没学 Gateway 之前,我们调用微服务是这样的:

  • 调用 ServiceOne 要访问http://localhost:8001/serviceOne

  • 调用 ServiceThree 要访问http://localhost:8003/serviceThree_toOne

  • 客户端(比如前端)要记很多服务的 IP 和端口,一旦服务地址变了,前端也要跟着改。

除此之外,我们还会遇到 3 个头疼的问题:

  1. 跨域问题:如果前端部署在http://localhost:8080,调用 8001、8003 端口的服务时,会因为浏览器的 “同源策略” 报跨域错误;

  2. 认证分散:每个服务都要写一套登录验证逻辑(比如判断 token 是否有效),重复开发且不好维护;

  3. 监控困难:要统计每个服务的调用量、响应时间,得在每个服务里加监控代码,非常繁琐。

Gateway 的出现就是为了解决这些问题—— 它是客户端和微服务之间的 “中间层”,所有请求都先过 Gateway,由它统一做这些 “非业务工作”:

  • 客户端只需要记 Gateway 的地址(比如http://localhost:8088),不用记所有服务的地址;

  • Gateway 统一处理跨域、认证,服务里不用再写重复逻辑;

  • Gateway 统一收集监控数据,不用每个服务单独配置。

生活类比:我们去医院看病,不用直接找内科、外科医生(对应微服务),而是先去 “前台”(对应 Gateway),前台会问清需求,指引我们去对应科室,还会核对我们的挂号信息(对应鉴权)—— 前台就是医院的 “网关”。

2. Gateway 是什么?和 Zuul 有什么区别?

        在学 Gateway 之前,我们得先知道它的定位:Spring Cloud 官方推出的服务网关,用来替代停更的 Netflix Zuul,专门解决微服务的 “统一入口” 问题。

2.1 先搞懂:Gateway 和 Zuul 的核心区别(我们为什么选 Gateway)

文档里对比了两者,我们用表格更直观地梳理,重点看我们关心的 “性能”“兼容性”“功能”:

对比维度 Netflix Zuul(第一代网关) Spring Cloud Gateway(现在用的)
底层实现 基于 Servlet,阻塞式 IO 基于 Spring WebFlux(Netty),非阻塞式 IO
性能 一般(阻塞 IO 处理慢,高并发下容易卡) 优秀(非阻塞 IO,支持长连接如 WebSockets)
功能支持 需依赖 Hystrix 实现限流、熔断,自身功能少 内置限流、负载均衡、路由,不用额外集成
兼容性 支持非 Spring Cloud 服务,但配置复杂 只适配 Spring Cloud,但和我们的 Nacos、OpenFeign 无缝集成
依赖冲突风险 无特殊依赖,但停更后无新功能 不能和spring-boot-starter-web(MVC)共存,容易冲突

我们的选择理由

  • 我们用的是 Spring Cloud Alibaba 生态(Nacos、OpenFeign),Gateway 和它们兼容性更好;

  • 我们之前学的 LoadBalancer 负载均衡,Gateway 默认集成,不用额外配置;

  • Zuul 已经停更,遇到 bug 没人修,Gateway 是官方主推,后续有新功能支持。

2.2 Gateway 的核心概念:3 个组件搞定路由

        Gateway 最核心的功能是 “路由转发”—— 把客户端请求转发到对应的微服务,而实现这个功能需要 3 个核心组件,我们得先理解它们:

核心组件 我们的通俗理解 作用案例
Route(路由) 一条 “转发规则”,比如 “请求/one/**就转发到 service-one” 我们配置 “/one/**→service-one”,就是一条 Route
Predicate(断言) 路由的 “匹配条件”,判断请求是否符合这条 Route 比如 “请求路径是/one/**吗?”“请求方法是 GET 吗?”,符合才转发
Filter(过滤器) 对请求 / 响应的 “拦截处理”,比如去掉路径前缀、加请求头 我们请求/one/serviceOne时,Filter 去掉/one前缀,实际转发到 service-one 的/serviceOne

举个例子帮我们理解:如果我们配置了一条 Route:

  • Route ID:service-one-route(唯一标识);

  • 目标地址:lb://service-one(lb 表示负载均衡,转发到 service-one 服务);

  • Predicate:Path=/one/**(请求路径以/one/开头);

  • Filter:StripPrefix=1(去掉路径的第一个前缀,比如/one/serviceOne变成/serviceOne);

当客户端发请求http://localhost:8088/one/serviceOne时:

  1. Predicate 判断:路径是/one/**,符合条件;

  2. Filter 处理:去掉/one前缀,请求变成/serviceOne

  3. 路由转发:通过负载均衡找到 service-one 的实例(8001 或 8004),转发请求。

2.3 Gateway 的工作流程:请求怎么走?

结合我们的微服务,Gateway 的完整工作流程我们画成图更清楚:

我们要注意的关键点

  • Filter 分 “pre” 和 “post”:pre 在转发前执行(比如鉴权),post 在响应后执行(比如加日志);

  • 负载均衡是内置的:Gateway 会自动从 Nacos 拿服务实例,和我们之前学的 LoadBalancer 逻辑一样。

3. 实战:我们亲手搭建 Gateway 子项目

        该案例是在我们之前的 “父项目 + ServiceOne/Two/Three/OneCopy” 基础上,新增 Gateway 子项目,让所有请求都通过 Gateway 转发。我们一步步来,重点关注 “依赖配置”“路由规则”“易错点”。

步骤 1:新建 Gateway 子项目(和之前建 ServiceOne 一样)

  1. 在父项目上右键→New→Module→Maven,项目名填 “Gateway”;

  2. 配置 pom.xml(核心是引入 Gateway 和 Nacos 依赖,排除冲突包);

  3. 写 application.yml(配置端口、Nacos 地址、路由规则);

  4. 写主类(加注解开启服务发现)。

步骤 2:配置 pom.xml(重点:排除 MVC 依赖,避免冲突)

        这是最容易踩坑的一步!因为 Gateway 基于 WebFlux(响应式编程),和我们父项目继承的spring-boot-starter-web(MVC)冲突,必须排除。我们的 pom.xml 配置如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <project xmlns="http://maven.apache.org/POM/4.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
     <parent>
         <groupId>com.zh</groupId>  <!-- 我们父项目的GroupId -->
         <artifactId>springcloud-parent</artifactId>
         <version>1.0-SNAPSHOT</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 ​
     <artifactId>Gateway</artifactId>
 ​
     <dependencies>
         <!-- 1. Nacos服务发现依赖:Gateway要从Nacos获取服务实例 -->
         <dependency>
             <groupId>com.alibaba.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
         </dependency>
 ​
         <!-- 2. Gateway核心依赖:提供网关功能 -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-gateway</artifactId>
         </dependency>
 ​
         <!-- 3. 排除父项目继承的spring-boot-starter-web(MVC),否则启动报错! -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <exclusions>
                 <exclusion>
                     <groupId>*</groupId>  <!-- 排除这个依赖下的所有子依赖 -->
                     <artifactId>*</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
 ​
         <!-- 4. 负载均衡依赖:Gateway默认集成,但保险起见加上,避免负载均衡不生效 -->
         <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-loadbalancer</artifactId>
         </dependency>
     </dependencies>
 </project>

我们的易错点提醒

  • 如果忘了排除spring-boot-starter-web,启动 Gateway 会报 “Circular dependency”(循环依赖)错误,因为 WebFlux 和 MVC 不能共存;

  • 不用额外引入 OpenFeign,Gateway 通过服务名直接转发,和 OpenFeign 不冲突。

步骤 3:配置 application.yml(两种路由方式,我们都学)

        Gateway 的路由配置有两种常见方式:动态路由(基于服务名)自定义路由(带过滤器),我们分别学,因为实际项目中都会用到。

方式 1:动态路由(简单,基于服务名转发)

        这种方式不用手动配每个服务的路由,Gateway 会自动从 Nacos 获取服务名,按 “http://网关地址/服务名/接口路径” 转发,适合我们快速测试。

我们的 application.yml 配置:

 server:
   port: 8088  # 网关的端口,我们选8088(别和其他服务冲突)
 spring:
   application:
     name: service-gateway  # 网关的服务名,注册到Nacos用
   cloud:
     nacos:
       # Nacos服务发现配置:网关要从Nacos拿其他服务的实例
       discovery:
         server-addr: 127.0.0.1:8848  # 我们本地的Nacos地址
         service: ${spring.application.name}
     gateway:
       # 核心:开启动态路由(基于服务名转发)
       discovery:
         locator:
           enabled: true  # 开启后,Gateway会自动按“服务名”路由
           # 可选:lower-case-service-id: true  # 服务名不区分大小写(比如Service-One也能匹配service-one)

我们的调用逻辑

  • 之前直接调用 ServiceOne:http://localhost:8001/serviceOne

  • 现在通过 Gateway 调用:http://localhost:8088/service-one/serviceOne

  • 原理:Gateway 看到service-one,会从 Nacos 找 service-one 的实例(8001/8004),然后转发请求,还会自动负载均衡。

方式 2:自定义路由(灵活,带过滤器,实际项目常用)

        动态路由虽然简单,但路径里带服务名(比如/service-one/)不够友好,我们可以通过自定义路由,把路径改成更简洁的(比如/one/→service-one),还能加过滤器处理路径。

我们修改 application.yml,用routes配置自定义路由:

 server:
   port: 8088
 spring:
   application:
     name: service-gateway
   cloud:
     nacos:
       discovery:
         server-addr: 127.0.0.1:8848
         service: ${spring.application.name}
     gateway:
       # 关闭动态路由(和自定义路由二选一,避免冲突)
       # discovery:
       #   locator:
       #     enabled: false
       # 核心:自定义路由规则
       routes:
         # 第一条路由:匹配/one/**→service-one
         - id: route-service-one  # 路由唯一ID(随便起,但不能重复)
           uri: lb://service-one  # 目标地址:lb=负载均衡,转发到service-one
           predicates:  # 匹配条件:请求路径以/one/开头
             - Path=/one/**
           filters:  # 过滤器:去掉路径的第一个前缀(/one)
             - StripPrefix=1
         # 第二条路由:匹配/two/**→service-two
         - id: route-service-two
           uri: lb://service-two
           predicates:
             - Path=/two/**
           filters:
             - StripPrefix=1
         # 第三条路由:匹配/three/**→service-three
         - id: route-service-three
           uri: lb://service-three
           predicates:
             - Path=/three/**
           filters:
             - StripPrefix=1

我们重点理解过滤器 StripPrefix=1

  • 客户端请求:http://localhost:8088/one/serviceOne

  • Filter 处理:StripPrefix=1 表示 “去掉路径的第一个/后的部分”,也就是去掉/one,剩下/serviceOne

  • 最终转发:Gateway 把请求转发到 service-one 的/serviceOne接口,和我们之前的调用逻辑一致。

我们的调用对比

微服务 动态路由调用地址 自定义路由调用地址
ServiceOne http://8088/service-one/serviceOne http://8088/one/serviceOne
ServiceTwo http://8088/service-two/serviceTwo http://8088/two/serviceTwo
ServiceThree http://8088/service-three/serviceThree_toOne http://8088/three/serviceThree_toOne

步骤 4:写 Gateway 的主类(最简单的主类)

        Gateway 的主类不用加复杂配置,只需要开启服务发现即可,因为路由规则都在 application.yml 里配好了:

package com.zh.gateway;
 ​
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
 ​
 @SpringBootApplication
 @EnableDiscoveryClient  // 开启服务发现:让Gateway能从Nacos获取其他服务的实例
 public class GatewayApplication {
     public static void main(String[] args) {
         // 启动网关,加载application.yml的路由配置
         SpringApplication.run(GatewayApplication.class, args);
     }
 }

我们的注意点

  • 主类不用加@EnableFeignClients,因为 Gateway 是转发请求,不用调用 Feign 接口;

  • 不用配置 RestTemplate,Gateway 有自己的转发机制。

4. 结果验证:我们亲手测试 Gateway 是否生效

        和之前测试 LoadBalancer、OpenFeign 一样,我们按 “启动顺序→Nacos 验证→接口调用” 的步骤来,确保 Gateway 能正常转发和负载均衡。

步骤 1:按顺序启动服务(重要,避免依赖问题)

  1. 先启动 Nacos Server(确保 8848 端口可用);

  2. 再启动服务提供者:ServiceOne(8001)、ServiceOneCopy(8004)、ServiceTwo(8002);

  3. 然后启动 ServiceThree(8003);

  4. 最后启动 Gateway(8088)。

        我们的检查点:启动后看每个服务的控制台,没有报错(尤其是 Gateway,别出现 MVC 冲突错误)。

步骤 2:查看 Nacos 服务列表(确认 Gateway 注册成功)

访问http://127.0.0.1:8848/nacos,在 “服务列表” 里能看到service-gateway(Gateway 的服务名),其他服务也都在:

服务名 实例数 健康实例数 说明
service-gateway 1 1 Gateway 实例
service-one 2 2 ServiceOne(8001)+OneCopy(8004)
service-two 1 1 ServiceTwo(8002)
service-three 1 1 ServiceThree(8003)

我们的确认点:所有服务的 “健康实例数” 等于 “实例数”,说明都正常注册到 Nacos 了。

步骤 3:调用接口验证(两种路由方式都测)

我们分别测试 “动态路由” 和 “自定义路由”,还要验证负载均衡是否生效。

测试 1:动态路由(基于服务名)
  • 调用 ServiceOne 的接口:http://localhost:8088/service-one/serviceOne

    • 第一次返回(来自 8001):{"code":0,"message":"Service one method return!"}

    • 第二次返回(来自 8004):{"code":0,"message":"Service one \"COPY\" method return!"}

    • 结论:Gateway 自动负载均衡,和我们之前学的 LoadBalancer 效果一样。

  • 调用 ServiceThree 的接口:http://localhost:8088/service-three/serviceThree_toOne

    • 第一次返回 data 是 ServiceOne(8001)的结果;

    • 第二次返回 data 是 ServiceOneCopy(8004)的结果;

    • 结论:Gateway 转发到 ServiceThree 后,ServiceThree 内部调用 ServiceOne 依然负载均衡,链路完整。

测试 2:自定义路由(带 StripPrefix 过滤器)
  • 调用 ServiceOne 的接口:http://localhost:8088/one/serviceOne

    • 结果和动态路由一样,会轮询 8001 和 8004;

    • 原理:Filter 去掉/one前缀,转发到service-one/serviceOne

  • 调用 ServiceTwo 的接口:http://localhost:8088/two/serviceTwo

    • 返回:{"code":0,"message":"Service two method return!"}

    • 结论:路由匹配成功,转发到 ServiceTwo。

我们的成就感:现在我们只用记 Gateway 的 8088 端口,就能调用所有微服务的接口,前端再也不用记多个地址了!

5. 我们的重点 & 易错点总结(避坑指南)

        Gateway 配置不算复杂,但初学者容易踩 “依赖冲突”“路由不匹配” 的坑,我们整理了最常见的问题,帮我们后续编码少走弯路:

5.1 重点知识(必须记住)

  1. Gateway 的定位:微服务的统一入口,所有客户端请求都走 Gateway,服务内部调用(如 ServiceThree→ServiceOne)可以不走 Gateway;

  2. 路由匹配顺序routes里的路由是 “按顺序匹配” 的,前面的路由匹配成功,就不会走后面的,所以我们要把精确的路由放前面(比如/one/detail/**/one/**前面);

  3. StripPrefix 的作用:去掉路径前缀,比如StripPrefix=2会去掉两个前缀(/one/two/serviceOne/serviceOne),根据我们的路由需求调整;

  4. 负载均衡内置:Gateway 默认集成 LoadBalancer,不用加@LoadBalanced,只要 uri 用lb://服务名,就会自动负载均衡。

5.2 易错点 & 解决方案(我们踩过的坑)

易错点描述 我们的解决方案
启动 Gateway 报 “循环依赖” 错误 忘了排除spring-boot-starter-web,在 pom.xml 里把这个依赖的子依赖全部排除(参考步骤 2 的 pom 配置)
自定义路由不生效,一直 404 1. 检查predicates的 Path 是否带/**(比如/one要改成/one/**);2. 检查 uri 是否是lb://服务名(别写成 http://IP: 端口);3. 检查服务名是否和 Nacos 里的一致(大小写敏感)
StripPrefix 过滤器没生效,路径不对 1. 确认filters里写的是StripPrefix=1(别少了 “=1”);2. 检查请求路径是否符合predicates(比如/one/serviceOne才会触发过滤器)
Gateway 能转发,但负载均衡不生效 1. 确认引入了spring-cloud-starter-loadbalancer依赖;2. uri 必须用lb://服务名(别写成 http:// 固定 IP);3. 目标服务有多个实例(比如 service-one 要有 8001 和 8004 两个实例)

6. 关联我们之前的知识:完整微服务链路终于通了!

学完 Gateway 后,我们的微服务架构链路就完整了,我们梳理一下整个流程:

  1. 服务注册:ServiceOne、ServiceTwo、ServiceThree、Gateway 启动后,都注册到 Nacos;

  2. 客户端请求:前端发请求http://localhost:8088/one/serviceOne(通过 Gateway);

  3. 网关转发:Gateway 根据路由规则(/one/**→lb://service-one),去掉/one前缀,从 Nacos 找 service-one 的实例(8001/8004);

  4. 负载均衡:Gateway 选一个实例(比如 8001),转发请求到http://127.0.0.1:8001/serviceOne

  5. 服务响应:ServiceOne 返回 JSON 结果,经过 Gateway 的 post 过滤器,返回给前端。

我们的完整链路图

7. 我们的后续学习方向

Gateway 是微服务的 “入口”,后续我们还需要给它加更多 “安全防护” 和 “监控能力”:

  1. 网关鉴权:在 Gateway 里加过滤器,验证客户端的 token(比如 JWT),没登录的请求直接拦截,不用每个服务都写鉴权;

  2. 限流熔断:用 Gateway 的内置限流功能(比如 RedisRateLimiter),限制每秒的请求数,避免服务被冲垮;

  3. 日志监控:在 Gateway 的 post 过滤器里加日志,记录每个请求的响应时间、状态码,方便我们排查问题;

  4. 跨域处理:在 Gateway 里统一配置跨域(比如允许前端的 8080 端口访问),不用每个服务都配@CrossOrigin

总结

        Gateway 对我们的微服务架构来说,就像 “大门” 一样重要 —— 它解决了我们之前 “地址难记、认证分散、跨域麻烦” 的问题,还和我们学的 Nacos、LoadBalancer 无缝集成。现在我们只用记 Gateway 的地址,就能调用所有微服务的接口,后续维护也更方便。

        记住:Gateway 的核心是 “路由 + 过滤器”,我们不用死记配置,而是根据业务需求调整路由规则和过滤器,比如我们想让/api/order/**转发到订单服务,就配置一条对应的 Route,灵活运用即可。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐