记一次 OKE 集群上的 TCP 流量黑洞排查与解决全过程

作者:小猿姐日期:2026/4/9

作为 Apecloud 团队,我们致力于通过开源的 KubeBlocks 项目,在 Kubernetes (K8s) 上为用户提供企业级的数据库高可用方案[1]。其中,SQL Server on K8s with Always On 是我们支持的关键能力之一,相比Microsoft 为在容器中运行 SQL Server 提供的基础StatefulSet方案, KubeBlocks 的 MSSQL Addon 提供了一整套生产级的生命周期管理能力,包括:多节点高可用配置动态扩缩容数据库/账户管理参数管理监控告警全量/增量/PITR备份恢复,以及 TDE/TLS 数据加密等,是现有 SQL Server operator 中最成熟、最完善的方案之一[2][3]。

近期,某客户计划在 Oracle Kubernetes Engine (OKE) 环境中部署我们的 SQL Server 高可用集群。为了确保方案的可靠性,我们在 OKE 环境中对 MSSQL Addon 进行了一轮全方位的回归测试。

在测试 Failover、资源动态扩缩容等运维操作(Ops)时,我们发现了一个在 OKE 上独有的异常现象,这与我们自建的 K8s 或其他云厂商环境中的表现截然不同:在 Pod 滚动重启或主备切换后,旧的主节点 Pod 在重建后需要耗时约 15 分钟才能重新加入集群。这个意料之外的延迟,促使我们深入排查,并最终揭示了一个潜藏在云网络深处的“TCP 流量黑洞”问题。本文将完整记录这一问题的排查与解决全过程。

一、 问题现象:切换后的长连接“假死”

在一次例行的主备切换测试中,我们搭建了如下的测试环境:

  • 原主节点:Pod IP 为 10.0.10.129。在 09:33 左右被手动 kill 模拟故障,重建后的新 Pod IP 为 10.0.10.98
  • 副本一:Pod IP 为 10.0.10.227,主备切换后的新主节点,同时也是本次抓包分析的机器。
  • 副本二:Pod IP 为 10.0.10.56

pod-before.png 图1: 切换前pod状态

pod-after.png 图2: 切换后pod状态

切换过程中的关键时间线日志如下:

  • 09:32:49:触发了新主的切换操作。
12026-03-18T15:48:05Z	INFO	SQLServer	Setting replica to SECONDARY role...
22026-03-19T01:32:49Z	INFO	HA	Cluster has no leader, attempt to take the leader
32026-03-19T01:32:49Z	INFO	SQLServer	Replica is now PRIMARY
42026-03-19T01:32:49Z	INFO	HA	Take the leader success!
52026-03-19T01:32:57Z	INFO	HA	This member is Cluster's leader
62026-03-19T01:32:57Z	DEBUG	HA	Refresh leader ttl
72026-03-19T01:33:57Z	INFO	HA	This member is Cluster's leader
82026-03-19T01:33:57Z	DEBUG	HA	Refresh leader ttl
92026-03-19T01:34:57Z	INFO	HA	This member is Cluster's leader
102026-03-19T01:34:57Z	DEBUG	HA	Refresh leader ttl
112026-03-19T01:35:57Z	INFO	HA	This member is Cluster's leader
122026-03-19T01:35:57Z	DEBUG	HA	Refresh leader ttl
13
  • 09:33:02:老节点(IP 10.0.10.129)被正式关闭。
  • 09:48:05:原主节点重建后POD(IP 10.0.10.98)以备节点身份重新加入集群。

异常现象:从 09:33:02 开始,原有连接的 TCP 流量仿佛掉入了黑洞,主备之间的同步彻底中断。SQL Server 的日志中只看得到同步中断,但没有任何连接异常的报错。系统长时间无法自动恢复,最终耗时约 15 分钟,旧主节点(Pod 10.0.10.98)才以备库的身份重新加入集群。

