大型站點TCP/IP協議優化

JosenZHANG發表於2022-01-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的時間,在RFC 793中規定為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。

相關文章