前言

在微服务架构的面试中,经常会被问到:“你们的微服务之间通过 Feign 调用时,请求会不会经过网关?”很多同学下意识会觉得“所有请求都要走网关统一管理”,从而给出错误的答案。本文将用一张图、两条链路,帮你把这个知识点彻底讲透,并且延伸到背后的架构设计思想。

一、直击结论:默认不经过网关

微服务之间的内部调用(如 service-order 通过 OpenFeign 调用 service-product),默认是直连的,流量完全不经过网关 Gateway。

很多开发者会混淆“外部请求”和“内部调用”的链路,下面我们通过具体的架构图来理解。


二、两种调用链路详细分析

整个系统中有两条完全独立的调用链路:

1. 外部客户端 → 网关 → 微服务(走网关)

  • 路径:前端页面、浏览器、Postman、App 等客户端首先请求到 API 网关(Gateway)
  • 网关职责:完成统一的身份认证、权限校验、限流、跨域处理、请求/响应日志记录、路由转发等工作。
  • 转发逻辑:网关根据路由规则(如 /order/**)将请求转发到对应的微服务,比如 service-order
  • 这是外部流量,必须经过网关,因为网关是整个系统对外的唯一入口,负责安全管控。

示意图描述:

[ 前端 / 客户端 ]
|
v
[ Gateway 网关 ] (鉴权、限流、跨域、日志)
|
v
[ service-order ]

2. 微服务 ↔ 微服务(Feign 内部调用,不走网关)

service-order 在业务逻辑中需要查询商品信息,通过 OpenFeign 调用 service-product 时:

  1. service-order 启动时,从 注册中心(Nacos / Eureka) 拉取 service-product 的实例列表(IP、端口、元数据等)。
  2. OpenFeign 根据内置的负载均衡策略,选择一个实例,直接发起 HTTP 请求。
  3. 这个请求是 点对点内网通信,完全不会经过 Gateway 网关。
  4. 网关对这一过程无感知,也不会执行任何网关层面配置的过滤器、限流规则。

示意图描述:

[ service-order ]
|
| Feign 直连 (通过注册中心获取实例地址)
v
[ service-product ]

图示中的两条独立链路

在面试画图时,一定要明确区分为两条线:

  • 链路一(业务内部调用)service-order → service-product,服务间点对点直连,不经过 API 网关。底层网络可能是内网、专线,也可能在特殊场景下走公网,但都属于应用层的直连通信,不经过 gateway
  • 链路二(外部访问)前端 → gateway → service-order,必须经过网关。

三、为什么内部调用不走网关?

这种设计背后有三点核心原因,面试时能讲出来会很加分:

1. 性能损耗

内部服务之间如果也绕路到网关,会多一层网络转发,增加不必要的延迟。直连方式下,吞吐量更高、响应更快。

2. 职责划分清晰

网关的核心定位是 面向外部客户端的统一入口,处理与业务无关的横切关注点(鉴权、限流、协议转换等)。
而微服务间的内部通信属于 集群内部流量,应该由服务自身和治理组件(如服务网格、熔断组件)来保障,无需网关介入。

3. 注册中心天然支持直连

Nacos、Eureka 等服务发现组件已经提供了所有健康实例的地址列表,配合 Ribbon/LoadBalancer 直接调用是最高效的方式,不需要额外增加一个中心化的转发节点。


四、特殊场景:强制内部调用走网关会怎样?(走网关是可以通过技术实现的)

极少情况下,如果业务要求所有调用都必须经过网关做统一限流、鉴权(比如早期遗留系统改造不彻底),可以手动修改 Feign 的调用地址为网关地址:

# 将 feign client 的 url 指向网关
feign:
client:
config:
service-product:
url: http://gateway:8080/product

两种强制走网关的实现写法完整对比

上面 yaml 配置是硬编码直连网关的方案,还有一种通过 @FeignClient 注解指定网关服务名的写法,二者底层逻辑完全不同,下面分开拆解:

写法1:注解 @FeignClient(value = "gateway")(Java 代码方式)
@FeignClient(value = "gateway", fallback = ProductFeignClientFallback.class)
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
  • value = "gateway":从注册中心发现网关服务,gateway 是网关在 Nacos/Eureka 注册的服务名;
  • 请求完整拼接:gateway服务地址 + /product/{id};
  • 负载均衡:会针对网关集群做客户端负载均衡(如果网关部署多实例);
  • 适用场景:网关注册到服务注册中心,集群部署。
写法2:配置文件指定 url: http://gateway:8080/product(yaml 硬编码方式)
  • url 是写死的固定地址,不走注册中心服务发现,直接硬编码网关域名 / IP 端口;
  • 请求完整拼接:http://gateway:8080/product + /{id};
  • 负载均衡:完全失效,只会请求这个固定地址,网关多实例无法自动轮询;
  • 适用场景:网关未注册进注册中心、单机固定地址部署。

底层核心原理区分:

  • @FeignClient(value):Feign 走服务发现模式,value 是服务 ID,负载均衡组件会从注册中心拉取该服务所有节点;
  • @FeignClient(url) / yaml 配置 url:Feign 切换为固定直连模式,一旦配置 url,服务发现逻辑直接禁用,负载均衡组件不会生效。

但无论采用上面哪种写法强制内部流量走网关,都会引入一系列严重问题:

  1. 负载均衡失效:原本由 Ribbon 根据注册中心多个实例进行的客户端负载均衡,现在所有流量都打到了网关这一个点,再由网关转发,网关容易成为瓶颈。
  2. 性能进一步下降:增加了一次网关的额外路由跳转,调用链路变成 service-order → gateway → service-product,延迟翻倍。
  3. 网关压力激增:内部服务间的高频调用会瞬间占满网关连接和线程资源,影响正常外部请求的处理。
  4. 架构混乱:网关本应面向外部,现在同时承载内部流量,界限模糊,排障困难。

结论:这种方案在生产环境中毫无业务价值,只会造成性能瓶颈和架构灾难,强烈不推荐。


五、扩展思考:内外流量分层治理

行业内标准做法是 内外流量分层治理,这也是面试官期望听到的进阶答案:

流量类型 入口 治理手段
外部流量(南北向) API Gateway 鉴权、限流、跨域、协议转换、日志审计
内部流量(东西向) 直连/Service Mesh 熔断降级(Sentinel / Hystrix)、负载均衡、链路追踪、服务网格
  • 前端请求统一走 Gateway 管控,做安全相关的第一道防线。
  • 服务间调用由 Sentinel 进行熔断限流保护,防止雪崩;由 SkyWalking / Zipkin 进行链路追踪;由注册中心与负载均衡器实现高可用调用。
  • 如果团队向 Service Mesh(如 Istio)演进,可以通过 sidecar 透明劫持流量,实现更细粒度的服务间治理,但依然不是通过网关中心转发。

这样分开治理,架构清晰,各层组件各司其职,扩展性和可维护性都更高。


六、总结

  1. 微服务之间通过 Feign 等 RPC 框架调用,默认不经过网关,而是通过注册中心发现实例后直连通信。
  2. 网关只负责处理来自外部的客户端请求,是整个系统的“门面”。
  3. 技术层面虽然可以通过改写 Feign 地址强制走网关,但会引入性能问题、负载均衡失效、网关压力暴增等弊端,不推荐在生产环境使用。
  4. 正确的架构思想是 “内外流量分层治理”:外部走网关管控,内部由 Sentinel/Hystrix 和注册中心保障可靠性。

更多推荐