1[HADR TRANSPORT] AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] Setting Reconnect Delay to 0 s
2[HADR TRANSPORT] LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in 
3CHadrTransportReplica::Reset called from function [CHadrTransportReplica::ReconnectTask], primary = 0, 
4primaryConnector = 1[HADR TRANSPORT] LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in 
5CHadrConfigState::ChangeState with session ID A23BA718-9B73-4588-893B-0F18C0275526 change from 
6HadrSessionConfig_ConfigRequest to HadrSessionConfig_ConfigRequest - function [CHadrSession::Reset][HADR TRANSPORT] 
7AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] 
8Session:[1DF067D5-EB9C-4A15-9216-A025B534D66F] CHadrTransportReplica State change from HadrSession_Timeout to 
9HadrSession_Configuring - function [CHadrTransportReplica::Reset_Deregistered][HADR TRANSPORT] AR[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33], 
10Seesion:[1DF067D5-EB9C-4A15-9216-A025B534D66F] Queue Timeout (10) from [CHadrTransportReplica::Reset_Deregistered][HADR TRANSPORT] 
11LOCAL AR:[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrConfigState::ChangeState 
12with session ID 1DF067D5-EB9C-4A15-9216-A025B534D66F change from HadrSessionConfig_ConfigRequest to 
13HadrSessionConfig_WaitingSynAck - function [CHadrSession::GenerateConfigMessage][HADR TRANSPORT] LOCAL AR:
14[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] in CHadrSession::GenerateConfigMessage 
15with session ID 1DF067D5-EB9C-4A15-9216-A025B534D66F Generate configure message(1) with viersion(1)[HADR TRANSPORT] AR
16[16FD79D5-4819-43D0-B534-C5132DDFF886]->[20D27FE9-032A-43E7-969F-78A9976AEA33] Transport is not in a connected state, 
17unable to send packet2026-03-18 10:22:48.60 spid27s     Using 'dbghelp.dll' version '4.0.5'
18

二、 抓包排查:经典的 TCP 指数退避重传

为了弄清流量去向,我们对通信链路(10.0.10.227:38101 -> 10.0.10.129:5022,5022 为 SQL Server Always On 的端点端口)进行了抓包分析。

关键报文序列如下:

109:32:54.234 129:5022  227:38101 ACK=40464 [正常确认]
209:33:02.246 227:38101  129:5022 seq=40464:52704 len=12240 [发送数据]
309:33:02.247 129:5022  227:38101 ACK=52704 [确认收到,这是129最后的遗言]
4
5# 之后 227 继续发送新数据,但再也等不到 129  ACK
609:33:13.258 227:38101  129:5022 seq=52704:64944 len=12240 [发送新数据]
709:33:13.468 227:38101  129:5022 seq=52704:61652 len=8948  [重传1,间隔0.4s]
809:33:13.876 227:38101  129:5022 seq=52704:61652 len=8948  [重传2,间隔0.9s]
909:33:14.740 227:38101  129:5022 seq=52704:61652 len=8948  [重传3,间隔1.7s]
10...
1109:36:46.964 227:38101  129:5022 seq=52704:61652 len=8948  [持续重传,间隔已达106.5s]
12

分析结论:这是一个非常典型的 TCP 重传机制 现象。当发送端未收到 ACK 时,会触发 RTO(超时重传),并且重传间隔呈指数级退避(0.4s → 0.9s → 1.7s → 3.3s ... 106.5s)。在 Linux 默认配置下,会重传 15 次,总耗时约 15 分钟才会彻底放弃并断开连接。

三、 剖析:为何应用层(SQL Server)未触发超时?

很多同学可能会问:SQL Server HADR(高可用灾备)机制默认有 10 秒的 SESSION_TIMEOUT,为什么没有生效?

陷阱在于:SESSION_TIMEOUT 主要用于检测心跳 ping 的丢失。 在当前场景下:

  1. 操作系统传输层的 TCP 连接仍然处于 ESTABLISHED 状态,内核正在努力重传,并没有向应用层抛出连接断开的 Error。
  2. SQL Server 认为“底层 TCP 连接还活着,只是数据传输慢”,因此一直在挂起等待。
  3. 最终结果:应用层无限期等待,直到 15 分钟后 TCP 栈最终放弃。这就造成了所谓的“幽灵连接”。
1TCP 层:持续重传,连接未断开(ESTABLISHED)
2    
3SQL Server 层:认为连接还活着(TCP 未报告错误)
4    
5HADR 层:等待数据同步完成,未触发 SESSION_TIMEOUT
6    
7结果:无限等待,直到 TCP 最终放弃(15 次重传  15-20 分钟)
8

四、 根因定位与云网络背景

结合业内经验与本次故障特征,我们梳理出了完整的根因链条:

  1. 云网络环境的特殊性:在部分云厂商的基础网络架构中,为了抵抗网络抖动,底层网络设备会尽量避免向对端发送 RST 报文。但在容器化 IP 频繁变更(Pod 重建)的场景下,这会导致对端无法感知连接断裂,从而引发 15 分钟黑洞问题。

oci-tcp_retries2.png 图1: OKE环境宿主机tcp_retries2参数默认值

  1. 应用关闭动作粗暴:最根本的原因是,在执行 Delete Pod 时,MSSQL 进程没有进行优雅关闭(Graceful Shutdown)。进程被暴力 Kill,操作系统和应用都来不及向对端发送 FINRST 报文主动断开连接,对端(案例中的227 节点)毫不知情,傻傻地等待并不断重传。

五、 解决之道:双管齐下

针对上述根因,我们采取了“应用层主动阻断 + 系统层被动兜底”的综合解决方案。

1. 应用层修复:实现优雅关闭 (主动解法)

最彻底的解法是让连接主动断开。我们为 MSSQL Addon 引入了 Graceful Shutdown 的 Patch。在接收到停止信号时,保证应用能主动执行 close() 释放 TCP 连接,向对端发送 FIN/RST

2. 系统层兜底:优化内核参数 tcp_retries2 (被动防御)

[!IMPORTANT] 在 Kubernetes 这样的容器化环境中,Pod 的生命周期是动态的,其 IP 地址随时可能因为调度、升级或故障而改变。虽然实现优雅关闭(Graceful Shutdown)是应用开发的最佳实践,但在很多突发场景下,例如 OOMKilled、节点故障(Node Failure)或进程直接崩溃(Crash),应用根本没有机会执行优雅关闭。这就导致了连接的另一端无法感知对端已经消失,从而陷入长时间的等待,形成网络黑洞。因此,仅仅依赖应用层的优雅关闭是不够的,必须在系统层面建立一道“被动防御”的兜底机制。

对于使用长连接访问的应用来说(默认使用都是 tcp 长连接,无论 HTTP/2 还是 HTTP/1),在没有设置合适请求 timeout 参数的情况下可能会出现 15mins 的超时问题,为了应对网络脑裂或机器突然断电等无法执行优雅关闭的极端物理故障,我们需要缩短操作系统的 TCP 放弃时间。[4]

Linux 内核的 net.ipv4.tcp_retries2 参数[5]控制着处于 ESTABLISHED(已建立连接)状态下的数据传输失败重传行为。需要澄清的一个常见误区是:tcp_retries2 并不是简单的绝对重传“次数”,它其实决定了内核计算总超时时间的边界。

**TCP 超时计算逻辑原理:**内核采用指数退避算法计算重传超时时间(RTO,初始为 1s,受限于 TCP_RTO_MIN 200ms 和 TCP_RTO_MAX 120s),计算公式大致如下:

基于上述算法:

  • 默认值 15:计算得出的总超时时间约为 924.6 秒(15.4 分钟)。在这 15 分钟内,应用层将毫无察觉地死等,这也是引发 15 分钟黑洞的直接原因。
  • 优化值 8:计算得出的总超时时间大幅降至 约 25.5 秒

我们将 tcp_retries2=8 的配置项纳入了基础设施的交付流程中(可通过初始化节点 sysctl 或 Pod initContainer 注入)。这样即使遇到连接黑洞,TCP 层最多等待约 25 秒就会强制断开连接,并向上层应用抛出 ETIMEDOUT 异常,从而让应用层的 Failover 机制迅速介入。

六、 验证与后续改进

在 OCI 集群应用上述修复后,我们进行了场景验证和测试:

  • 测试场景:集群下发变配Ops,测试滚动重启过程中主备切换是否正常。
  • 测试结果:我们通过对比 tcp_retries2 参数在默认值(15)和优化值(8)下的表现,来验证修复效果。
对比项场景一:默认值 (tcp_retries2=15)场景二:优化后 (tcp_retries2=8)
恢复时长约 16 分钟2 分钟内
现象主备切换后,连接长时间中断,出现明显的“流量黑洞”,集群长时间无法恢复同步。主备切换后,连接迅速恢复,集群快速完成同步,有效解决了流量黑洞问题。
GIF 演示见表格下方见表格下方

20260323102810_rec_.gif

20260323101438_rec_-ezgif.com-cut.gif

后续改进与展望: 此次故障排查不仅解决了眼前的问题,也为我们提升系统整体的健壮性提供了宝贵的经验。后续,KubeBlocks团队将从以下几个方面深化改进:

  1. 全面推广优雅关闭(Graceful Shutdown)实践: 将优雅关闭作为所有有状态应用(包括但不限于 Redis、PostgreSQL 等数据库)开发和上线的强制性标准。确保应用在退出时能主动清理和释放资源,从根源上杜绝“幽灵连接”。
  2. 优化基础设施的交付流程: 将 tcp_retries2=8 等关键内核参数的优化,作为节点初始化的标准配置项,纳入了基础设施的交付流程中,确保集群环境的一致性和可靠性。
  3. 常态化混沌工程演练: 将此类网络分区、Pod 强制删除等故障场景纳入常态化的混沌工程演练平台。通过主动注入故障,持续性地检验和提升系统的弹性和自愈能力,变被动响应为主动防御。

Ref:

  1. KubeBlocks 官方网站
  2. KubeBlocks 官方博客 - SQLServer Addon
  3. KubeBlocks 官方博客 - SQLServer Always On AG 深度解析
  4. Kubernetes Controller高可用诡异的15mins超时
  5. net.ipv4.tcp_retries2深度解析与优化指南

记一次 OKE 集群上的 TCP 流量黑洞排查与解决全过程》 是转载文章,点击查看原文


相关推荐


Spring Boot 牵手Spring AI,玩转DeepSeek大模型
小码哥_常2026/4/1

Spring Boot 牵手Spring AI,玩转DeepSeek大模型 引言:开启 AI 集成之旅 在当今这个 AI 技术迅猛发展的时代,你是否想过如何将强大的大模型融入到我们日常的 Java 开发中,为应用赋予智能交互的能力呢?今天,我们就来探索如何通过 Spring Boot 集成 Spring AI,进而调用 DeepSeek 大模型,为你的项目注入全新的 AI 活力。这不仅能让你深入了解 AI 与后端开发融合的前沿技术,还能为你在实际项目中运用 AI 技术提供宝贵的经验 。接下来,就


OpenClaw实战|从识图到公众号内容自动化,我跑通了完整链路
后端小肥肠2026/3/23

大家好,我是小肥肠。今天这篇文章,想跟大家分享一下我最近刚跑通两个skill:让 OpenClaw图像理解和 飞书 文章一键转存至公众号草稿箱。 1. 前言 最近我在慢慢做一件事:把之前在 Coze 里折腾过的插件和工作流,陆续迁到 OpenClaw + Skill 这一套上。上周末我主要搞定了两件事,都已经不是“纸上谈兵”,是真的跑起来了。 第一件:把图像理解插件迁移成 OpenClaw Skill 我把之前做的图像理解Coze插件,迁成了一个独立的 xfc-img-understand sk


墨梅博客 1.9.0 发布与 LeanCloud 停服应对 | 2026 年第 11 周草梅周报
草梅友仁2026/3/15

本文在草梅友仁的博客发布和更新,并在多个平台同步发布。如有更新,以博客上的版本为准。您也可以通过文末的 原文链接 查看最新版本。 前言 欢迎来到草梅周报!这是一个由草梅友仁基于 AI 整理的周报,旨在为您提供最新的博客更新、GitHub 动态、个人动态和其他周刊文章推荐等内容。 开源动态 本周墨梅博客的开发依旧在稳步进行中。 您可以前往 Demo 站试用:demo.momei.app/ 您可以通过邮箱 admin@example.com,密码 momei123456 登录演示用管理员账号


ai-agent工程师指南
哈里谢顿2026/3/7

一些基本概念 1 Zero-shot & Few-shot 是什么? 1. Zero-shot(零样本) 不给例子,直接让模型做。 不提供任何参考样例 只告诉模型任务是什么 完全靠模型本身能力去理解、推理 例子 把下面句子分类成积极 / 消极:这部电影太好看了! 这就是 zero-shot。 2. Few-shot(少样本 / 小样本) 给几个例子,再让模型做。 给 1~10 个左右的示例 告诉模型:我要你像这样输出 模型照着格式、逻辑去做 例子 分类:今天心情很好 → 积极分类


CSDN创作变现活动!社区镜像或使用视频教程分别单个最高得 80 元,收益上不封顶!
CSDN官方博客2026/2/27

CSDN AI 社区是聚焦 AI 技术产业落地的开发者服务平台(官方入口),核心为创作者搭建技术价值转化桥梁,AI社区涵盖: 镜像市场(社区镜像)、算力市场等模块。 本次推出镜像创作激励活动,以下是方案活动规则、参与要求及激励政策,保障创作者权益与活动有序开展。 一、活动总则 活动时间: 2026年1月1日 - 2026年2月28日 现金奖励: 1、按照官方指定镜像任务创作,单个社区镜像奖励 30-80元现金 ,创作越多可获得现金奖


深度解析 JWT:从 RFC 原理到 NestJS 实战与架构权衡
NEXT062026/2/18

1. 引言 HTTP 协议本质上是无状态(Stateless)的。在早期的单体应用时代,为了识别用户身份,我们通常依赖 Session-Cookie 机制:服务端在内存或数据库中存储 Session 数据,客户端浏览器通过 Cookie 携带 Session ID。 然而,随着微服务架构和分布式系统的兴起,这种有状态(Stateful)的机制暴露出了明显的弊端:Session 数据需要在集群节点间同步(Session Sticky 或 Session Replication),这极大地限制了系统


RTOS核心三剑客:任务、信号量与队列深度解析
牛逍遥2026/2/9

RTOS核心三剑客:任务、信号量与队列深度解析 一、裸机编程的瓶颈:为什么需要RTOS? 在嵌入式开发中,裸机程序通常采用**超级循环(Super Loop)**结构: void main() { while(1) { read_sensors();// 读取传感器 process_data();// 处理数据 update_display();// 刷新显示 handle_uart();// 串口通信 check_safety();// 安全检测 } } 裸机编程的致命缺陷: 阻塞操作导致响


Objective-C手机验证码短信接口调用流程:创建请求对象并设置报文体
2601_949146532026/2/1

在iOS原生开发中,基于Objective-C对接手机验证码短信接口是账号安全、用户验证场景的核心需求,但新手常因请求对象创建不规范、报文体参数编码错误、请求头配置缺失等问题,导致接口返回405(API ID错误)、407(内容含敏感字符)等异常。本文聚焦objective-c手机验证码短信接口的核心调用流程,拆解创建NSURLRequest请求对象、配置请求头、设置报文体的完整逻辑,提供可直接复用的实战代码,解决参数编码、状态码解析等痛点,帮助开发者高效完成接口对接。 一、Objective


没显卡也能玩!Ollama 本地大模型保姆级入门指南
字节逆旅2026/1/22

如果你想在自己电脑上跑 AI,又不希望数据被大厂拿走,Ollama 绝对是目前最香的选择。不用配复杂的 Python 环境,不用求爷爷告奶奶找 API Key,只要一键安装,就能实现“大模型自由”。不过我的电脑很早就有了python环境了,忘记啥时候安装的,虽然在python方面还是个菜鸟。 1. 怎么安装 直接去 Ollama 官网 下载。有1个多G,先有个心理准备。 第一步: 安装完后,它会躲在右下角任务栏。 第二步: 打开终端(CMD 或 PowerShell),输入下面的命令。这


一个致力于为 C# 程序员提供更佳的编码体验和效率的 Visual Studio 扩展插件
追逐时光者2026/1/14

前言 今天大姚给大家分享一个致力于为 C# 程序员提供更佳的编码体验和效率的 Visual Studio 扩展插件:Codist。 Codist 插件介绍 Codist 是一个使用 .NET 编写、开源免费的 Visual Studio 扩展插件,致力于为 C# 程序员提供更好的编程体验和生产效率。它不仅强化了语法高亮、快速信息提示、导航栏、滚动条和显示质量,还集成了自动版本号更新、括号自动补全、支持高级编辑功能的智能工具栏、代码分析等功能。 支持 Visual Studio 版本 Visu

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客