当前位置: 首页 > news >正文

一次深入排查:Spring Cloud Gateway TCP 连接复用导致 K8s 负载均衡失效

时间:2025-10
标签:Spring Cloud Gateway、Netty、TCP连接池、Kubernetes、负载均衡


📌 背景

最近在项目中发现一个非常隐蔽的问题:
使用 Spring Cloud Gateway (SCG) 作为网关时,下游服务部署在 Kubernetes 中,理论上流量应由 Service 的负载均衡机制 均匀分发到各个 Pod。

但实际上,所有请求几乎都被转发到了同一个 Pod,负载严重不均衡。
最初怀疑是 K8s 的问题,后来深入分析发现——罪魁祸首竟然是 TCP 连接复用


⚙️ 问题现象

在网关容器中执行命令:

watch -n 0.5 "lsof | grep ESTABLISHED | grep {下游服务k8s负载名称}"

发现所有请求复用相同的 TCP 连接:

gateway -> pod-1:8080 ESTABLISHED

无论发出多少请求,连接始终没有新建,这意味着:

  • K8s 的负载均衡机制(基于 TCP 三次握手时的连接调度)被绕过;
  • 所有流量都打到了一个后端 Pod 上。

🔍 问题分析

1️⃣ Spring Cloud Gateway 的连接模型

Spring Cloud Gateway 默认使用 Reactor Netty HttpClient
其内部维护了一个全局的 ConnectionProvider(连接池),用于:

  • 连接复用;
  • TCP Keep-Alive;
  • 减少握手开销,提高性能。

关键点在于:

Gateway 的所有下游请求默认共享同一个 HttpClient,也就共享同一个连接池。

这意味着:

  • 同一目标主机的请求会不断复用同一个 TCP 连接;
  • 从 K8s 的视角,这些请求都来自同一个“长连接”,所以不会重新做负载调度。

2️⃣ 负载不均衡的根因

Kubernetes 的 Service 负载均衡 是在 TCP 层(L4)做的。
每当客户端(这里是 Gateway)新建一个 TCP 连接时,kube-proxy 才会随机选择一个 Pod。
如果连接被长时间复用,那么 K8s 根本没有机会重新分配流量。

于是出现了:

❌ 请求很多,连接很少 → K8s 只看到一个连接 → 流量集中在一个 Pod 上。


🧩 解决方案尝试

❌ 方案 1:设置 HTTP Header Connection: close

尝试在响应或请求头中加入:

exchange.getResponse().getHeaders().add("Connection", "close");

但是无效。
因为 SCG 内部的 RemoveHopByHopHeadersFilter 会在转发前移除 Connection 等 hop-by-hop 头部,根本到不了下游。


❌方案 2:自定义 HttpClient 禁用连接复用

通过自定义 HttpClient,为指定路由禁用连接池和 Keep-Alive:

HttpClient client = HttpClient.create(ConnectionProvider.newConnection()).protocol(HttpClient.Protocol.HTTP11).keepAlive(false).headers(h -> h.add("Connection", "close")).responseTimeout(Duration.ofSeconds(10)).doOnConnected(conn -> {log.info("[NoReuseConnectionFilter] 新连接建立:{}", conn.channel().id().asShortText());conn.addHandlerLast(new ReadTimeoutHandler(5, TimeUnit.SECONDS));}).doOnRequest((req, conn) -> {log.info("[NoReuseConnectionFilter] 发送请求到下游: {} | connectionId={}",exchange.getRequest().getURI(), conn.channel().id().asShortText());}).doOnResponse((res, conn) -> {log.info("[NoReuseConnectionFilter] 收到响应: {} | 状态码={} | connectionId={}",exchange.getRequest().getURI(), res.status().code(), conn.channel().id().asShortText());}).doOnDisconnected(conn -> {log.info("[NoReuseConnectionFilter] 连接关闭:{}", conn.channel().id().asShortText());});

这样,每次请求都会新建 TCP 连接,不再走共享连接池。但是找不到修改转发请求池的地方,NettyRoutingFilter中的client是不能被改变的。


⚙️ 方案 3:继承 NettyRoutingFilter 实现路由级隔离(最终方案 ✅)

在源码分析后发现:
NettyRoutingFilter 的核心方法 getHttpClient() 是决定底层请求池的关键。

