在 TCP 發展早期,工程師須要面對流量衝突和堵塞的問題,其中涌現了大批的解決方案,其中之一是由 John Nagle 提出的算法。node
Nagle 的算法旨在防止通信被大量的小包淹沒。該理論不涉及全尺寸 tcp 包(最大報文長度,簡稱 MSS)的處理。只針對比 MSS 小的包,只有當接收方成功地將之前的包(ACK)的全部確認發送回來時,這些包纔會被髮送。在等待期間,發送方能夠緩衝更多的數據以後再發送。nginx
if package.size >= MSS.size算法
send(package)緩存
elsif acks.all_received?安全
send(package)服務器
else網絡
# acumulate data併發
endsocket
與此同時,誕生了另外一個理論,延時 ACKtcp
在 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 "";
}
}