Linux TCP队列相关参数的总结 转
在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ù)。網(wǎng)上搜到的文章會(huì)告訴你需要修改哪些參數(shù),但我們經(jīng)常是知其然而不知其所以然,每次照抄過來后,可能很快就忘記或混淆了它們的含義。本文嘗試總結(jié)TCP隊(duì)列緩沖相關(guān)的內(nèi)核參數(shù),從協(xié)議棧的角度梳理它們,希望可以更容易的理解和記憶。注意,本文內(nèi)容均來源于參考文檔,沒有去讀相關(guān)的內(nèi)核源碼做驗(yàn)證,不能保證內(nèi)容嚴(yán)謹(jǐn)正確。作為Java程序員沒讀過內(nèi)核源碼是硬傷。
下面我以server端為視角,從 連接建立、 數(shù)據(jù)包接收 和 數(shù)據(jù)包發(fā)送 這3條路徑對(duì)參數(shù)進(jìn)行歸類梳理。
一、連接建立
簡(jiǎn)單看下連接的建立過程,客戶端向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(int port,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ì)SYN flooding(即客戶端只發(fā)送SYN包發(fā)起握手而不回應(yīng)ACK完成連接建立,填滿server端的半連接隊(duì)列,讓它無(wú)法處理正常的握手請(qǐng)求),Linux實(shí)現(xiàn)了一種稱為SYN cookie的機(jī)制,通過net.ipv4.tcp_syncookies控制,設(shè)置為1表示開啟。簡(jiǎn)單說SYN cookie就是將連接信息編碼在ISN(initial sequence number)中返回給客戶端,這時(shí)server不需要將半連接保存在隊(duì)列中,而是利用客戶端隨后發(fā)來的ACK帶回的ISN還原連接信息,以完成連接的建立,避免了半連接隊(duì)列被攻擊SYN包填滿。對(duì)于一去不復(fù)返的客戶端握手,不理它就是了。
二、數(shù)據(jù)包的接收
先看看接收數(shù)據(jù)包經(jīng)過的路徑:
數(shù)據(jù)包的接收,從下往上經(jīng)過了三層:網(wǎng)卡驅(qū)動(dòng)、系統(tǒng)內(nèi)核空間,最后到用戶態(tài)空間的應(yīng)用。Linux內(nèi)核使用sk_buff(socket kernel buffers)數(shù)據(jù)結(jié)構(gòu)描述一個(gè)數(shù)據(jù)包。當(dāng)一個(gè)新的數(shù)據(jù)包到達(dá),NIC(network interface controller)調(diào)用DMA engine,通過Ring Buffer將數(shù)據(jù)包放置到內(nèi)核內(nèi)存區(qū)。Ring Buffer的大小固定,它不包含實(shí)際的數(shù)據(jù)包,而是包含了指向sk_buff的描述符。當(dāng)Ring Buffer滿的時(shí)候,新來的數(shù)據(jù)包將給丟棄。一旦數(shù)據(jù)包被成功接收,NIC發(fā)起中斷,由內(nèi)核的中斷處理程序?qū)?shù)據(jù)包傳遞給IP層。經(jīng)過IP層的處理,數(shù)據(jù)包被放入隊(duì)列等待TCP層處理。每個(gè)數(shù)據(jù)包經(jīng)過TCP層一系列復(fù)雜的步驟,更新TCP狀態(tài)機(jī),最終到達(dá)recv Buffer,等待被應(yīng)用接收處理。有一點(diǎn)需要注意,數(shù)據(jù)包到達(dá)recv Buffer,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ù)。
網(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模式:Mode 0 (balance-rr)Round-robin策略,這個(gè)模式具備負(fù)載均衡和容錯(cuò)能力Mode 1 (active-backup)主備策略,在綁定中只有一個(gè)網(wǎng)卡被激活,其他處于備份狀態(tài)Mode 2 (balance-xor)XOR策略,通過源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 load balancingMode 6 (balance-alb)Adaptive load balancing
詳細(xì)的說明參考內(nèi)核文檔Linux Ethernet Bonding Driver HOWTO。我們可以通過
cat /proc/net/bonding/bond0查看本機(jī)的Bonding模式:一般很少需要開發(fā)去設(shè)置網(wǎng)卡Bonding模式,自己實(shí)驗(yàn)的話可以參考這篇文檔
網(wǎng)卡多隊(duì)列及中斷綁定
隨著網(wǎng)絡(luò)的帶寬的不斷提升,單核CPU已經(jīng)不能滿足網(wǎng)卡的需求,這時(shí)通過多隊(duì)列網(wǎng)卡驅(qū)動(dòng)的支持,可以將每個(gè)隊(duì)列通過中斷綁定到不同的CPU核上,充分利用多核提升數(shù)據(jù)包的處理能力。
首先查看網(wǎng)卡是否支持多隊(duì)列,使用lspci -vvv命令,找到Ethernet controller項(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
Ring Buffer
Ring Buffer位于NIC和IP層之間,是一個(gè)典型的FIFO(先進(jìn)先出)環(huán)形隊(duì)列。Ring Buffer沒有包含數(shù)據(jù)本身,而是包含了指向sk_buff(socket kernel buffers)的描述符。
可以使用ethtool -g eth0查看當(dāng)前Ring Buffer的設(shè)置:
上面的例子接收隊(duì)列為4096,傳輸隊(duì)列為256。可以通過ifconfig觀察接收和傳輸隊(duì)列的運(yùn)行狀況:- RX errors:收包總的錯(cuò)誤數(shù)
- RX dropped: 表示數(shù)據(jù)包已經(jīng)進(jìn)入了
Ring Buffer,但是由于內(nèi)存不夠等系統(tǒng)原因,導(dǎo)致在拷貝到內(nèi)存的過程中被丟棄。 - RX overruns: overruns意味著數(shù)據(jù)包沒到
Ring Buffer就被網(wǎng)卡物理層給丟棄了,而CPU無(wú)法及時(shí)的處理中斷是造成Ring Buffer滿的原因之一,例如中斷分配的不均勻。
當(dāng)dropped數(shù)量持續(xù)增加,建議增大Ring Buffer,使用ethtool -G進(jìn)行設(shè)置。
Input Packet 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è)置。recv Buffer
recv buffer是調(diào)節(jié)TCP性能的關(guān)鍵參數(shù)。BDP(Bandwidth-delay product,帶寬延遲積) 是網(wǎng)絡(luò)的帶寬和與RTT(round trip time)的乘積,BDP的含義是任意時(shí)刻處于在途未確認(rèn)的最大數(shù)據(jù)量。RTT使用ping命令可以很容易的得到。為了達(dá)到最大的吞吐量,recv Buffer的設(shè)置應(yīng)該大于BDP,即recv Buffer >= bandwidth * RTT。假設(shè)帶寬是100Mbps,RTT是100ms,那么BDP的計(jì)算如下:BDP = 100Mbps * 100ms = (100 / 8) * (100 / 1000) = 1.25MB
Linux在2.6.17以后增加了
recv Buffer自動(dòng)調(diào)節(jié)機(jī)制,recv buffer的實(shí)際大小會(huì)自動(dòng)在最小值和最大值之間浮動(dòng),以期找到性能和資源的平衡點(diǎn),因此大多數(shù)情況下不建議將recv buffer手工設(shè)置成固定值。
當(dāng)net.ipv4.tcp_moderate_rcvbuf設(shè)置為1時(shí),自動(dòng)調(diào)節(jié)機(jī)制生效,每個(gè)TCP連接的recv Buffer由下面的3元數(shù)組指定:net.ipv4.tcp_rmem =
最初
recv buffer被設(shè)置為,同時(shí)這個(gè)缺省值會(huì)覆蓋net.core.rmem_default的設(shè)置。隨后recv buffer根據(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)閉。recv buffer的缺省值由net.core.rmem_default設(shè)置,但如果設(shè)置了net.ipv4.tcp_rmem,缺省值則被覆蓋。可以通過系統(tǒng)調(diào)用setsockopt()設(shè)置recv buffer的最大值為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)等額外信息。因此上面討論的
recv buffer最佳值僅僅等于BDP是不夠的,還需要考慮保存socket等額外信息的開銷。Linux根據(jù)參數(shù)net.ipv4.tcp_adv_win_scale計(jì)算額外開銷的大小:
如果net.ipv4.tcp_adv_win_scale的值為1,則二分之一的緩沖空間用來做額外開銷,如果為2的話,則四分之一緩沖空間用來做額外開銷。因此recv buffer的最佳值應(yīng)該設(shè)置為:
三、數(shù)據(jù)包的發(fā)送
發(fā)送數(shù)據(jù)包經(jīng)過的路徑:
和接收數(shù)據(jù)的路徑相反,數(shù)據(jù)包的發(fā)送從上往下也經(jīng)過了三層:用戶態(tài)空間的應(yīng)用、系統(tǒng)內(nèi)核空間、最后到網(wǎng)卡驅(qū)動(dòng)。應(yīng)用先將數(shù)據(jù)寫入TCP send buffer,TCP層將send buffer中的數(shù)據(jù)構(gòu)建成數(shù)據(jù)包轉(zhuǎn)交給IP層。IP層會(huì)將待發(fā)送的數(shù)據(jù)包放入隊(duì)列QDisc(queueing discipline)。數(shù)據(jù)包成功放入QDisc后,指向數(shù)據(jù)包的描述符sk_buff被放入Ring Buffer輸出隊(duì)列,隨后網(wǎng)卡驅(qū)動(dòng)調(diào)用DMA engine將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)鏈路上。
同樣我們逐層來梳理隊(duì)列緩沖有關(guān)的參數(shù)。
send Buffer
同recv Buffer類似,和send Buffer有關(guān)的參數(shù)如下:net.ipv4.tcp_wmem =
net.core.wmem_default
net.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的覆蓋。send Buffer在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限制。QDisc
QDisc(queueing discipline )位于IP層和網(wǎng)卡的ring buffer之間。我們已經(jīng)知道,ring buffer是一個(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
Ring Buffer
和數(shù)據(jù)包的接收一樣,發(fā)送數(shù)據(jù)包也要經(jīng)過Ring Buffer,使用ethtool -g eth0查看:
其中TX項(xiàng)是Ring Buffer的傳輸隊(duì)列,也就是發(fā)送隊(duì)列的長(zhǎng)度。設(shè)置也是使用命令ethtool -G。TCP Segmentation和Checksum Offloading
操作系統(tǒng)可以把一些TCP/IP的功能轉(zhuǎn)交給網(wǎng)卡去完成,特別是Segmentation(分片)和checksum的計(jì)算,這樣可以節(jié)省CPU資源,并且由硬件代替OS執(zhí)行這些操作會(huì)帶來性能的提升。
一般以太網(wǎng)的MTU(Maximum Transmission 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資源并帶來性能的提升:
可以使用ethtool -k eth0查看網(wǎng)卡當(dāng)前的offloading情況:
上面這個(gè)例子checksum和tcp segmentation的offloading都是打開的。如果想設(shè)置網(wǎng)卡的offloading開關(guān),可以使用ethtool -K(注意K是大寫)命令,例如下面的命令關(guān)閉了tcp segmentation offload:sudo ethtool -K eth0 tso off
網(wǎng)卡多隊(duì)列和網(wǎng)卡Bonding模式
在數(shù)據(jù)包的接收過程中已經(jīng)介紹過了。
至此,終于梳理完畢。整理TCP隊(duì)列相關(guān)參數(shù)的起因是最近在排查一個(gè)網(wǎng)絡(luò)超時(shí)問題,原因還沒有找到,產(chǎn)生的“副作用”就是這篇文檔。再想深入解決這個(gè)問題可能需要做TCP協(xié)議代碼的profile,需要繼續(xù)學(xué)習(xí),希望不久的將來就可以再寫文檔和大家分享了。
參考文檔
Queueing in the Linux Network Stack
TCP Implementation in Linux: A Brief Tutorial
Impact of Bandwidth Delay Product on TCP Throughput
Java程序員也應(yīng)該知道的系統(tǒng)知識(shí)系列之網(wǎng)卡
說說網(wǎng)卡中斷處理
總結(jié)
以上是生活随笔為你收集整理的Linux TCP队列相关参数的总结 转的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 条件兵是指哪些兵种?
- 下一篇: 军人爸妈怎么免票啊?