Nginx 教程(2):性能

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 "";

      }

    }

猜你喜欢

转载自my.oschina.net/u/3084334/blog/1615586
今日推荐