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