在Linux上做網(wǎng)絡應用的性能優(yōu)化時,一般都會對TCP相關的內(nèi)核參數(shù)進行調(diào)節(jié),特別是和緩沖、隊列有關的參數(shù)。很多文章會告訴你需要修改哪些參數(shù),但我們經(jīng)常是知其然而不知其所以然,每次照抄過來后,可能很快就忘記或混淆了它們的含義。
下面我以server端為視角,從 連接建立、 數(shù)據(jù)包接收 和 數(shù)據(jù)包發(fā)送 這3條路徑對參數(shù)進行歸類梳理。
一、連接建立
簡單看下連接的建立過程,客戶端向server發(fā)送SYN包,server回復SYN+ACK,同時將這個處于SYN_RECV狀態(tài)的連接保存到半連接隊列??蛻舳朔祷谹CK包完成三次握手,server將ESTABLISHED狀態(tài)的連接移入accept隊列,等待應用調(diào)用accept()??梢钥吹浇⑦B接涉及兩個隊列:
半連接隊列,保存SYN_RECV狀態(tài)的連接。隊列長度由net.ipv4.tcp_max_syn_backlog設置
accept隊列,保存ESTABLISHED狀態(tài)的連接。隊列長度為min(net.core.somaxconn,backlog)。其中backlog是我們創(chuàng)建ServerSocket(intport,int backlog)時指定的參數(shù),最終會傳遞給listen方法:#include int listen(int sockfd, int backlog); 如果我們設置的backlog大于net.core.somaxconn,accept隊列的長度將被設置為net.core.somaxconn
另外,為了應對SYNflooding(即客戶端只發(fā)送SYN包發(fā)起握手而不回應ACK完成連接建立,填滿server端的半連接隊列,讓它無法處理正常的握手請求),Linux實現(xiàn)了一種稱為SYNcookie的機制,通過net.ipv4.tcp_syncookies控制,設置為1表示開啟。簡單說SYNcookie就是將連接信息編碼在ISN(initialsequencenumber)中返回給客戶端,這時server不需要將半連接保存在隊列中,而是利用客戶端隨后發(fā)來的ACK帶回的ISN還原連接信息,以完成連接的建立,避免了半連接隊列被攻擊SYN包填滿。對于一去不復返的客戶端握手,不理它就是了。
二、數(shù)據(jù)包的接收
先看看接收數(shù)據(jù)包經(jīng)過的路徑:
數(shù)據(jù)包的接收,從下往上經(jīng)過了三層:網(wǎng)卡驅(qū)動、系統(tǒng)內(nèi)核空間,最后到用戶態(tài)空間的應用。Linux內(nèi)核使用sk_buff(socketkernel buffers)數(shù)據(jù)結(jié)構(gòu)描述一個數(shù)據(jù)包。當一個新的數(shù)據(jù)包到達,NIC(networkinterface controller)調(diào)用DMAengine,通過RingBuffer將數(shù)據(jù)包放置到內(nèi)核內(nèi)存區(qū)。RingBuffer的大小固定,它不包含實際的數(shù)據(jù)包,而是包含了指向sk_buff的描述符。當RingBuffer滿的時候,新來的數(shù)據(jù)包將給丟棄。一旦數(shù)據(jù)包被成功接收,NIC發(fā)起中斷,由內(nèi)核的中斷處理程序?qū)?shù)據(jù)包傳遞給IP層。經(jīng)過IP層的處理,數(shù)據(jù)包被放入隊列等待TCP層處理。每個數(shù)據(jù)包經(jīng)過TCP層一系列復雜的步驟,更新TCP狀態(tài)機,最終到達recvBuffer,等待被應用接收處理。有一點需要注意,數(shù)據(jù)包到達recvBuffer,TCP就會回ACK確認,既TCP的ACK表示數(shù)據(jù)包已經(jīng)被操作系統(tǒng)內(nèi)核收到,但并不確保應用層一定收到數(shù)據(jù)(例如這個時候系統(tǒng)crash),因此一般建議應用協(xié)議層也要設計自己的確認機制。
上面就是一個相當簡化的數(shù)據(jù)包接收流程,讓我們逐層看看隊列緩沖有關的參數(shù)。
**1、網(wǎng)卡Bonding模式 **
當主機有1個以上的網(wǎng)卡時,Linux會將多個網(wǎng)卡綁定為一個虛擬的bonded網(wǎng)絡接口,對TCP/IP而言只存在一個bonded網(wǎng)卡。多網(wǎng)卡綁定一方面能夠提高網(wǎng)絡吞吐量,另一方面也可以增強網(wǎng)絡高可用。Linux支持7種Bonding模式:
詳細的說明參考內(nèi)核文檔LinuxEthernet Bonding Driver HOWTO。我們可以通過 cat/proc/net/bonding/bond0查看本機的Bonding模式:
一般很少需要開發(fā)去設置網(wǎng)卡Bonding模式,自己實驗的話可以參考這篇文檔。
Mode 0(balance-rr) Round-robin策略,這個模式具備負載均衡和容錯能力
Mode 1(active-backup) 主備策略,在綁定中只有一個網(wǎng)卡被激活,其他處于備份狀態(tài)
Mode 2(balance-xor) XOR策略,通過源MAC地址與目的MAC地址做異或操作選擇slave網(wǎng)卡
Mode 3 (broadcast) 廣播,在所有的網(wǎng)卡上傳送所有的報文
Mode 4 (802.3ad) IEEE 802.3ad動態(tài)鏈路聚合。創(chuàng)建共享相同的速率和雙工模式的聚合組
Mode 5 (balance-tlb) Adaptive transmit loadbalancing
Mode 6 (balance-alb) Adaptive loadbalancing
2、網(wǎng)卡多隊列及中斷綁定
隨著網(wǎng)絡的帶寬的不斷提升,單核CPU已經(jīng)不能滿足網(wǎng)卡的需求,這時通過多隊列網(wǎng)卡驅(qū)動的支持,可以將每個隊列通過中斷綁定到不同的CPU核上,充分利用多核提升數(shù)據(jù)包的處理能力。
首先查看網(wǎng)卡是否支持多隊列,使用lspci-vvv命令,找到Ethernetcontroller項:
如果有MSI-X, Enable+ 并且Count > 1,則該網(wǎng)卡是多隊列網(wǎng)卡。
然后查看是否打開了網(wǎng)卡多隊列。使用命令cat/proc/interrupts,如果看到eth0-TxRx-0表明多隊列支持已經(jīng)打開:
最后確認每個隊列是否綁定到不同的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
3、RingBuffer
Ring Buffer位于NIC和IP層之間,是一個典型的FIFO(先進先出)環(huán)形隊列。RingBuffer沒有包含數(shù)據(jù)本身,而是包含了指向sk_buff(socketkernel buffers)的描述符。可以使用ethtool-g eth0查看當前RingBuffer的設置:
上面的例子接收隊列為4096,傳輸隊列為256??梢酝ㄟ^ifconfig觀察接收和傳輸隊列的運行狀況:
RXerrors:收包總的錯誤數(shù)
RX dropped:表示數(shù)據(jù)包已經(jīng)進入了RingBuffer,但是由于內(nèi)存不夠等系統(tǒng)原因,導致在拷貝到內(nèi)存的過程中被丟棄。
RX overruns:overruns意味著數(shù)據(jù)包沒到RingBuffer就被網(wǎng)卡物理層給丟棄了,而CPU無法及時的處理中斷是造成RingBuffer滿的原因之一,例如中斷分配的不均勻。當dropped數(shù)量持續(xù)增加,建議增大RingBuffer,使用ethtool-G進行設置。
4、InputPacket Queue(數(shù)據(jù)包接收隊列)
當接收數(shù)據(jù)包的速率大于內(nèi)核TCP處理包的速率,數(shù)據(jù)包將會緩沖在TCP層之前的隊列中。接收隊列的長度由參數(shù) net.core.netdev_max_backlog設置。
5、recvBuffer
recv buffer是調(diào)節(jié)TCP性能的關鍵參數(shù)。BDP(Bandwidth-delayproduct,帶寬延遲積) 是網(wǎng)絡的帶寬和與RTT(roundtrip time)的乘積,BDP的含義是任意時刻處于在途未確認的最大數(shù)據(jù)量。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自動調(diào)節(jié)機制,recvbuffer的實際大小會自動在最小值和最大值之間浮動,以期找到性能和資源的平衡點,因此大多數(shù)情況下不建議將recvbuffer手工設置成固定值。
當net.ipv4.tcp_moderate_rcvbuf設置為1時,自動調(diào)節(jié)機制生效,每個TCP連接的recvBuffer由下面的3元數(shù)組指定:
net.ipv4.tcp_rmem =
最初recvbuffer被設置為,同時這個缺省值會覆蓋net.core.rmem_default的設置。隨后recvbuffer根據(jù)實際情況在最大值和最小值之間動態(tài)調(diào)節(jié)。在緩沖的動態(tài)調(diào)優(yōu)機制開啟的情況下,我們將net.ipv4.tcp_rmem的最大值設置為BDP。
當net.ipv4.tcp_moderate_rcvbuf被設置為0,或者設置了socket選項SO_RCVBUF,緩沖的動態(tài)調(diào)節(jié)機制被關閉。recvbuffer的缺省值由net.core.rmem_default設置,但如果設置了net.ipv4.tcp_rmem,缺省值則被覆蓋。可以通過系統(tǒng)調(diào)用setsockopt()設置recvbuffer的最大值為net.core.rmem_max。在緩沖動態(tài)調(diào)節(jié)機制關閉的情況下,建議把緩沖的缺省值設置為BDP。
注意這里還有一個細節(jié),緩沖除了保存接收的數(shù)據(jù)本身,還需要一部分空間保存socket數(shù)據(jù)結(jié)構(gòu)等額外信息。因此上面討論的recvbuffer最佳值僅僅等于BDP是不夠的,還需要考慮保存socket等額外信息的開銷。Linux根據(jù)參數(shù) net.ipv4.tcp_adv_win_scale計算額外開銷的大?。?/p>
如果 net.ipv4.tcp_adv_win_scale的值為1,則二分之一的緩沖空間用來做額外開銷,如果為2的話,則四分之一緩沖空間用來做額外開銷。因此recvbuffer的最佳值應該設置為:
三、數(shù)據(jù)包的發(fā)送
發(fā)送數(shù)據(jù)包經(jīng)過的路徑:
和接收數(shù)據(jù)的路徑相反,數(shù)據(jù)包的發(fā)送從上往下也經(jīng)過了三層:用戶態(tài)空間的應用、系統(tǒng)內(nèi)核空間、最后到網(wǎng)卡驅(qū)動。應用先將數(shù)據(jù)寫入TCP sendbuffer,TCP層將sendbuffer中的數(shù)據(jù)構(gòu)建成數(shù)據(jù)包轉(zhuǎn)交給IP層。IP層會將待發(fā)送的數(shù)據(jù)包放入隊列QDisc(queueingdiscipline)。數(shù)據(jù)包成功放入QDisc后,指向數(shù)據(jù)包的描述符sk_buff被放入RingBuffer輸出隊列,隨后網(wǎng)卡驅(qū)動調(diào)用DMAengine將數(shù)據(jù)發(fā)送到網(wǎng)絡鏈路上。
同樣我們逐層來梳理隊列緩沖有關的參數(shù)。
1、sendBuffer
同recvBuffer類似,和sendBuffer有關的參數(shù)如下:net.ipv4.tcp_wmem = net.core.wmem_defaultnet.core.wmem_max 發(fā)送端緩沖的自動調(diào)節(jié)機制很早就已經(jīng)實現(xiàn),并且是無條件開啟,沒有參數(shù)去設置。如果指定了tcp_wmem,則net.core.wmem_default被tcp_wmem的覆蓋。sendBuffer在tcp_wmem的最小值和最大值之間自動調(diào)節(jié)。如果調(diào)用setsockopt()設置了socket選項SO_SNDBUF,將關閉發(fā)送端緩沖的自動調(diào)節(jié)機制,tcp_wmem將被忽略,SO_SNDBUF的最大值由net.core.wmem_max限制。
2、QDisc
QDisc(queueing discipline )位于IP層和網(wǎng)卡的ringbuffer之間。我們已經(jīng)知道,ringbuffer是一個簡單的FIFO隊列,這種設計使網(wǎng)卡的驅(qū)動層保持簡單和快速。而QDisc實現(xiàn)了流量管理的高級功能,包括流量分類,優(yōu)先級和流量整形(rate-shaping)??梢允褂胻c命令配置QDisc。
QDisc的隊列長度由txqueuelen設置,和接收數(shù)據(jù)包的隊列長度由內(nèi)核參數(shù) net.core.netdev_max_backlog控制所不同,txqueuelen是和網(wǎng)卡關聯(lián),可以用ifconfig命令查看當前的大?。?/p>
使用ifconfig調(diào)整txqueuelen的大小:
ifconfig eth0 txqueuelen 2000
3、RingBuffer
和數(shù)據(jù)包的接收一樣,發(fā)送數(shù)據(jù)包也要經(jīng)過RingBuffer,使用ethtool-g eth0查看:
其中TX項是RingBuffer的傳輸隊列,也就是發(fā)送隊列的長度。設置也是使用命令ethtool-G。
4、TCPSegmentation和Checksum Offloading
操作系統(tǒng)可以把一些TCP/IP的功能轉(zhuǎn)交給網(wǎng)卡去完成,特別是Segmentation(分片)和checksum的計算,這樣可以節(jié)省CPU資源,并且由硬件代替OS執(zhí)行這些操作會帶來性能的提升。一般以太網(wǎng)的MTU(MaximumTransmission Unit)為1500 bytes,假設應用要發(fā)送數(shù)據(jù)包的大小為7300bytes,MTU1500字節(jié)- IP頭部20字節(jié) -TCP頭部20字節(jié)=有效負載為1460字節(jié),因此7300字節(jié)需要拆分成5個segment:
Segmentation(分片)操作可以由操作系統(tǒng)移交給網(wǎng)卡完成,雖然最終線路上仍然是傳輸5個包,但這樣節(jié)省了CPU資源并帶來性能的提升:
可以使用ethtool-k eth0查看網(wǎng)卡當前的offloading情況:
上面這個例子checksum和tcpsegmentation的offloading都是打開的。如果想設置網(wǎng)卡的offloading開關,可以使用ethtool-K(注意K是大寫)命令,例如下面的命令關閉了tcp segmentation offload:sudo ethtool -K eth0 tso off
5、網(wǎng)卡多隊列和網(wǎng)卡Bonding模式
在數(shù)據(jù)包的接收過程中已經(jīng)介紹過了。
至此,終于梳理完畢。
審核編輯:湯梓紅
評論
查看更多