微服务之间调用到底经不经过网关?
前言
在微服务架构的面试中,经常会被问到:“你们的微服务之间通过 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 时:
service-order启动时,从 注册中心(Nacos / Eureka) 拉取service-product的实例列表(IP、端口、元数据等)。- OpenFeign 根据内置的负载均衡策略,选择一个实例,直接发起 HTTP 请求。
- 这个请求是 点对点内网通信,完全不会经过 Gateway 网关。
- 网关对这一过程无感知,也不会执行任何网关层面配置的过滤器、限流规则。
示意图描述:
[ 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,服务发现逻辑直接禁用,负载均衡组件不会生效。
但无论采用上面哪种写法强制内部流量走网关,都会引入一系列严重问题:
- 负载均衡失效:原本由 Ribbon 根据注册中心多个实例进行的客户端负载均衡,现在所有流量都打到了网关这一个点,再由网关转发,网关容易成为瓶颈。
- 性能进一步下降:增加了一次网关的额外路由跳转,调用链路变成
service-order → gateway → service-product,延迟翻倍。 - 网关压力激增:内部服务间的高频调用会瞬间占满网关连接和线程资源,影响正常外部请求的处理。
- 架构混乱:网关本应面向外部,现在同时承载内部流量,界限模糊,排障困难。
结论:这种方案在生产环境中毫无业务价值,只会造成性能瓶颈和架构灾难,强烈不推荐。
五、扩展思考:内外流量分层治理
行业内标准做法是 内外流量分层治理,这也是面试官期望听到的进阶答案:
| 流量类型 | 入口 | 治理手段 |
|---|---|---|
| 外部流量(南北向) | API Gateway | 鉴权、限流、跨域、协议转换、日志审计 |
| 内部流量(东西向) | 直连/Service Mesh | 熔断降级(Sentinel / Hystrix)、负载均衡、链路追踪、服务网格 |
- 前端请求统一走 Gateway 管控,做安全相关的第一道防线。
- 服务间调用由 Sentinel 进行熔断限流保护,防止雪崩;由 SkyWalking / Zipkin 进行链路追踪;由注册中心与负载均衡器实现高可用调用。
- 如果团队向 Service Mesh(如 Istio)演进,可以通过 sidecar 透明劫持流量,实现更细粒度的服务间治理,但依然不是通过网关中心转发。
这样分开治理,架构清晰,各层组件各司其职,扩展性和可维护性都更高。
六、总结
- 微服务之间通过 Feign 等 RPC 框架调用,默认不经过网关,而是通过注册中心发现实例后直连通信。
- 网关只负责处理来自外部的客户端请求,是整个系统的“门面”。
- 技术层面虽然可以通过改写 Feign 地址强制走网关,但会引入性能问题、负载均衡失效、网关压力暴增等弊端,不推荐在生产环境使用。
- 正确的架构思想是 “内外流量分层治理”:外部走网关管控,内部由 Sentinel/Hystrix 和注册中心保障可靠性。
更多推荐
所有评论(0)