tcp_nodelay, tcp_nopush 和 sendfile
tcp_nodelay
在 TCP 发展早期,工程师需要面对流量冲突和堵塞的问题,其中涌现了大批的解决方案,其中之一是由 John Nagle 提出的算法。
Nagle 的算法旨在防止通讯被大量的小包淹没。该理论不涉及全尺寸 tcp 包(最大报文长度,简称 MSS)的处理。只针对比 MSS 小的包,只有当接收方成功地将以前的包(ACK)的所有确认发送回来时,这些包才会被发送。在等待期间,发送方可以缓冲更多的数据之后再发送。
if package.size >= MSS.size
send(package)
elsif acks.all_received?
send(package)
else
# acumulate data
end
与此同时,诞生了另一个理论,延时 ACK
在 TCP 通讯中,在发送数据后,需要接收回应包(ACK)来确认数据被成功传达。
延时 ACK 旨在解决线路被大量的 ACK 包拥堵的状况。为了减少 ACK 包的数量,接收者等待需要回传的数据加上 ACK 包回传给发送方,如果没有数据需要回传,必须在至少每 2 个 MSS,或每 200 至 500 毫秒内发送 ACK(以防我们不再收到包)。
if packages.any?
send
elsif last_ack_send_more_than_2MSS_ago? || 200_ms_timer.finished?
send
else
# wait
end
正如你可能在一开始就注意到的那样 —— 这可能会导致在持久连接上的一些暂时的死锁。让我们重现它!
假设:
-
初始拥塞窗口等于 2。拥塞窗口是另一个 TCP 机制的一部分,称为慢启动。细节现在并不重要,只要记住它限制了一次可以发送多少个包。在第一次往返中,我们可以发送 2 个 MSS 包。在第二次发送中:4 个 MSS 包,第三次发送中:8 个MSS,依此类推。
-
4 个已缓存的等待发送的数据包:A, B, C, D
-
A, B, C是 MSS 包
-
D 是一个小包
场景:
-
由于是初始的拥塞窗口,发送端被允许传送两个包:A 和 B
-
接收端在成功获得这两个包之后,发送一个 ACK
-
发件端发送 C 包。然而,Nagle 却阻止它发送 D 包(包长度太小,等待 C 的ACK)
-
在接收端,延迟 ACK 使他无法发送 ACK(每隔 2 个包或每隔 200 毫秒发送一次)
-
在 200ms 之后,接收器发送 C 包的 ACK
-
发送端收到 ACK 并发送 D 包
-
在这个数据交换过程中,由于 Nagel 和延迟 ACK 之间的死锁,引入了 200ms 的延迟。
Nagle 算法是当时真正的救世主,而且目前仍然具有极大的价值。但在大多数情况下,我们不会在我们的网站上使用它,因此可以通过添加 TCP_NODELAY 标志来安全地关闭它。
tcp_nodelay on; # sets TCP_NODELAY flag, used on keep-alive connections
享受这200ms提速吧!
sendfile
正常来说,当要发送一个文件时需要下面的步骤:
-
malloc(3) – 分配一个本地缓冲区,储存对象数据。
-
read(2) – 检索和复制对象到本地缓冲区。
-
write(2) – 从本地缓冲区复制对象到 socket 缓冲区。
-
这涉及到两个上下文切换(读,写),并使相同对象的第二个副本成为不必要的。正如你所看到的,这不是最佳的方式。值得庆幸的是还有另一个系统调用,提升了发送文件(的效率),它被称为:sendfile(2)(想不到吧!居然是这名字)。这个调用在文件 cache 中检索一个对象,并传递指针(不需要复制整个对象),直接传递到 socket 描述符,Netflix 表示,使用 sendfile(2) 将网络吞吐量从 6Gbps 提高到了 30Gbps。
然而,sendfile(2) 有一些注意事项:
-
不可用于 UNIX sockets(例如:当通过你的上游服务器发送静态文件时)
-
能否执行不同的操作,取决于操作系统
-
在 nginx 中打开它
sendfile on;
tcp_nopush
tcp_nopush 与 tcp_nodelay 相反。不是为了尽可能快地推送数据包,它的目标是一次性优化数据的发送量。
在发送给客户端之前,它将强制等待包达到最大长度(MSS)。而且这个指令只有在 sendfile 开启时才起作用。
sendfile on;
tcp_nopush on;
看起来 tcp_nopush 和 tcp_nodelay 是互斥的。但是,如果所有 3 个指令都开启了,nginx 会:
-
确保数据包在发送给客户之前是已满的
-
对于最后一个数据包,tcp_nopush 将被删除 —— 允许 TCP 立即发送,没有 200ms 的延迟
-
在 nginx 和上游服务器之间 keep-alive
upstream backend {
# The number of idle keepalive connections to an upstream server that remain open for each worker process
keepalive 16;
}
server {
location /http/ {
proxy_pass http://http_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
来源:oschina
链接:https://my.oschina.net/u/3084334/blog/1615586