Spring Cloud 服务网关 Gateway 详解:微服务的 “统一入口” 实战
Spring Cloud 服务网关 Gateway 详解:微服务的 “统一入口” 实战(我们的学习指南)
结合我们之前学的 Nacos 服务注册、LoadBalancer 负载均衡、OpenFeign 服务调用,今天我们要学的Gateway是微服务架构的 “最后一块拼图”—— 它就像微服务的 “前台接待员”,所有客户端请求都先经过它,由它统一处理路由、鉴权、限流,解决我们之前直接调用服务时的 “地址难记、跨域麻烦、认证分散” 等问题。下面我们从 “为什么需要”“是什么”“怎么实现” 一步步拆解,全程结合我们熟悉的 ServiceOne、ServiceThree 等案例,帮我们建立完整的微服务链路认知。
1. 先想清楚:我们为什么需要服务网关?
在没学 Gateway 之前,我们调用微服务是这样的:
-
调用 ServiceOne 要访问
http://localhost:8001/serviceOne
; -
调用 ServiceThree 要访问
http://localhost:8003/serviceThree_toOne
; -
客户端(比如前端)要记很多服务的 IP 和端口,一旦服务地址变了,前端也要跟着改。
除此之外,我们还会遇到 3 个头疼的问题:
-
跨域问题:如果前端部署在
http://localhost:8080
,调用 8001、8003 端口的服务时,会因为浏览器的 “同源策略” 报跨域错误; -
认证分散:每个服务都要写一套登录验证逻辑(比如判断 token 是否有效),重复开发且不好维护;
-
监控困难:要统计每个服务的调用量、响应时间,得在每个服务里加监控代码,非常繁琐。
而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
时:
-
Predicate 判断:路径是
/one/**
,符合条件; -
Filter 处理:去掉
/one
前缀,请求变成/serviceOne
; -
路由转发:通过负载均衡找到 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 一样)
-
在父项目上右键→New→Module→Maven,项目名填 “Gateway”;
-
配置 pom.xml(核心是引入 Gateway 和 Nacos 依赖,排除冲突包);
-
写 application.yml(配置端口、Nacos 地址、路由规则);
-
写主类(加注解开启服务发现)。
步骤 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: 8088spring:application:name: service-gatewaycloud:nacos:discovery:server-addr: 127.0.0.1:8848service: ${spring.application.name}gateway:# 关闭动态路由(和自定义路由二选一,避免冲突)# discovery:# locator:# enabled: false# 核心:自定义路由规则routes:# 第一条路由:匹配/one/**→service-one- id: route-service-one # 路由唯一ID(随便起,但不能重复)uri: lb://service-one # 目标地址:lb=负载均衡,转发到service-onepredicates: # 匹配条件:请求路径以/one/开头- Path=/one/**filters: # 过滤器:去掉路径的第一个前缀(/one)- StripPrefix=1# 第二条路由:匹配/two/**→service-two- id: route-service-twouri: lb://service-twopredicates:- Path=/two/**filters:- StripPrefix=1# 第三条路由:匹配/three/**→service-three- id: route-service-threeuri: lb://service-threepredicates:- 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:按顺序启动服务(重要,避免依赖问题)
-
先启动 Nacos Server(确保 8848 端口可用);
-
再启动服务提供者:ServiceOne(8001)、ServiceOneCopy(8004)、ServiceTwo(8002);
-
然后启动 ServiceThree(8003);
-
最后启动 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 重点知识(必须记住)
-
Gateway 的定位:微服务的统一入口,所有客户端请求都走 Gateway,服务内部调用(如 ServiceThree→ServiceOne)可以不走 Gateway;
-
路由匹配顺序:
routes
里的路由是 “按顺序匹配” 的,前面的路由匹配成功,就不会走后面的,所以我们要把精确的路由放前面(比如/one/detail/**
放/one/**
前面); -
StripPrefix 的作用:去掉路径前缀,比如
StripPrefix=2
会去掉两个前缀(/one/two/serviceOne
→/serviceOne
),根据我们的路由需求调整; -
负载均衡内置: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 后,我们的微服务架构链路就完整了,我们梳理一下整个流程:
-
服务注册:ServiceOne、ServiceTwo、ServiceThree、Gateway 启动后,都注册到 Nacos;
-
客户端请求:前端发请求
http://localhost:8088/one/serviceOne
(通过 Gateway); -
网关转发:Gateway 根据路由规则(
/one/**→lb://service-one
),去掉/one
前缀,从 Nacos 找 service-one 的实例(8001/8004); -
负载均衡:Gateway 选一个实例(比如 8001),转发请求到
http://127.0.0.1:8001/serviceOne
; -
服务响应:ServiceOne 返回 JSON 结果,经过 Gateway 的 post 过滤器,返回给前端。
我们的完整链路图:
![]() | ![]() |
7. 我们的后续学习方向
Gateway 是微服务的 “入口”,后续我们还需要给它加更多 “安全防护” 和 “监控能力”:
-
网关鉴权:在 Gateway 里加过滤器,验证客户端的 token(比如 JWT),没登录的请求直接拦截,不用每个服务都写鉴权;
-
限流熔断:用 Gateway 的内置限流功能(比如 RedisRateLimiter),限制每秒的请求数,避免服务被冲垮;
-
日志监控:在 Gateway 的 post 过滤器里加日志,记录每个请求的响应时间、状态码,方便我们排查问题;
-
跨域处理:在 Gateway 里统一配置跨域(比如允许前端的 8080 端口访问),不用每个服务都配
@CrossOrigin
。
总结
Gateway 对我们的微服务架构来说,就像 “大门” 一样重要 —— 它解决了我们之前 “地址难记、认证分散、跨域麻烦” 的问题,还和我们学的 Nacos、LoadBalancer 无缝集成。现在我们只用记 Gateway 的地址,就能调用所有微服务的接口,后续维护也更方便。
记住:Gateway 的核心是 “路由 + 过滤器”,我们不用死记配置,而是根据业务需求调整路由规则和过滤器,比如我们想让/api/order/**
转发到订单服务,就配置一条对应的 Route,灵活运用即可。