大型站點TCP/IP協議優化

  • 2022 年 1 月 12 日
  • 筆記

作為一個DAU上百萬或千萬的站點,不僅僅需要做好網站應用程序、數據庫的優化,還應從TCP/IP協議層去進行相關的優化;

在我的工作中,曾使用到了以下的幾種基本的優化方式:

增大最大連接數

在Linux系統里,所有的網絡連接都是通過文件描述符(file descriptor)來實現的,因此一個進程所能打開的文件描述符數量決定了這個進程所能創建的最大連接數;

由於Linux系統對進程的文件描述符數量限制是1024;對於大規模的分佈式站點來說,這樣的連接數限制是遠遠不夠的,建議適當增大該值:

首先在Linux中查詢文件描述符數量限制:

ulimit -n

默認情況下會顯示1024,然後編輯 /etc/security/limits.conf 文件,加入下面兩句:

* soft nofile 10000
* hard nofile 10000

重啟系統之後,再使用ulimit -n查看得到的結果就是10000了。

減少TCP斷開連接時的TIME_WAIT時間

在TCP斷開連接的四次揮手結束階段,連接斷開的發起方會進入到TIME_WAIT狀態。

在你的Linux服務器上執行如下命令:

netstat -n | grep 'tcp'

在輸出的最後一列你會可能看到值為TIME_WAIT的行,這代表該TCP連接已經進入了TIME_WAIT狀態,進入到該狀態的連接是不會釋放的,默認情況下需要等待2MSL的時間(1MSL大致等於TTL衰減為0的時間,在RFC793中規定為2分鐘)。因此在斷開連接之後的2個2分鐘時間段內,連接會一直保持為TIME_WAIT並持有文件句柄不釋放,在大型站點中會導致服務器連接很快背耗盡,實際上對於現在的網速,等待2MSL(4分鐘)的時間過長,建議將該時間設置為30秒即可:

  • 編輯 /etc/sysctl.conf 文件,添加下面的行:
    net.ipv4.tcp_fin_timeout = 30
    net.ipv4.tcp_tw_recycle = 1
    net.ipv4.tcp_tw_reuse = 1
  • 執行 sudo /sbin/sysctl -p 命令,使修改立即生效。

禁用延遲確認

 在TCP協議中,延遲確認(Delayed ACK)是一把雙刃劍;在絕大多數情況下,延遲確認在服務器收到數據包之後,不需要對每個數據包理解響應ACK,而是將多個數據包的ACK響應合併為一個,從而提高了網絡傳輸的性能並降低了網絡的負載,然而在某些條件下,也會帶來負面影響;

影響主要有兩方面:

  • TCP協議在在RFC 896中引入了Nagle算法,該算法主要用於解決在TCP傳輸過程中的小包問題(small-packet problem),大概意思是發送方在發送少量的數據時,並非立刻發送,而是在需要發送的數據量累計到一定閾值(MSS:Maximum Segment Size)時才開始發送;維基百科給了一個形象的解釋:
    if there is new data to send then
        if the window size ≥ MSS and available data is ≥ MSS then
            send complete MSS segment now
        else
            if there is unconfirmed data still in the pipe then
                enqueue data in the buffer until an acknowledge is received
            else
                send data immediately
            end if
        end if
    end if

    注意上述中的「an acknowledge is received」,也就是說如果一個發送方正在使用Nagle算法來發送小規模數據,這些數據可能遲遲不會被發送出去,直到接收方響應了一個ACK,而接收方因為延遲確認的緣故,只會在延遲確認超時時間達到之後才會發送ACK給發送方;從而導致了發送數據的時間被拉長。

  •  在丟包嚴重的網絡環境中,延遲確認會使得傳輸的性能變得更為低下;
    例如發送方需要發送序列號為1、2、3、4、5、6、7、8、9的九個包到接收方,如果1、2、3三個包都發送成功了,第4、5、6、7四個包發送失敗,接收方在收到第8個包時,返回一個ACK seq=4的包給發送方,發送方重發第4個包,但接收方由於延遲確認的原因不會在收到第4個包時立即發送ACK seq=5的包,而是要等到延遲確認超時之後才響應,接下來發送方會重發第5個包,接收方又要等到延遲確認超時之後再響應ACK seq=6……以此類推,這會使得原本不太好的網絡傳輸雪上加霜。

正如上面所述,延遲確認是一把雙刃劍,在大多數環境下具有促進作用,因此關閉與否識情況而定,在某些情況下可以適當減小延遲確認超時時間。

啟用SACK

上面談到延遲確認帶來的兩方面影響中,在第二個方面里提到了丟包之後發送方需要等到接收方響應ACK之後,才會重發丟失的包;如接收到ACK 4時才重發第4個包,而後接收到ACK 5時重發第5個包,接收到ACK 6時重發第6個包……,如果丟包數量較大時,這個步驟會顯得格外的綿長。

SACK(Selective Acknowledgment)解決了這個問題,它使得接收方在收到第8個包之後響應的ACK seq=4這個包里,會把它已經收到的包序列號也寫入(這裡是8,代表第8個包已經收到);接收方在收到這個ACK包之後,就可以知道第1、2、3、8四個包發送成功,從而推斷出需要重發第4、5、6、7這四個包。

SACK是雙方面的,需要發送方和接收方同時啟用才能生效。

使用QUIC協議

TCP作為一個面向連接的安全可靠的傳輸協議,它也有很多天然的缺陷,例如上面談到的失敗重發問題,以及其他諸如建立/斷開連接需要三次握手四次揮手的問題、發送包時的隊頭阻塞問題,因此傳輸的性能並不夠好;Google提出了基於UDP協議的上層QUIC協議;它在建立/斷開連接時無需三次握手四次揮手,它採用RAID5算法緩解了TCP協議中丟包時的失敗重傳問題。

現在Google的Chrome瀏覽器、Microsoft的Edge瀏覽器都支持Quic協議,同時即將到來的HTTP/3也是建立在QUIC協議之上(RFC 9000)。

很多流行的Web服務器如騰訊的Caddy內置了對QUIC協議的支持,Nginx也推出了官方支持QUIC和HTTP/3的預覽版Nginx-quic。