Nagle 算法:别为一个小字节占一辆车
1984 年,John Nagle 发现有人在 telnet 里敲一个字符,网络上就要跑一个完整的 40 字节 IP 头 + 32 字节 TCP 头 + 1 字节数据的包——效率 1.4%。他的解法:发送方把小包攒起来,满足任一条件才发:① 收到了所有在途数据的 ACK;② 攒够了一个 MSS(约 1460 字节)。规则一句话:同一时刻,最多只能有一个没被确认的小包在外面飘着。
延迟确认:对端的温柔一刀
几乎同时,接收方也有自己的优化:收到数据不立刻回 ACK,等一等——盼着应用层正好有响应数据,ACK 搭在数据包上捎回去(捎带确认);实在没得捎,最多等 40ms(Linux 的典型值)也必须回。单独看都没毛病:Nagle 省带宽,延迟确认省 ACK 包。
相遇即死锁:40ms 从哪来
场景:客户端写-读一问一答,双方都开默认选项
客户端 服务端
write(小数据) ────────→ 收到,想捎带 ACK 等响应
Nagle:上一个包没确认? 应用 40ms 后才慢慢生成响应
攒着不发 ←──── 无 ACK
……僵持,直到某一边破局
实际表现:每个请求多出 40ms 左右的莫名延迟诊断特征很典型:抓包看 RTT 正常、CPU 不忙、DB 很快,但接口就是稳定慢 40ms 上下——十有八九是它俩。Wireshark 里能看到 ACK 前有一段 40ms 的沉默。
解法:关 Nagle + 应用层自己攒
交互式、请求-响应式的服务(RPC、数据库驱动、游戏、IM)一律关闭 Nagle:
// Java
Socket socket = new Socket();
socket.setTcpNoDelay(true);
// Go
conn.SetNoDelay(true)
// MySQL 驱动、Redis 客户端大多默认已关;确认一下总没错
// 注意:关 Nagle 不等于放弃攒包,是把攒包的责任收归应用层:
// 反例:循环 100 次每次 write 8 字节 → 100 个小包
// 正解:拼成 800 字节的 byte[] 一次 write → 1 个包
// 延迟敏感 + 批量能力,两头都要什么时候可以留着 Nagle
大流量、非交互的场景反而受益:日志采集上报、指标推送这类高频小包遥测,对延迟不敏感、对包量敏感,Nagle 帮你省真实带宽。判断标准就一条:这条连接上,是人等结果,还是机器攒数据。人等的,关。下一篇从传输层抬头上岸,进入应用层:HTTP/1.1 攒了三十年的家底,和它天生的欠账。
评论 (0)