Gateway (上):路由与转发,微服务的统一入口

一、服务调通了,外部访问还是乱的

Nacos 和 Feign 解决了服务间调用,外部访问却还是乱的:cart-service 跑在 8082,item-service 在 8081,trade-service 在 8083,order-service 又是另一个端口。调试时每测一个功能换一个地址,测购物车用 8082,测商品切回 8081,测订单再换一个。

端口散落只是表面,后面还跟着三个问题:

  • 地址暴露:内部服务的端口直接给浏览器用,内部拓扑一览无余,扩容、迁移时前端也得跟着改
  • 跨域各配一遍:每个服务都要配 CORS,漏配一个接口就在浏览器里报跨域错误
  • 公共逻辑重复:鉴权、限流、访问日志这些横切逻辑,要么每个服务抄一遍,要么漏写

鉴权逻辑本来写一次就够,实际可能散落在五个服务里,版本还会不一致。

问题在于外部客户端不该直接面对一群内部服务。需要一个统一入口,所有外部请求先到这里,经过校验和过滤,再由它转发到真正的服务------这一层就是网关。


二、网关把什么挡在了外面

Spring Cloud Gateway 在微服务里的位置可以用一个词概括:守门人。外部请求先到它这里,它决定请求该去哪个内部服务。

它挡在外面的主要是三类东西:

地址暴露。 前端只认网关的地址,内部有几个服务、分别跑在什么端口,前端不知道也不需要知道。扩容、迁移、换实例,只要路由里的服务名不变,外部无感。

横切逻辑。 鉴权、限流、访问日志、跨域处理这些事,每个服务都要做,每个服务做一遍就是重复。放到网关,写一次,所有请求都经过它,统一生效。

协议与路径的差异。 内部服务的接口路径可能各不相同,外部只需要一套统一的路径规则。网关把外部路径映射到内部路径,两边可以各自演化。

和 Nginx 放在一起看,两者守的位置不一样。Nginx 站在更外层,处理静态资源、TLS 终止、最基础的转发;Gateway 站在应用层,能读 JWT、查缓存、写 Java 逻辑。生产环境里常见的是两者组合------Nginx 承担流量杂活,Gateway 处理需要懂业务的那部分。

至于 Spring Cloud 为什么选 WebFlux 而不是 Spring MVC:网关的请求轻、并发高,每个请求停留时间很短,非阻塞模型更合适。知道这一点即可,不影响日常使用。


三、路由:请求进门之后,往哪走

网关收到请求后,第一个问题是"这个请求该给谁"。答案在路由配置里。

