这不是关于理解TCP的所有内容或阅读TCP/IP插图。它是关于一点TCP知识是如何至关重要的。原因如下。
当我在递归中心时,我用Python编写了一个TCP堆栈(还写了如果用Python编写TCP堆栈会发生什么)。这是一次有趣的学习经历,我想仅此而已。
一年后,在工作中,有人在Slake上提到“嘿,我在向NSQ发布消息,每次需要40毫秒。”我断断续续地思考这个问题已经有一个星期了,但毫无进展。
稍微介绍一下背景知识:NSQ是一个队列,您可以将消息发送到该队列。发布消息的方式是在localhost上发出HTTP请求。向localhost发送HTTP请求确实不应该花费40毫秒。有些事出了很大的问题。NSQ守护进程没有处于高CPU负载下,它没有使用大量内存,这似乎不是垃圾收集暂停。帮助。
然后,我想起了一周前我读到的一篇文章--我们如何将每个帖子请求减少了200毫秒。在那篇文章中,他们谈到了为什么他们的每个POST请求都多花了200毫秒。那是..。怪怪的。以下是帖子中的关键段落。
Ruby的net::HTTP将POST请求分成两个TCP包-一个用于头,另一个用于正文。相比之下,Curl将两者结合在一起,如果它们可以放在一个包中的话。更糟糕的是,Net::HTTP没有在它打开的TCP套接字上设置TCP_NODELAY,因此它在发送第二个数据包之前等待对第一个数据包的确认。这种行为是纳格尔算法的结果。
移动到连接的另一端,HAProxy必须选择如何确认这两个数据包。在版本1.4.18(我们使用的版本)中,它选择使用TCP延迟确认。延迟确认与Nagle的算法相互作用严重,并导致请求暂停,直到服务器达到延迟确认超时。
申请:嗨!这是数据包1.HAProxy:<;静默,等待第二个数据包>;HAProxy:<;嗯,我最终还是会确认的,但是NBD>;应用程序:<;静默>;应用程序:<;嗯,我在等待ACK,可能是网络拥塞。<;HAProxy:好的,我很无聊。这是一个ACK应用程序:太棒了,这是第二个数据包!HAProxy:太好了。我们这里说完了
应用程序和HAProxy都处于被动-主动等待对方发送信息的阶段?这是额外的200ms。应用程序之所以这么做是因为Nagle的算法,而HAProxy是因为延迟的ACK。
据我所知,每个Linux系统默认都会发生延迟的ACK,所以这不是边缘情况,也不是异常情况--如果您以超过1个TCP数据包的形式发送数据,那么这种情况可能会发生在您身上。
所以我读了这篇文章,然后就忘了。但是我还在为我额外的40毫秒发愁,然后我就想起来了。
我想-那不会是我的问题,对吧?可以吗?我给我的团队发了一封电子邮件,说“我想我可能疯了,但这可能是TCP的问题”。
快速侧栏-我刚刚通过@alicemazzy的这条很棒的tweet读到了John Nagle(Nagle的算法)对Hacker News的这条评论。
真正的问题是ACK延迟。200ms的“ACK Delay”计时器是一个糟糕的主意,伯克利的一些人在1985年左右陷入了BSD,因为他们没有真正理解这个问题。延迟的ACK打赌在200ms内会有来自应用程序级别的回复。TCP继续使用延迟的ACK,即使它每次都会输掉这个赌注。
他接着评论说,ACK很小而且便宜,而且延迟ACK在实践中引起的问题可能比它们解决的问题严重得多。
我过去常常认为TCP是非常低级的,我不需要理解它。这几乎是真的!但在现实生活中,有时您会有一个bug,而这个bug是由于TCP算法中的某些东西造成的。因此,了解TCP是很重要的。(正如我们经常在本博客上讨论的那样,事实证明,这在很多事情上都是正确的,比如系统调用和操作系统:):)
这种延迟的ACKS/TCP_NODELAY交互特别糟糕-它可能会影响任何使用任何编程语言编写发出HTTP请求的代码的人。您不必是系统编程向导就能遇到这种情况。稍微了解一下TCP是如何工作的确实帮助我解决了这个问题,并认识到博客文章描述的东西也可能是我的问题。不过,我也用了strace。永不停歇。