TCP 优化最佳实践
Sep 13, 2021 22:00 · 2049 words · 5 minute read
1. TCP 背景知识:Nagle 算法与延迟确认
如今的互联网非常发达,全球化的 TCP/IP 网络跨越了万水千山传输 web 页面和各种文件。自互联网诞生以来已经发生了翻天覆地的变化,当时学术机构和政府大量使用 Telnet 和 NCP(Network Control Program)协议。随着流量、设备指数级增长,有效地管理流量越来越重要。
当 TCP/IP 协议栈从 80 年代早起成为主流后,有些设置就可用于优化流量来避免拥塞和数据丢失。但即使是现在,仍然很难搞清楚何时何地使用这些设置。本文将阐述一些常见的优化 TCP 设置的最佳实践,尤其是 Nagle 算法,TCP_NODELAY,延迟确认和 TCP_QUICKACK。
Nagle 算法(Nagle algorithm)
Nagle 算法,以发明者 Nagle 命名,是一种通过减少小包发送数量来提高 TCP 带宽利用率的算法,防止如果应用程序将数据递交至套接字的速度缓慢而节点传输大量小包。如果某个进程导致传输很多小包,可能会造成不必要的网络拥塞。当有效数据量小于 TCP 包头的数据,就是典型的小包。
这有点像用大卡车穿越整个城市来运一把椅子,除非椅子需要立刻被送达,不妨等整辆车塞满再运。这就是 Nagle 算法的原理。Nagle 算法用于将多个小包的数据合并至单个 TCP 帧中来优化数据传输,从而使 TCP 的数据负载增大。当有效负载只有 1 字节,TCP 头和 IP 头各占 20 字节,整个包就 41 字节,大量应用程序都能弄出这种包。要是你的环境被配置为立即发送数据,最终可能会发送一个 41 字节的数据包,其中只有一个字节的有效负载。。。
延迟确认(Delayed ACK)
延迟确认是另一种用于提高网络性能减少拥塞的技术。延迟确认减少 ACK 确认帧和协议开销。不用每次请求都发送 ACK,而是先等会,看看有没有顺风车可以搭。如果有其他包正在发送就捎上 ACK 一起;如果实在没有其他包,那就在超时(200 到 500 毫秒)后单独发送 ACK。延迟确认基本就是在赌 200 - 500 毫秒内有新包到来。但是在某些情况下,这项技术会导致应用层性能减弱。
当你决定采取哪些 TCP 优化方法时,必须要知晓它们对应用层的性能影响。
Nagel 算法和延迟确认是在同一时间发明出来的,但由于作者之间缺乏沟通与合作,这两个方案是相互冲突的。John Nagle 自己都说了:
“That still irks me. The real problem is not tinygram prevention. It’s ACK delays, and that stupid fixed timer. They both went into TCP around the same time, but independently. I did tinygram prevention (the Nagle algorithm) and Berkeley did delayed ACKs, both in the early 1980s. The combination of the two is awful.”
“这让我火大,真正的问题出在 ACK 延迟,还有傻逼的固定计时器。它们都是在同一时间进入 TCP 的,但相互独立。。。我做了 Nagle 算法但是伯克利搞了延迟确认,都在 80 年代早期。两者的结合不堪设想。”
2. Nagle 算法和延迟确认在 TCP/IP 网络中不能很好地结合
默认情况下,Nagle 算法和延迟确认在网络中广泛使用。Nagle 算法无时不刻只允许一个数据包在网络中主动传输,Nagle 算法和延迟确认之间的相互作用往往会抑制流量。因此在高频来往的环境中 Nagle 算法是不可取的。
举个栗子:延迟确认想一次尽可能多地发送数据,但是 Nagle 算法依赖一个 ACK 来发送数据。同时使用 Nagle 算法和延迟确认就会有问题因为延迟确认故意延迟发送 ACK 包而 Nagle 算法就等着接收 ACK!这就造成了 200 到 500 毫秒的随机停顿而非立即发送至接收端并传递至应用程序。
在数据需要被实时传输的情况下,比如按键输入和鼠标移动之类的用户交互,关掉 Nagle 算法能够带来更好的用户体验;但是关掉 Nagle 算法对物理距离本身造成的 RTT(round trip time) 时间没有什么帮助。
如果 ACK 很少(不占用大量带宽),那么延迟确认也没有什么帮助。在复杂的场景下就很难说何时使用 Nagle 算法,延迟确认和其他 TCP 优化选项。
3. TCP_NODELAY & TCP_QUICKACK
理解 Nagle 算法和延迟确认的作用非常重要。TCP_NODELAY socket 选项使得我们能够通过关掉 Nagle 算法来避免延迟,强制 socket 立即将缓冲区中的数据发送出去;如果要关闭延迟确认,则要使用 socket 的 TCP_QUICKACK 选项。
TCP_NODELAY
If set, disable the Nagle algorithm. This means that
segments are always sent as soon as possible, even if
there is only a small amount of data. When not set, data
is buffered until there is a sufficient amount to send
out, thereby avoiding the frequent sending of small
packets, which results in poor utilization of the network.
This option is overridden by TCP_CORK; however, setting
this option forces an explicit flush of pending output,
even if TCP_CORK is currently set.
TCP_QUICKACK (since Linux 2.4.4)
Enable quickack mode if set or disable quickack mode if
cleared. In quickack mode, acks are sent immediately,
rather than delayed if needed in accordance to normal TCP
operation. This flag is not permanent, it only enables a
switch to or from quickack mode. Subsequent operation of
the TCP protocol will once again enter/leave quickack mode
depending on internal protocol processing and factors such
as delayed ack timeouts occurring and data transfer. This
option should not be used in code intended to be portable.
如果应用层协议有多次握手,比如 SSL、Citrix 和 Telnet,Nagle 算法会导致性能下降,我们就要开启 TCP_NODELAY 来提高性能。
是否开启 TCP_NODELAY(禁用 Nagle)还是取决于我们的工作负载和服务的流量模式。通常局域网(LAN)比广域网(WAN)的流量拥塞情况好很多。像 SOAP、XMLRPC 和 HTTP 这样的非交互式流量,就没必要开启 TCP_NODELAY 来关闭 Nagle 算法。
下面这些情况最好开启 TCP_NODELAY:
- 和服务器频繁通讯(Citrix、网游协议)
- Telnet、SSL
相反如果看到大量的小包,那就关掉 TCP_NODELAY 来利用 Nagle 算法减少小包,甚至要做些实验来调试。
4. 修复 Nagle 算法和延迟确认导致的问题
- 开启 TCP_NODELAY 来禁用 Nagle 算法。
- 在代理服务器和负载均衡器侧调整。如果你的应用程序和环境偶尔会有高频来往的流量,这就很有用了,通过在负载均衡器层面动态切换 Nagle 算法和 TCP_NODELAY 甚至可以应对各种情况。
- 减小延迟确认计时。
在调整时也要同时观察网络中的流量状态来验证你的想法。