天天看點

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

在Linux上做網絡應用的性能優化時,一般都會對TCP相關的核心參數進行調節,特别是和緩沖、隊列有關的參數。網上搜到的文章會告訴你需要修改哪些參數,但我們經常是知其然而不知其是以然,每次照抄過來後,可能很快就忘記或混淆了它們的含義。本文嘗試總結TCP隊列緩沖相關的核心參數,從協定棧的角度梳理它們,希望可以更容易的了解和記憶。注意,本文内容均來源于參考文檔,沒有去讀相關的核心源碼做驗證,不能保證内容嚴謹正确。作為Java程式員沒讀過核心源碼是硬傷。

下面我以server端為視角,從 連接配接建立、 資料包接收 和 資料包發送 這3條路徑對參數進行歸類梳理。

一、連接配接建立

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

簡單看下連接配接的建立過程,用戶端向server發送SYN包,server回複SYN+ACK,同時将這個處于SYN_RECV狀态的連接配接儲存到半連接配接隊列。用戶端傳回ACK包完成三次握手,server将ESTABLISHED狀态的連接配接移入accept隊列,等待應用調用accept()。

可以看到建立連接配接涉及兩個隊列:

  • 半連接配接隊列,儲存SYN_RECV狀态的連接配接。隊列長度由net.ipv4.tcp_max_syn_backlog設定
  • accept隊列,儲存ESTABLISHED狀态的連接配接。隊列長度為min(net.core.somaxconn,backlog)。其中backlog是我們建立ServerSocket(intport,int backlog)時指定的參數,最終會傳遞給listen方法:

include int listen(int sockfd, int backlog);

如果我們設定的backlog大于net.core.somaxconn,accept隊列的長度将被設定為net.core.somaxconn

另外,為了應對SYNflooding(即用戶端隻發送SYN包發起握手而不回應ACK完成連接配接建立,填滿server端的半連接配接隊列,讓它無法處理正常的握手請求),Linux實作了一種稱為SYNcookie的機制,通過net.ipv4.tcp_syncookies控制,設定為1表示開啟。簡單說SYNcookie就是将連接配接資訊編碼在ISN(initialsequencenumber)中傳回給用戶端,這時server不需要将半連接配接儲存在隊列中,而是利用用戶端随後發來的ACK帶回的ISN還原連接配接資訊,以完成連接配接的建立,避免了半連接配接隊列被攻擊SYN包填滿。對于一去不複返的用戶端握手,不理它就是了。

二、資料包的接收

先看看接收資料包經過的路徑:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

資料包的接收,從下往上經過了三層:網卡驅動、系統核心空間,最後到使用者态空間的應用。Linux核心使用sk_buff(socketkernel buffers)資料結構描述一個資料包。當一個新的資料包到達,NIC(networkinterface controller)調用DMAengine,通過RingBuffer将資料包放置到核心記憶體區。RingBuffer的大小固定,它不包含實際的資料包,而是包含了指向sk_buff的描述符。當RingBuffer滿的時候,新來的資料包将給丢棄。一旦資料包被成功接收,NIC發起中斷,由核心的中斷處理程式将資料包傳遞給IP層。經過IP層的處理,資料包被放入隊列等待TCP層處理。每個資料包經過TCP層一系列複雜的步驟,更新TCP狀态機,最終到達recvBuffer,等待被應用接收處理。有一點需要注意,資料包到達recvBuffer,TCP就會回ACK确認,既TCP的ACK表示資料包已經被作業系統核心收到,但并不確定應用層一定收到資料(例如這個時候系統crash),是以一般建議應用協定層也要設計自己的确認機制。

上面就是一個相當簡化的資料包接收流程,讓我們逐層看看隊列緩沖有關的參數。

網卡Bonding模式

當主機有1個以上的網卡時,Linux會将多個網卡綁定為一個虛拟的bonded網絡接口,對TCP/IP而言隻存在一個bonded網卡。多網卡綁定一方面能夠提高網絡吞吐量,另一方面也可以增強網絡高可用。Linux支援7種Bonding模式:詳細的說明參考核心文檔LinuxEthernet Bonding Driver HOWTO。我們可以通過cat/proc/net/bonding/bond0檢視本機的Bonding模式:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

一般很少需要開發去設定網卡Bonding模式,自己實驗的話可以參考這篇文檔

Mode 0(balance-rr) Round-robin政策,這個模式具備負載均衡和容錯能力

Mode 1(active-backup) 主備政策,在綁定中隻有一個網卡被激活,其他處于備份狀态

Mode 2(balance-xor) XOR政策,通過源MAC位址與目的MAC位址做異或操作選擇slave網卡

Mode 3 (broadcast) 廣播,在所有的網卡上傳送所有的封包

