TCP 延迟分析

本文是对《延迟增加了多少?》一文的答案和分析。

总延迟应该是 900ms 左右。

Ping 的延迟指的是 RTT, Round Trip Time. 即一个包发过去,对方发一个包回来,总延迟是 200ms。一种误解是认为 ping 测得的延迟是 200ms,所以一个请求发过去是 200ms,响应发回来是 200ms,总延迟是 400ms。如果仔细想一想的话,我们在发送端测量延迟的时候,没有办法只测量一个包从发送端达到接收端的延迟。除非是让接收端在回复的时候记录收到包的时间?但是发送端和接收端的时钟可能不一致,如果精确测量的话,协议上就要依赖不同的机器时钟对齐。直接让总时间除以 2?这也意义不大,因为包去和回的路线不一定一样,延迟也不一定是一半一半。所以我们在讨论延迟的时候,都是默认 RTT

TCP 握手的延迟是 1 个 RTT,即 200ms。这里也有很多人会有一个误解,就是认为「TCP 是三次握手,所以握手带来的延迟是 1.5 RTT」。这个误解是因为教科书的 TCP 序列图太迷惑了,看起来像是握手需要发送 3 个包,之后才能发送数据。实际上,在握手阶段,第三个包 ACK 发出之后,发送端直接开始发送数据。即,客户端发送 SYN 建立握手,收到 SYN+ACK,消耗了 1 个 RTT;发送端随即发送 ACK,然后直接进入数据发送阶段,开始发送数据。所以握手阶段第三个 ACK 包是不会带来延迟的

很多教材的 TCP 握手序列图是这样
但其实应该是这样,在第三个 ACK 发送之后直接开始发送数据

上文题目提到,请求的大小是 16KiB,这里经常出现的误解是:「一个 Frame 的 MTU 是 1500bytes,减去 20 bytes IP header 和 20 bytes TCP header,一个包携带的实际数据是 1460 bytes,所以 16KiB 请求传输完成是 16KiB/1460 bytes 个 RTT。」这也是被教科书常用的序列图迷惑了,TCP 传输中,发送端并不是发送一个包,等待 ACK,发送下一个包……而是直接传输很多包,接收端可以直接用一个 ACK 去 ACK 多个包。发送端是「一组包一组包地发送」,而不是「一个包一个包地发送」。

错误的序列图:每一个数据都等待 ACK
实际上一直发送数据,直到达到 rwnd 或者 cwnd 的瓶颈

那么一下子可以发送多少个包呢?发送端可以一下子把 16KiB 的数据都发送完吗?这里涉及到 TCP 的拥塞控制了。为了避免过载接收端和中间链路上的路由器等设备,TCP 发送端发送出去但是未确认的数据会保持在接收端窗口 (rwnd)和拥塞控制窗口之内 (cwnd)。接收端的窗口一般不是瓶颈,所以这里忽略不讨论。cwnd 在 Linux 的初始值是 10,即 TCP 连接建立之后,会一下子发送 10 个 MSS 的数据,即 1460 bytes * 10 大约是 14.6KiB 的数据。然后会等待接收端发送回来 ACK,再开始下一轮数据的发送,并且逐渐增大 cwnd。对于响应,同理,这时候服务 B 的服务器变成了发送端,也是需要发送两轮数据。更详细的解释可以阅读这篇:《TCP 拥塞控制对数据延迟的影响》。

最后,来到了四次挥手。这里的误解是 TCP 断开连接 4 次挥手需要花费 2 个 RTT。实际上,断开连接的过程是不产生耗时的,因为在 TCP 断开连接之前,应用的请求已经发送完成,响应也已经收到,kernel 已经将收到的数据送给了用户态,用户态的程序已经继续运行了。即使应用调用 close(), 也是直接返回,kernel 再慢慢处理关闭连接的过程。所以断开连接是 kernel 来做的「收尾工作」,不会贡献请求处理的延迟。

综上,总结一下,实际耗时是 900ms,其中:

  • TCP 建立连接需要 1 个 RTT;
  • 如果是 HTTP 请求很小,响应也很小,那么请求和响应需要耗费 1 个 RTT;
  • 但是 HTTP 请求很大,因为 cwnd 的限制,需要额外耗费 1 个 RTT 来传输;
  • 同理,响应也很大,也需要额外花费 1 个 RTT;
  • 服务端程序处理需要花费 100ms,保持不变;

最后是 200ms * 4 个 RTT + 100ms = 900ms;

再留一个问题给读者:你知道应该如何优化,让这个请求耗费的延迟最小吗?(分析一下最小延迟是多少,一个 RTT 用来传输数据是比不可少的,服务端的 100ms 也无法节省,所以最小延迟是 300ms,如何从 900ms 优化到 300ms 呢?)

提示

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?
  7. 网工闯了什么祸?
  8. 网络中的环路和防环技术
  9. 延迟增加了多少?
  10. TCP 延迟分析
  11. 重新认识 TCP 的握手和挥手
  12. 重新认识 TCP 的握手和挥手:答案和解析
  13. TCP 下载速度为什么这么慢?
  14. TCP 长肥管道性能分析
  15. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。


TCP 延迟分析”已经有15条评论

  1. 保持长连接+连接复用/多路复用,减少 TCP 连接握手时间。保持长连接要记得关闭 空闲时 slow start。

  2. 之前做过类似的事,也是合规原因,不过跨机房是nginx和server之间,正好总结了一遍
    传输带来的时延为log_2(N/(10*MSS)+1)]+1.5+1个rtt,除了最后的1,其余由传输窗口和握手带来的延迟,最自然的想法就要通过连接复用消除了,但是nginx worker间不存在复用,qps不大,连接复用率也上不去,于是我们还直接调大了tcp_init_cwnd

    • 哈哈,实际上是 4 个,你的描述里面「请求两个」中的第二个,和 「响应两个」中的第一个,其实是同一个 RTT。这个地方比较绕,把整个过程用纸画箭头数一数就发现了。

      • 我画了一下,的确是这样的,请求端的第二轮数据包到达服务端后就可以进行处理了。

Leave a comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注