IT运维中有一类最折磨人的故障:网络带宽没满、服务器CPU没高、数据库响应正常,但用户就是反馈系统卡、传文件慢、视频会议掉帧。
这时候,很多工程师会陷入"循环排查"的噩梦——一遍遍检查各个指标,每个组件看起来都正常,但系统就是慢。
根本原因往往藏在一个被忽略的地方:TCP零窗口(TCP Zero Window)。
什么是TCP零窗口?
TCP是有流量控制机制的。通信双方在每个ACK报文里,都会携带一个"接收窗口大小(Window Size)"字段,告诉对方:"我的接收缓冲区还剩多少空间,你最多可以再发多少数据。"
当接收方的缓冲区被填满、处理不过来的时候,它会在ACK里把窗口大小设置为0,发出一个TCP Zero Window通告,意思是:
"停一下,我处理不过来了,等我告诉你再发。"
发送方收到这个通告后,必须停止发送数据,只能定时发送探测包(ZWP,Zero Window Probe),等对方窗口恢复。
这个等待期间,从外部看:
- 网络链路是空闲的(没有数据在传输)
-
- 服务器没有收到数据(但它在等窗口恢复)
-
- 用户感知:传输卡住了,系统无响应
一个真实的故障场景
某企业内网的文件服务器,员工反馈上传大文件经常卡住,但同事之间直接传文件没有问题。
检查路径:
- 网络链路正常,带宽没有跑满
-
- 文件服务器CPU和内存都正常
-
- 网络管理员查了交换机统计,没有错误帧和丢包
抓包之后,发现了问题:文件服务器每隔几秒就会向客户端发出TCP Zero Window通告,然后等待一段时间才恢复。恢复时间从几十毫秒到几百毫秒不等。
- 网络管理员查了交换机统计,没有错误帧和丢包
最终定位:文件服务器的TCP接收缓冲区设置过小,且同时有多个连接在上传,导致接收缓冲区频繁被填满。调大 net.core.rmem_max 和 net.ipv4.tcp_rmem 后,问题消失。
如何发现TCP零窗口问题?
方法一:用Wireshark直接分析
如果已经抓到了数据包,在Wireshark中过滤:
tcp.window_size == 0
或者直接用专家信息(Analyze → Expert Information),Wireshark会自动标注出所有零窗口事件,并显示持续时间。
要进一步分析影响范围,可以看时序图(Statistics → TCP Stream Graphs → Time Sequence):
- 正常情况:数据平滑发送
-
- 零窗口问题:会看到明显的"台阶"——数据停止传输一段时间后才恢复
方法二:用全流量分析工具持续监控
- 零窗口问题:会看到明显的"台阶"——数据停止传输一段时间后才恢复
如果是偶发性问题,或者不知道具体在哪条连接上,就需要在网络上持续采集,事后回溯。
专业的全流量分析工具(如AnaTraf)会实时统计每条TCP会话的零窗口事件次数、持续时间,并在告警中标出异常会话。排查时只需要筛选"零窗口事件频繁"的会话,立刻定位到问题连接,然后导出PCAP做进一步分析。
这比每次出问题才临时抓包要高效得多——因为很多TCP零窗口问题是瞬时的,故障已经恢复之后再抓就抓不到了。
TCP零窗口的常见根因
出现零窗口,意味着接收方的处理速度跟不上发送方的发送速度,根因通常是以下几类:
1. 应用层处理慢
接收缓冲区里的数据来不及被应用程序消费。
- 常见于:数据库、文件服务器、日志服务器在高并发时应用处理瓶颈
-
- 排查方式:对比业务高峰期和低峰期的零窗口频率
2. TCP缓冲区设置过小
操作系统默认的TCP接收缓冲区在高带宽延迟积(BDP)网络下会成为瓶颈。
- 排查方式:对比业务高峰期和低峰期的零窗口频率
在Linux上查看当前设置:
- ```bash
- cat /proc/sys/net/ipv4/tcp_rmem
输出格式:最小值 默认值 最大值(单位:字节)
- ```
- 临时调大(重启后失效):
- ```bash
- sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
- sysctl -w net.core.rmem_max=16777216
- ```
- 永久生效(写入/etc/sysctl.conf):
- ```
- net.core.rmem_max = 16777216
- net.ipv4.tcp_rmem = 4096 87380 16777216
- ```
3. 内存压力
当服务器内存紧张时,Linux内核会主动收缩TCP缓冲区,触发零窗口。
检查:
# 查看内存相关的TCP计数器
netstat -s | grep -i window
ss -m # 查看连接的内存使用
4. NIC驱动或虚拟化层问题
某些虚拟机网卡驱动在高负载下会出现接收端处理积压,导致TCP层面反压。
- 检查ethtool -S eth0里的rx_drops和rx_missed_errors
零窗口 vs 拥塞控制:别搞混
有工程师会把零窗口和TCP拥塞窗口(CWND)混淆,两者本质不同:
| TCP零窗口 | TCP拥塞控制 | |
|---|---|---|
| 触发方 | 接收方(接收缓冲区满) | 发送方(推测网络拥塞) |
| 表现 | ACK里Window Size = 0 | 慢启动、指数退避 |
| 原因 | 接收端处理慢 | 网络丢包或延迟过大 |
| 排查重点 | 接收端CPU/内存/应用处理能力 | 网络链路丢包率、RTT |
当你看到传输卡住时,先确认是零窗口还是拥塞——这决定了你下一步该看服务器还是看网络。
小结
TCP零窗口是一类典型的"看不出来但影响很大"的性能问题。带宽监控、APM应用监控都很难发现它,因为它发生在TCP协议栈层面,既不是网络故障,也不是应用代码bug。
排查步骤总结:
- 抓包:用Wireshark过滤
tcp.window_size == 0,确认是否存在零窗口 -
- 定位:找出是哪端(客户端还是服务端)在发零窗口通告
-
- 关联:看零窗口发生时,对应端的CPU、内存、应用负载状态
-
- 调参:优先调整TCP缓冲区大小,其次排查应用处理性能
遇到"网络没问题但业务慢"这类奇怪故障,先看看有没有零窗口事件。很多时候,问题就在这里。
- 调参:优先调整TCP缓冲区大小,其次排查应用处理性能
你处理过类似的TCP零窗口问题吗?欢迎分享排查经历。