Mode 4 (802.3ad) IEEE 802.3ad動态鍊路聚合。建立共享相同的速率和雙工模式的聚合組

Mode 5 (balance-tlb) Adaptive transmit loadbalancing

Mode 6 (balance-alb) Adaptive loadbalancing

網卡多隊列及中斷綁定

随着網絡的帶寬的不斷提升,單核CPU已經不能滿足網卡的需求,這時通過多隊列網卡驅動的支援,可以将每個隊列通過中斷綁定到不同的CPU核上,充分利用多核提升資料包的處理能力。

首先檢視網卡是否支援多隊列,使用lspci-vvv指令,找到Ethernetcontroller項:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

如果有MSI-X, Enable+ 并且Count > 1,則該網卡是多隊列網卡。

然後檢視是否打開了網卡多隊列。使用指令cat/proc/interrupts,如果看到eth0-TxRx-0表明多隊列支援已經打開:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

最後确認每個隊列是否綁定到不同的CPU。cat/proc/interrupts查詢到每個隊列的中斷号,對應的檔案/proc/irq/${IRQ_NUM}/smp_affinity為中斷号IRQ_NUM綁定的CPU核的情況。以十六進制表示,每一位代表一個CPU核:

(00000001)代表CPU0(00000010)代表CPU1(00000011)代表CPU0和CPU1

如果綁定的不均衡,可以手工設定,例如:

echo "1" > /proc/irq/99/smp_affinity echo "2" > /proc/irq/100/smp_affinity echo "4" > /proc/irq/101/smp_affinity echo "8" > /proc/irq/102/smp_affinity echo "10" > /proc/irq/103/smp_affinity echo "20" > /proc/irq/104/smp_affinity echo "40" > /proc/irq/105/smp_affinity echo "80" > /proc/irq/106/smp_affinity

RingBuffer

Ring Buffer位于NIC和IP層之間,是一個典型的FIFO(先進先出)環形隊列。RingBuffer沒有包含資料本身,而是包含了指向sk_buff(socketkernel buffers)的描述符。

可以使用ethtool-g eth0檢視目前RingBuffer的設定:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

上面的例子接收隊列為4096,傳輸隊列為256。可以通過ifconfig觀察接收和傳輸隊列的運作狀況:

RXerrors:收包總的錯誤數

RX dropped:表示資料包已經進入了RingBuffer,但是由于記憶體不夠等系統原因,導緻在拷貝到記憶體的過程中被丢棄。

RX overruns:overruns意味着資料包沒到RingBuffer就被網卡實體層給丢棄了,而CPU無法及時的進行中斷是造成RingBuffer滿的原因之一,例如中斷配置設定的不均勻。

當dropped數量持續增加,建議增大RingBuffer,使用ethtool-G進行設定。

InputPacket Queue(資料包接收隊列)

當接收資料包的速率大于核心TCP處理包的速率,資料包将會緩沖在TCP層之前的隊列中。接收隊列的長度由參數net.core.netdev_max_backlog設定。

recvBuffer

recv buffer是調節TCP性能的關鍵參數。BDP(Bandwidth-delayproduct,帶寬延遲積) 是網絡的帶寬和與RTT(roundtrip time)的乘積,BDP的含義是任意時刻處于在途未确認的最大資料量。RTT使用ping指令可以很容易的得到。為了達到最大的吞吐量,recvBuffer的設定應該大于BDP,即recvBuffer >= bandwidth * RTT。假設帶寬是100Mbps,RTT是100ms,那麼BDP的計算如下:

BDP = 100Mbps 100ms = (100 / 8) (100 / 1000) = 1.25MB

Linux在2.6.17以後增加了recvBuffer自動調節機制,recvbuffer的實際大小會自動在最小值和最大值之間浮動,以期找到性能和資源的平衡點,是以大多數情況下不建議将recvbuffer手工設定成固定值。

當net.ipv4.tcp_moderate_rcvbuf設定為1時,自動調節機制生效,每個TCP連接配接的recvBuffer由下面的3元數組指定:

net.ipv4.tcp_rmem =

最初recvbuffer被設定為,同時這個預設值會覆寫net.core.rmem_default的設定。随後recvbuffer根據實際情況在最大值和最小值之間動态調節。在緩沖的動态調優機制開啟的情況下,我們将net.ipv4.tcp_rmem的最大值設定為BDP。

