由于Nagle算法和延迟ACK导致的TCP性能问题 - awokezhou/LinuxPage GitHub Wiki
概述
TCP当中的很多机制使得它运行的很好。比如快速重传,当有连续数据流传输的时候,TCP做的很好。但是如果发送一个数据块,然后停止并等待通信对端的应用层ack,这时会变得比较缓慢。就像一个水泵失去了它的动力——只要整个管道充满水,水泵就能正常工作和流动,但是如果只有少量的水而充满空气,水泵的叶轮就会因为没有大量的水来推送而抽动。
延迟ACK
延迟ACK意味着TCP不会立即响应任何接收到的单个分组(如果一下接收到两个分组,则会立刻响应两个ACK)。基于一个前提假设——接收应用程序可能会做出某种响应,所以如果你接收到一个单一的TCP分组,将会等待100~200ms。这种设计是因为考虑到不想在收到一个TCP分组后立刻发送一个空的ACK,而是在将要发出的数据包中捎带ACK,这种考虑是非常好的。
但是,如果应用程序不打算发送任何响应呢,是的,一点小的延迟又有什么关系呢?应用程序没有什么问题,它会继续执行,但是TCP栈会等待,这时就有了Nagle算法的问题。
Nagle算法
为了提高效率,通常希望发送完全大小的分组。Nagle算法考虑到,如果你有一个很小的数据要发送,但是却构不成一个完全大小的分组,并且你已经有一些没有确认的ACK等待发送,那么对不起,等着吧,直到应用程序给你充足的能够构成一个完全的分组,或者所有的ACK都已经处理。
Nagle算法其实是为了防止低智商的应用程序开发人员写出如下的糟糕代码
for (i=0; i<100000; i++)
write(socket, &buffer[i], 1);
一个例子
99900 bytes = 68 full-sized 1448-bytes 数据包, 加上1436 bytes额外的一个数据包
100000 bytes = 69 full-sized 1448-bytes 数据包,加上88 bytes额外的一个数据包
-
你发送68(偶数)个完全大小的数据包,Nagle算法将会把最后一个数据包1436bytes暂停发送
-
你的68个完全大小的数据包被接收端无延迟的立刻确认
-
Nagle算法获得ACK,释放最后一个1436bytes的数据包
-
接收者接收到这个1436bytes的数据包,延迟ACK
-
接收端应用程序准备了一个1byte的应答报文
-
延迟ACK结合这个1byte的应答报文和TCP的ACK,发送给你
我们来考虑100000bytes的情况
-
你发送69个完全数据包,Nagle算法暂停最后一个包88bytes
-
接收者响应任何一个数据包,一共68个包
-
接收者接收到第69个数据包
-
延迟ACK意味着接收者不会响应这个包直到:1.应用程序准备了一些响应数据;2.发送方发送了一个数据包
-
应用程序不会准备任何数据,因为100000bytes没有接收完全
-
你也不会发送最后一个包,因为Nagle规定只有接收到ack才发送
现在产生了一个僵局,性能问题出现:
-
Nagle算法不会发送最后一个数据,直到它接收到ack
-
延迟ACK不会发送ACK直到应用程序准备了响应数据
-
应用程序不会准备任何响应数据,直到它接收完全数据包
所以,在每个100000bytes的传输末尾,总会有这样一个尴尬的停顿。最终延迟ACK的定时器溢出,未完成的应答和ACK得到执行。在一个千兆网络上,这些200ms的停顿对应应用程序来说是毁灭性的。这些停顿让请求/应答形式的应用层协议每秒限制处理事务数不超过5个,原本它们可以每秒处理上千个事务。理想情况下,在千兆网络上每传输100000bytes所花费的时间应该在1ms之内。相反,因为它在固定的时机的停顿,每发送100000bytes的时间是201ms,慢了200倍