默认实现如下:

protected HttpClient getHttpClient(ServerWebExchange exchange) {return this.httpClient;
}

于是我们自定义子类:

@Component
@Order(-1)
public class CustomNettyRoutingFilter extends NettyRoutingFilter {@Overrideprotected HttpClient getHttpClient(ServerWebExchange exchange) {// 对指定路由使用独立连接池if (exchange.getRequest().getURI().getHost().contains("{下游服务k8s负载名称}")) {return HttpClient.create(ConnectionProvider.newConnection()).keepAlive(false).protocol(HttpClient.Protocol.HTTP11);}// 其他路由使用默认clientreturn super.getHttpClient(exchange);}
}

结果:每个请求都会重新建立连接,K8s 的负载均衡恢复正常。

日志输出也能看到每次请求的连接 ID 都不同:

[NoReuseConnectionFilter] 新连接建立:1f2a3b4c
[NoReuseConnectionFilter] 新连接建立:3d4e5f6a
...

💡 最终结论

问题原因解决方案
K8s 负载均衡失效Spring Cloud Gateway 共享 TCP 连接池复写 NettyRoutingFilter#getHttpClient(),为特定路由创建独立 HttpClient
Connection: close 无效被内部过滤器移除不推荐
网关并发极高TCP 连接复用可考虑部分路由隔离连接池,避免性能下降

🚀 总结与启示

  • Spring Cloud Gateway 默认的连接池设计追求性能,但在某些高并发、分布式环境下会导致意料之外的问题;
  • K8s 的负载均衡依赖新建连接,如果上游长连接复用,就会破坏其调度逻辑;
  • 扩展 NettyRoutingFilter 是一种优雅且非侵入的方案,可以精确控制哪些路由需要隔离。

🧭 延伸阅读

  • Spring Cloud Gateway 源码:NettyRoutingFilter.java
  • Reactor Netty ConnectionProvider 文档
  • Kubernetes Service 负载均衡机制详解

http://www.dtcms.com/a/540013.html

相关文章:

  • 基于 Vue3 及TypeScript 项目后的总结
  • Android下解决滑动冲突的常见思路是什么?
  • 建筑外观设计网站如何做一个门户网站
  • SQL多表查询完全指南-从JOIN到复杂关联的数据整合利器
  • Redis主从复制与哨兵集群
  • 电科金仓“异构多活架构”:破解浙江省人民医院集团化信创难题的密钥
  • 从零搭建群晖私有影音库:NasTool自动化追剧全流程拆解与远程访问协议优化实践
  • Maven项目管理:高效构建与依赖管理!
  • 【win11】funasr 1:配置conda环境
  • 2025年--Lc219-590. N 叉树的后序遍历(递归版,带测试用例)-Java版
  • YOLO11追踪简单应用
  • Spring Web MVC 入门秘籍:从概念到实践的快速通道(上)
  • 网站是什么字体企业内网模板
  • 建一个小型购物网站要有服务器网易博客搬家wordpress
  • 申威服务器安装Nacos 2.0.3 RPM包详细步骤(Kylin V10 sw_64架构)​附安装包
  • 当同一个弹性云服务器所在子网同时设置了snat和弹性公网IP时,会优先使用哪个
  • 基于Chrome140的TK账号自动化(关键词浏览)——需求分析环境搭建(一)
  • 如何自建内网穿透(FRP)服务器
  • 服务器上用Slurm 管理训练bash 脚本任务
  • SpringBoot入门,第一个SpringBoot的创建
  • Spring AOP核心原理分析
  • HTTPS 错误排查实战,从握手到应用层的工程化流程
  • 基于ASP身份认证服务器实现远程办公VPN双因素认证的架构与实践
  • 服务器会遭受到哪些网络攻击
  • 网站设计制作软件江门自助建站模板
  • 滨州做网站推广h5制作方法
  • KTM5800——30bit 绝对角度细分器支持最多 4096 对极与一键非线性自校准集成双 16bit 2M SAR ADC,可替代TW29
  • 局域网网站制作保定哪有做网站的
  • 基于LPJ模型的植被NPP模拟、驱动力分析及其气候变化响应预测
  • 人脸识别4-Windows下基于MSVC编译SeetaFace6