路由的三要素

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-route                 # 路由唯一标识
          uri: lb://item-service         # 目标:走负载均衡找 item-service
          predicates:                    # 匹配条件
            - Path=/items/**
  • id:这条路由的唯一标识
  • uri :目标地址。写 lb://item-service 表示不写死 IP,交给负载均衡去解析
  • predicates:匹配条件,请求满足条件才会走这条路由

lb:// 需要说明一下:lb 就是 load balance,后面跟的是服务名。底层还是 Nacos + LoadBalancer 那条链路------Nacos 篇讲的"服务名 → 真实 IP"在这里原样复用,网关自己不发明解析规则。

内置断言里,Path 匹配路径、Method 匹配 GET/POST,Header、Query、Cookie 也都有对应实现。日常九成场景用 Path 就够,其余的知道存在、用到再查。

多条路由并列

实际项目里一般是多条路由放在一起:

yaml 复制代码
routes:
  - id: item-route
    uri: lb://item-service
    predicates:
      - Path=/items/**
  - id: cart-route
    uri: lb://cart-service
    predicates:
      - Path=/carts/**
  - id: trade-route
    uri: lb://trade-service
    predicates:
      - Path=/orders/**

匹配按配置顺序进行,命中第一条就停。这一点是后面 404 坑的一个伏笔:路径写岔,请求要么落到不该去的地方,要么哪条都不命中。

一次请求的完整路径

text 复制代码
浏览器    GET http://localhost:8080/items/1
   │
   ▼
Gateway 遍历所有路由,逐条用 predicates 匹配
   │   Path=/items/** 命中 item-route
   ▼
uri lb://item-service → LoadBalancer 选一个实例
   │   item-service → 192.168.10.3:8081
   ▼
转发 GET http://192.168.10.3:8081/items/1
   │
   ▼
item-service 处理,响应原路返回浏览器

四、三次 404:没配路由、路径写错、路径形态不对

网关启动成功、Nacos 也注册上了,但访问 http://localhost:8080/carts 返回:

json 复制代码
{
    "timestamp": "2026-10-09T07:37:36.947+00:00",
    "path": "/carts",
    "status": 404,
    "error": "Not Found"
}

404,而 Nacos 控制台里 cart-service 明明在线。

第一次:根本没配路由

排查分三步:先看网关日志,Netty 在 8080 启动正常、注册到 Nacos 也成功,排除网关进程问题;再看日志里有没有转发记录,从头到尾没有任何一行提到路由匹配------请求进来之后网关压根没找到匹配的路由;最后翻配置文件,routes 根本没配。网关起来了,但一条路由都没有,任何请求进来它都不知道该发给谁。

补上路由后重启,/carts 通了:

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-service
          uri: lb://item-service
          predicates:
            - Path=/items/**
        - id: cart-service
          uri: lb://cart-service
          predicates:
            - Path=/carts/**

第二次:配了路由,路径写错

yaml 复制代码
- Path=/cart/**,/search/**

/cart 少了 s------cart-service 的接口路径是 /carts,写成 /cart 永远匹配不到;/search/** 本应是搜索服务的路径,为省一条配置塞进了 item-service 的路由里,把两个不相关的服务混在一条规则下。访问 /carts 依然 404。

第三次:路径写对了,转发出去还是 404

外部请求是 GET /api/carts/1,网关匹配 Path=/api/carts/** 之后把整个路径原样转给了 cart-service,而 cart-service 的接口映射是 /carts/1------带着 /api 前缀过去,下游自然找不到。解法是加一个过滤器做路径重写:

yaml 复制代码
- id: cart-service
  uri: lb://cart-service
  predicates:
    - Path=/api/carts/**
  filters:
    - StripPrefix=1

StripPrefix=1 表示转发前剥掉路径的第一段(/api),下游收到的才是 /carts/1。前两次的坑出在"匹配",这一次出在"转发时路径变不变"------路由匹配只决定请求归谁,过滤器决定请求以什么形态交给下游,这是两件分开的事,也是 filters 藏在配置里容易被忽略的原因。

怎么分清是网关的锅还是下游的锅

三次 404 表面相同,实际是三个层次的问题:没配路由、路径写错、路径形态不对。共同点是 404 不代表"服务挂了"------服务挂了通常是 502 或 503,网关这里的 404 只有两种来源:路由表里没有匹配项,或者匹配后转过去的路径下游不认。

排查这类问题,第一步固定为看网关日志里有没有转发记录:

  • 有转发记录、下游返回 404 → 请求到下游了,是下游服务自己的路径问题
  • 没有转发记录、网关直接返回 404 → 网关路由表里没有匹配项,是路由配置问题

这一步直接分清是网关的锅还是下游的锅。第一次排查时挨个确认了下游服务的状态,方向判断晚了一步;后来把"先看转发记录"固定为处理网关问题的第一动作,两边乱找的情况就没有再出现。


五、从三篇串联看网关的位置

服务名怎么变成 IP:三篇回答的是同一个问题

Nacos、Feign、Gateway 三次都碰到了"服务名怎么变成真实 IP",但需要三套机制,是因为解决的问题不同:

  • Nacos :手动查 DiscoveryClient,把底层"服务名 → 实例"的解析看清,它是地基
  • Feign :用动态代理封装这条链路,解决服务与服务之间的调用(对内),产出是编译期的接口契约
  • Gateway :用路由 + lb:// 复用同一条链路,解决外部请求进来的第一跳(对外),产出是运行时可配的转发规则

服务发现只做一次,三者都踩在这块地基上,各自按场景包装:Feign 要的是"调用方写起来像本地方法",Gateway 要的是"请求进门先过一道关"。这也是微服务分层的常见形态------底层能力收敛,上层按用途封装。

网关是单点,为什么还敢把流量都交给它

所有外部流量都过网关,它一挂,整个系统对外不可用。这个担心在架构上是成立的,所以生产环境网关从来都是按高可用组件对待的:多实例部署,前面还有 Nginx 或云负载均衡做流量分发和健康摘除,单实例故障对用户无感。

即便如此,网关仍然是故障影响面最大的一环------内部服务挂一个只影响对应功能,网关挂了是全部功能。这决定了网关的代码必须保持"薄":只做路由、鉴权、限流这类必须统一的事,业务逻辑一律不进网关。网关越重,稳定性风险越大,这也是各家网关层长期克制的原因。

断言和过滤器为什么设计成链

路由配置里,predicates 和 filters 结构上都是一个列表:请求先过一串断言,全部命中才进入这条路由;再过一串过滤器,逐个执行。这就是责任链模式。

这个模式在这几篇里反复出现:Feign 的 RequestInterceptor、Gateway 的 GlobalFilter、Sentinel 的 SlotChain,都是"一组处理环节按序执行,每环做一件事"。认清这一点之后,学新组件时看的就不再是新 API,而是这条链上有哪几环、分别解决什么问题------会一个,一类都会。

网关这个位置,是架构上的必然

责任链是模式层面的共性,网关这个位置本身也是架构层面的共性:它不是 Spring Cloud 的发明。任何技术栈的微服务架构,只要对多个服务提供统一外部入口,都会演化出这一层。Spring Cloud Gateway 只是这个位置上的一种实现,Nginx、Kong、Apisix 各自是另一种实现。配置可以查文档,知道网关守的是哪条边界、替外部挡掉了什么,比记住 routes 的写法更重要。


转发只是网关最基础的能力。请求进门之后,谁有权访问、userId 怎么带进集群、流量超了怎么办------这些留给下篇:统一鉴权怎么做、user-info 怎么透传,以及和 Feign 拦截器怎么配合接力。

相关推荐
paopaokaka_luck1 小时前
校园新闻推荐系统(DeepSeek新闻助手、个性化推荐算法、 Redis热词排行榜、ECharts运营分析、互动与实时通知、新闻运营闭环)
java·spring boot·mysql·spring·tomcat·echarts·intellij idea
yuniko-n1 小时前
【JUC】线程池
java·开发语言
2603_969579281 小时前
备份云盘是什么意思?简单说就是“用云盘来备份数据”
学习
小天源1 小时前
智叟云集政策监测技术方案——原文解析、证据提取与可核验摘要
java·服务器·数据库
yuzhiboyouye1 小时前
我现在得后端状况以及需要做的
java
小羊没烦恼!1 小时前
在.net中读写config文件的各种方法
java·大数据·运维·服务器·前端·数据库·oracle
技术卷1 小时前
DeepSeek 部分 API 中断事件:重要细节与事实
人工智能·后端·网络安全
骑士雄师1 小时前
《结构化思维》第五章 · 精读回顾(案例版 · 含结构图)
笔记
昔我往昔1 小时前
LangChain_RAG_学习小结
后端·python