當net.ipv4.tcp_moderate_rcvbuf被設定為0,或者設定了socket選項SO_RCVBUF,緩沖的動态調節機制被關閉。recvbuffer的預設值由net.core.rmem_default設定,但如果設定了net.ipv4.tcp_rmem,預設值則被覆寫。可以通過系統調用setsockopt()設定recvbuffer的最大值為net.core.rmem_max。在緩沖動态調節機制關閉的情況下,建議把緩沖的預設值設定為BDP。注意這裡還有一個細節,緩沖除了儲存接收的資料本身,還需要一部分空間儲存socket資料結構等額外資訊。是以上面讨論的recvbuffer最佳值僅僅等于BDP是不夠的,還需要考慮儲存socket等額外資訊的開銷。Linux根據參數net.ipv4.tcp_adv_win_scale計算額外開銷的大小:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

如果net.ipv4.tcp_adv_win_scale的值為1,則二分之一的緩沖空間用來做額外開銷,如果為2的話,則四分之一緩沖空間用來做額外開銷。是以recvbuffer的最佳值應該設定為:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

三、資料包的發送

發送資料包經過的路徑:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

和接收資料的路徑相反,資料包的發送從上往下也經過了三層:使用者态空間的應用、系統核心空間、最後到網卡驅動。應用先将資料寫入TCP sendbuffer,TCP層将sendbuffer中的資料建構成資料包轉交給IP層。IP層會将待發送的資料包放入隊列QDisc(queueingdiscipline)。資料包成功放入QDisc後,指向資料包的描述符sk_buff被放入RingBuffer輸出隊列,随後網卡驅動調用DMAengine将資料發送到網絡鍊路上。

同樣我們逐層來梳理隊列緩沖有關的參數。

sendBuffer

同recvBuffer類似,和sendBuffer有關的參數如下:

net.ipv4.tcp_wmem = net.core.wmem_defaultnet.core.wmem_max

發送端緩沖的自動調節機制很早就已經實作,并且是無條件開啟,沒有參數去設定。如果指定了tcp_wmem,則net.core.wmem_default被tcp_wmem的覆寫。sendBuffer在tcp_wmem的最小值和最大值之間自動調節。如果調用setsockopt()設定了socket選項SO_SNDBUF,将關閉發送端緩沖的自動調節機制,tcp_wmem将被忽略,SO_SNDBUF的最大值由net.core.wmem_max限制。

QDisc

QDisc(queueing discipline )位于IP層和網卡的ringbuffer之間。我們已經知道,ringbuffer是一個簡單的FIFO隊列,這種設計使網卡的驅動層保持簡單和快速。而QDisc實作了流量管理的進階功能,包括流量分類,優先級和流量整形(rate-shaping)。可以使用tc指令配置QDisc。

QDisc的隊列長度由txqueuelen設定,和接收資料包的隊列長度由核心參數net.core.netdev_max_backlog控制所不同,txqueuelen是和網卡關聯,可以用ifconfig指令檢視目前的大小:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

使用ifconfig調整txqueuelen的大小:

ifconfig eth0 txqueuelen 2000

和資料包的接收一樣,發送資料包也要經過RingBuffer,使用ethtool-g eth0檢視:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

其中TX項是RingBuffer的傳輸隊列,也就是發送隊列的長度。設定也是使用指令ethtool-G。

TCPSegmentation和Checksum Offloading

作業系統可以把一些TCP/IP的功能轉交給網卡去完成,特别是Segmentation(分片)和checksum的計算,這樣可以節省CPU資源,并且由硬體代替OS執行這些操作會帶來性能的提升。

一般以太網的MTU(MaximumTransmission Unit)為1500 bytes,假設應用要發送資料包的大小為7300bytes,MTU1500位元組- IP頭部20位元組 -TCP頭部20位元組=有效負載為1460位元組,是以7300位元組需要拆分成5個segment:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

Segmentation(分片)操作可以由作業系統移交給網卡完成,雖然最終線路上仍然是傳輸5個包,但這樣節省了CPU資源并帶來性能的提升:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

可以使用ethtool-k eth0檢視網卡目前的offloading情況:

Linux TCP隊列相關參數的總結include int listen(int sockfd, int backlog);

上面這個例子checksum和tcpsegmentation的offloading都是打開的。如果想設定網卡的offloading開關,可以使用ethtool-K(注意K是大寫)指令,例如下面的指令關閉了tcp segmentation offload:

sudo ethtool -K eth0 tso off

網卡多隊列和網卡Bonding模式

在資料包的接收過程中已經介紹過了。

至此,終于梳理完畢。整理TCP隊列相關參數的起因是最近在排查一個網絡逾時問題,原因還沒有找到,産生的“副作用”就是這篇文檔。再想深入解決這個問題可能需要做TCP協定代碼的profile,需要繼續學習,希望不久的将來就可以再寫文檔和大家分享了。

參考文檔

Queueing in the Linux Network Stack

TCP Implementation in Linux: A Brief Tutorial

Impact of Bandwidth Delay Product on TCP Throughput

Java程式員也應該知道的系統知識系列之網卡

說說網卡中斷處理

繼續閱讀