问题出现得很突然,日志倒是留得挺全。内网调 Redis 和 MySQL 都在 1ms 以内,可接口的 TTFB 稳定在三百多毫秒,抓包看到服务端响应发出去之后要等四十毫秒才发下一个包,一来一回就是好几次四十毫秒。
刚开始盯着「应用层没开 TCP_NODELAY,头部和正文分成两次 write 写出去」,折腾了半个多小时,排掉了。
排查方向其实有限,先把可能性排一排。Nagle 算法把小包攒着等前面的数据被确认,没确认就不发下一个。另一种情况是接收端默认延迟确认四十毫秒才回 ACK,两边一凑正好互相等。
不容易想到的是,中间多了一层代理,每跳都各自重演一次这个等待。
第一件事是抓包看每个包之间的时间差
tcpdump -i eth0 -nn -ttt host 10.0.4.8 and port 8080 -c 50 -w /tmp/lat.pcap
再往下确认 nginx 侧这两个开关的取值
grep -E 'tcp_nodelay|tcp_nopush' /etc/nginx/nginx.conf
接着打开立即发送并按跳验证效果
sed -i 's/^\s*tcp_nodelay.*/ tcp_nodelay on;/' /etc/nginx/nginx.conf && nginx -t && nginx -s reload
末了用 curl 分段看时间都花在哪
curl -o /dev/null -s -w 'connect %{time_connect} ttfb %{time_starttransfer}\n' http://10.0.4.8:8080/api
怎么算真的好了,看的是同一批现象。同一接口按十次取样,ttfb 稳定在五十毫秒以内,抓包里相邻包的时间差不再出现整四十毫秒的固定间隔。
容易忽略的是,内网小包场景收益明显,大文件传输反而要留着 Nagle 做聚合,别在全局配置里一刀切。线上能稳定复现的问题都不算难,难的是只在大流量下才出现的那种。
