记录一下那天晚上的排查过程。CentOS 7.9 的 Java 服务往客户端推数据,抓包里 ZeroWindow 和 Window Update 交替出现,接口偶发十几秒不返回,ss 里服务端 socket 的 Send-Q 一直堆着几百 K。
刚开始盯着「客户端是一次性读全量再处理的模型,处理期间不再 recv」,折腾了半个多小时,排掉了。
把日志和配置对完,能解释这个现象的基本就这几处。接收端应用读得太慢,内核接收缓冲区被填满,只能通告零窗口。也可能是接收缓冲区 net.ipv4.tcp_rmem 的上限太小,大响应发不出去。
少数情况下是中间有 NAT 设备,窗口更新包被丢弃或者延迟送达。
第一件事是在服务端看发送队列积压
ss -tnmi 'sport = :8080' | grep -E 'Send-Q|snd_wnd|rcv_space' | head
再往下抓包确认零窗口的方向
tcpdump -i eth0 -nn 'tcp port 8080 and tcp[14:2] = 0' -c 20
调大收发缓冲区上限
sysctl -w net.ipv4.tcp_rmem='4096 87380 16777216' net.ipv4.tcp_wmem='4096 65536 16777216'
复测时观察窗口是否还在归零
ss -tnmi state established '( dport = :8080 )' | head -5
别只看报错消失,验证要多做一步。抓包里 ZeroWindow 降到个位数,Send-Q 不再堆积,接口响应时间回到两百毫秒以内。
容易忽略的是,零窗口说明瓶颈在接收方,调服务端参数只能缓解;客户端把响应全读进内存再处理,遇上大包迟早还会重现。这类问题最好一次改到位,反复小改反而容易把原因搅混。
