什么是 TCP Nagle 算法?为什么会造成 40ms 延迟?
Nagle 算法的核心规则只有一条:连接上有未被确认的小包时,不再发新的小包,等 ACK 回来再把缓冲区里的数据攒一起发。目的是减少小包数量——Telnet 按一个键就产生一个 41 字节的包,其中 40 字节是 TCP+IP 头部,有效载荷只有 1 字节,带宽利用率不到 3%。1984 年 John Nagle 在 RFC 896 里提出这个方案,解决的就是交互式应用疯狂发小包导致的广域网拥塞。
算法默认开启(RFC 1122 推荐),通过 TCP_NODELAY 选项关闭。
追问
Nagle 和延迟 ACK 怎么会互相卡死?
Nagle 在发送端等 ACK,延迟 ACK 在接收端等更多数据再确认,两者同时启用就形成僵持:发送方写了一个小包,等 ACK;接收方收到后不马上回 ACK,等 40ms(Linux 默认)或 200ms(Windows 默认)看还有没有后续数据。典型场景是 write-write-read 模式:第一次写直接发出,第二次写被 Nagle 挡住,接收端延迟 ACK 等 40ms,发送端就卡在这 40ms 上。腾讯云有实际案例,营销平台 10% 的请求耗时稳定卡在 38-42ms,根因就是这对组合。
什么时候必须关掉 Nagle?
实时交互场景:游戏同步、远程桌面、WebSocket 推送。这些场景宁可多发几个小包也不能容忍额外延迟。Redis 3.x 的主从同步曾因 Nagle 导致从库延迟飙升,后来在源码里给同步 socket 加了 TCP_NODELAY 才解决。
Nagle 和 TCP_CORK 有什么区别?
Nagle 是"有小包没确认就不发",侧重减少小包数量;CORK 是"攒够一个 MSS 再发",侧重提高吞吐量。CORK 更激进——把数据一直攒到 MSS 或超时(通常 200ms)才放行,适合 HTTP 流水线这类一次要发大量数据的场景。Nginx 在发送响应头时用 CORK,等响应体也凑齐了一起发,减少系统调用次数。
怎么确认线上问题是 Nagle 引起的?
抓包看时序:发送端有小包发出后,超过 40ms 才收到 ACK 或才发下一个包,大概率是 Nagle + 延迟 ACK。也可以临时在客户端设 TCP_NODELAY 做对比——如果 40ms 档消失了就确认了。注意要区分是 Nagle 的问题还是单纯的网络延迟,对比开关前后的延迟分布比看绝对值更可靠。
写段代码
c// 禁用 Nagle 算法 int flag = 1; setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)); // Linux 上启用快速 ACK(接收端) setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &flag, sizeof(flag));