自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux高性能網(wǎng)絡(luò)編程之TCP連接的內(nèi)存使用

運(yùn)維 系統(tǒng)運(yùn)維
當(dāng)服務(wù)器的并發(fā)TCP連接數(shù)以十萬計(jì)時,我們就會對一個TCP連接在操作系統(tǒng)內(nèi)核上消耗的內(nèi)存多少感興趣。socket編程方法提供了SO_SNDBUF、SO_RCVBUF這樣的接口來設(shè)置連接的讀寫緩存,linux上還提供了以下系統(tǒng)級的配置來整體設(shè)置服務(wù)器上的TCP內(nèi)存使用,但這些配置看名字卻有些互相沖突、概念模糊的感覺。

當(dāng)服務(wù)器的并發(fā)TCP連接數(shù)以十萬計(jì)時,我們就會對一個TCP連接在操作系統(tǒng)內(nèi)核上消耗的內(nèi)存多少感興趣。socket編程方法提供了SO_SNDBUF、SO_RCVBUF這樣的接口來設(shè)置連接的讀寫緩存,linux上還提供了以下系統(tǒng)級的配置來整體設(shè)置服務(wù)器上的TCP內(nèi)存使用,但這些配置看名字卻有些互相沖突、概念模糊的感覺,如下(sysctl -a命令可以查看這些配置):

 

  1. net.ipv4.tcp_rmem = 8192 87380 16777216  
  2. net.ipv4.tcp_wmem = 8192 65536 16777216  
  3. net.ipv4.tcp_mem = 8388608 12582912 16777216  
  4. net.core.rmem_default = 262144  
  5. net.core.wmem_default = 262144  
  6. net.core.rmem_max = 16777216  
  7. net.core.wmem_max = 16777216 

還有一些較少被提及的、也跟TCP內(nèi)存相關(guān)的配置:

  1. net.ipv4.tcp_moderate_rcvbuf = 1net.ipv4.tcp_adv_win_scale = 2 

(注:為方便下文講述,介紹以上系統(tǒng)配置時前綴省略掉,配置值以空格分隔的多個數(shù)字以數(shù)組來稱呼,例如tcp_rmem[2]表示上面第一行最后一列16777216。)

網(wǎng)上可以找到很多這些系統(tǒng)配置項(xiàng)的說明,然而往往還是讓人費(fèi)解,例如,tcp_rmem[2]和rmem_max似乎都跟接收緩存最大值有關(guān),但它們卻可以不一致,究竟有什么區(qū)別?或者tcp_wmem[1]和wmem_default似乎都表示發(fā)送緩存的默認(rèn)值,沖突了怎么辦?在用抓包軟件抓到的syn握手包里,為什么TCP接收窗口大小似乎與這些配置完全沒關(guān)系?

TCP連接在進(jìn)程中使用的內(nèi)存大小千變?nèi)f化,通常程序較復(fù)雜時可能不是直接基于socket編程,這時平臺級的組件可能就封裝了TCP連接使用到的用戶態(tài)內(nèi)存。不同的平臺、組件、中間件、網(wǎng)絡(luò)庫都大不相同。而內(nèi)核態(tài)為TCP連接分配內(nèi)存的算法則是基本不變的,這篇文章將試圖說明TCP連接在內(nèi)核態(tài)中會使用多少內(nèi)存,操作系統(tǒng)使用怎樣的策略來平衡宏觀的吞吐量與微觀的某個連接傳輸速度。這篇文章也將一如既往的面向應(yīng)用程序開發(fā)者,而不是系統(tǒng)級的內(nèi)核開發(fā)者,所以,不會詳細(xì)的介紹為了一個TCP連接、一個TCP報(bào)文操作系統(tǒng)分配了多少字節(jié)的內(nèi)存,內(nèi)核級的數(shù)據(jù)結(jié)構(gòu)也不是本文的關(guān)注點(diǎn),這些也不是應(yīng)用級程序員的關(guān)注點(diǎn)。這篇文章主要描述linux內(nèi)核為了TCP連接上傳輸?shù)臄?shù)據(jù)是怎樣管理讀寫緩存的。

一、緩存上限是什么?

(1)先從應(yīng)用程序編程時可以設(shè)置的SO_SNDBUF、SO_RCVBUF說起。

無論何種語言,都對TCP連接提供基于setsockopt方法實(shí)現(xiàn)的SO_SNDBUF、SO_RCVBUF,怎么理解這兩個屬性的意義呢?

SO_SNDBUF、SO_RCVBUF都是個體化的設(shè)置,即,只會影響到設(shè)置過的連接,而不會對其他連接生效。SO_SNDBUF表示這個連接上的內(nèi)核寫緩存上限。實(shí)際上,進(jìn)程設(shè)置的SO_SNDBUF也并不是真的上限,在內(nèi)核中會把這個值翻一倍再作為寫緩存上限使用,我們不需要糾結(jié)這種細(xì)節(jié),只需要知道,當(dāng)設(shè)置了SO_SNDBUF時,就相當(dāng)于劃定了所操作的TCP連接上的寫緩存能夠使用的最大內(nèi)存。然而,這個值也不是可以由著進(jìn)程隨意設(shè)置的,它會受制于系統(tǒng)級的上下限,當(dāng)它大于上面的系統(tǒng)配置wmem_max(net.core.wmem_max)時,將會被wmem_max替代(同樣翻一倍);而當(dāng)它特別小時,例如在2.6.18內(nèi)核中設(shè)計(jì)的寫緩存最小值為2K字節(jié),此時也會被直接替代為2K。

SO_RCVBUF表示連接上的讀緩存上限,與SO_SNDBUF類似,它也受制于rmem_max配置項(xiàng),實(shí)際在內(nèi)核中也是2倍大小作為讀緩存的使用上限。SO_RCVBUF設(shè)置時也有下限,同樣在2.6.18內(nèi)核中若這個值小于256字節(jié)就會被256所替代。

(2)那么,可以設(shè)置的SO_SNDBUF、SO_RCVBUF緩存使用上限與實(shí)際內(nèi)存到底有怎樣的關(guān)系呢?

TCP連接所用內(nèi)存主要由讀寫緩存決定,而讀寫緩存的大小只與實(shí)際使用場景有關(guān),在實(shí)際使用未達(dá)到上限時,SO_SNDBUF、SO_RCVBUF是不起任何作用的。對讀緩存來說,接收到一個來自連接對端的TCP報(bào)文時,會導(dǎo)致讀緩存增加,當(dāng)然,如果加上報(bào)文大小后讀緩存已經(jīng)超過了讀緩存上限,那么這個報(bào)文會被丟棄從而讀緩存大小維持不變。什么時候讀緩存使用的內(nèi)存會減少呢?當(dāng)進(jìn)程調(diào)用read、recv這樣的方法讀取TCP流時,讀緩存就會減少。因此,讀緩存是一個動態(tài)變化的、實(shí)際用到多少才分配多少的緩沖內(nèi)存,當(dāng)這個連接非??臻e時,且用戶進(jìn)程已經(jīng)把連接上接收到的數(shù)據(jù)都消費(fèi)了,那么讀緩存使用內(nèi)存就是0。

寫緩存也是同樣道理。當(dāng)用戶進(jìn)程調(diào)用send或者write這樣的方法發(fā)送TCP流時,就會造成寫緩存增大。當(dāng)然,如果寫緩存已經(jīng)到達(dá)上限,那么寫緩存維持不變,向用戶進(jìn)程返回失敗。而每當(dāng)接收到TCP連接對端發(fā)來的ACK確認(rèn)了報(bào)文的成功發(fā)送時,寫緩存就會減少,這是因?yàn)門CP的可靠性決定的,發(fā)出去報(bào)文后由于擔(dān)心報(bào)文丟失而不會銷毀它,可能會由重發(fā)定時器來重發(fā)報(bào)文。因此,寫緩存也是動態(tài)變化的,空閑的正常連接上,寫緩存所用內(nèi)存通常也為0。

因此,只有當(dāng)接收網(wǎng)絡(luò)報(bào)文的速度大于應(yīng)用程序讀取報(bào)文的速度時,可能使讀緩存達(dá)到了上限,這時這個緩存使用上限才會起作用。所起作用為:丟棄掉新收到的報(bào)文,防止這個TCP連接消耗太多的服務(wù)器資源。同樣,當(dāng)應(yīng)用程序發(fā)送報(bào)文的速度大于接收對方確認(rèn)ACK報(bào)文的速度時,寫緩存可能達(dá)到上限,從而使send這樣的方法失敗,內(nèi)核不為其分配內(nèi)存。

二、緩存的大小與TCP的滑動窗口到底有什么關(guān)系?

(1)滑動窗口的大小與緩存大小肯定是有關(guān)的,但卻不是一一對應(yīng)的關(guān)系,更不會與緩存上限具有一一對應(yīng)的關(guān)系。因此,網(wǎng)上很多資料介紹rmem_max等配置設(shè)置了滑動窗口的最大值,與我們tcpdump抓包時看到的win窗口值完全不一致,是講得通的。下面我們來細(xì)探其分別在哪里。

讀緩存的作用有2個:1、將無序的、落在接收滑動窗口內(nèi)的TCP報(bào)文緩存起來;2、當(dāng)有序的、可以供應(yīng)用程序讀取的報(bào)文出現(xiàn)時,由于應(yīng)用程序的讀取是延時的,所以會把待應(yīng)用程序讀取的報(bào)文也保存在讀緩存中。所以,讀緩存一分為二,一部分緩存無序報(bào)文,一部分緩存待延時讀取的有序報(bào)文。這兩部分緩存大小之和由于受制于同一個上限值,所以它們是會互相影響的,當(dāng)應(yīng)用程序讀取速率過慢時,這塊過大的應(yīng)用緩存將會影響到套接字緩存,使接收滑動窗口縮小,從而通知連接的對端降低發(fā)送速度,避免無謂的網(wǎng)絡(luò)傳輸。當(dāng)應(yīng)用程序長時間不讀取數(shù)據(jù),造成應(yīng)用緩存將套接字緩存擠壓到?jīng)]空間,那么連接對端會收到接收窗口為0的通知,告訴對方:我現(xiàn)在消化不了更多的報(bào)文了。

反之,接收滑動窗口也是一直在變化的,我們用tcpdump抓三次握手的報(bào)文:

  1. 14:49:52.421674 IP houyi-vm02.dev.sd.aliyun.com.6400 > r14a02001.dg.tbsite.net.54073: S 2736789705:2736789705(0) ack 1609024383 win 5792 <mss 1460,sackOK,timestamp 2925954240 2940689794,nop,wscale 9> 

可以看到初始的接收窗口是5792,當(dāng)然也遠(yuǎn)小于最大接收緩存(稍后介紹的tcp_rmem[1])。

這當(dāng)然是有原因的,TCP協(xié)議需要考慮復(fù)雜的網(wǎng)絡(luò)環(huán)境,所以使用了慢啟動、擁塞窗口(參見 高性能網(wǎng)絡(luò)編程2----TCP消息的發(fā)送 ),建立連接時的初始窗口并不會按照接收緩存的最大值來初始化。這是因?yàn)?,過大的初始窗口從宏觀角度,對整個網(wǎng)絡(luò)可能造成過載引發(fā)惡性循環(huán),也就是考慮到鏈路上各環(huán)節(jié)的諸多路由器、交換機(jī)可能扛不住壓力不斷的丟包(特別是廣域網(wǎng)),而微觀的TCP連接的雙方卻只按照自己的讀緩存上限作為接收窗口,這樣雙方的發(fā)送窗口(對方的接收窗口)越大就對網(wǎng)絡(luò)產(chǎn)生越壞的影響。慢啟動就是使初始窗口盡量的小,隨著接收到對方的有效報(bào)文,確認(rèn)了網(wǎng)絡(luò)的有效傳輸能力后,才開始增大接收窗口。

不同的linux內(nèi)核有著不同的初始窗口,我們以廣為使用的linux2.6.18內(nèi)核為例,在以太網(wǎng)里,MSS大小為1460,此時初始窗口大小為4倍的MSS,簡單列下代碼(*rcv_wnd即初始接收窗口):

 

  1. int init_cwnd = 4; 
  2.  if (mss > 1460*3) 
  3.   init_cwnd = 2; 
  4.  else if (mss > 1460) 
  5.   init_cwnd = 3; 
  6.  if (*rcv_wnd > init_cwnd*mss) 
  7.   *rcv_wnd = init_cwnd*mss; 

大家可能要問,為何上面的抓包上顯示窗口其實(shí)是5792,并不是1460*4為5840呢?這是因?yàn)?460想表達(dá)的意義是:將1500字節(jié)的MTU去除了20字節(jié)的IP頭、20字節(jié)的TCP頭以后,一個最大報(bào)文能夠承載的有效數(shù)據(jù)長度。但有些網(wǎng)絡(luò)中,會在TCP的可選頭部里,使用12字節(jié)作為時間戳使用,這樣,有效數(shù)據(jù)就是MSS再減去12,初始窗口就是(1460-12)*4=5792,這與窗口想表達(dá)的含義是一致的,即:我能夠處理的有效數(shù)據(jù)長度。

在linux3以后的版本中,初始窗口調(diào)整到了10個MSS大小,這主要來自于GOOGLE的建議。原因是這樣的,接收窗口雖然常以指數(shù)方式來快速增加窗口大小(擁塞閥值以下是指數(shù)增長的,閥值以上進(jìn)入擁塞避免階段則為線性增長,而且,擁塞閥值自身在收到128以上數(shù)據(jù)報(bào)文時也有機(jī)會快速增加),若是傳輸視頻這樣的大數(shù)據(jù),那么隨著窗口增加到(接近)最大讀緩存后,就會“開足馬力”傳輸數(shù)據(jù),但若是通常都是幾十KB的網(wǎng)頁,那么過小的初始窗口還沒有增加到合適的窗口時,連接就結(jié)束了。這樣相比較大的初始窗口,就使得用戶需要更多的時間(RTT)才能傳輸完數(shù)據(jù),體驗(yàn)不好。

那么這時大家可能有疑問,當(dāng)窗口從初始窗口一路擴(kuò)張到最大接收窗口時,最大接收窗口就是最大讀緩存嗎?

不是,因?yàn)楸仨毞忠徊糠志彺嬗糜趹?yīng)用程序的延時報(bào)文讀取。到底會分多少出來呢?這是可配的系統(tǒng)選項(xiàng),如下:

  1. net.ipv4.tcp_adv_win_scale = 2 

這里的 tcp_adv_win_scale意味著,將要拿出1/(2^ tcp_adv_win_scale )緩存出來做應(yīng)用緩存。即,默認(rèn) tcp_adv_win_scale配置為2時,就是拿出至少1/4的內(nèi)存用于應(yīng)用讀緩存,那么,最大的接收滑動窗口的大小只能到達(dá)讀緩存的3/4。

(2)最大讀緩存到底應(yīng)該設(shè)置到多少為合適呢?

當(dāng)應(yīng)用緩存所占的份額通過tcp_adv_win_scale配置確定后,讀緩存的上限應(yīng)當(dāng)由最大的TCP接收窗口決定。初始窗口可能只有4個或者10個MSS,但在無丟包情形下隨著報(bào)文的交互窗口就會增大,當(dāng)窗口過大時,“過大”是什么意思呢?即,對于通訊的兩臺機(jī)器的內(nèi)存而言不算大,但是對于整個網(wǎng)絡(luò)負(fù)載來說過大了,就會對網(wǎng)絡(luò)設(shè)備引發(fā)惡性循環(huán),不斷的因?yàn)榉泵Φ木W(wǎng)絡(luò)設(shè)備造成丟包。而窗口過小時,就無法充分的利用網(wǎng)絡(luò)資源。所以,一般會以BDP來設(shè)置最大接收窗口(可計(jì)算出最大讀緩存)。BDP叫做帶寬時延積,也就是帶寬與網(wǎng)絡(luò)時延的乘積,例如若我們的帶寬為2Gbps,時延為10ms,那么帶寬時延積BDP則為2G/8*0.01=2.5MB,所以這樣的網(wǎng)絡(luò)中可以設(shè)最大接收窗口為2.5MB,這樣最大讀緩存可以設(shè)為4/3*2.5MB=3.3MB。

為什么呢?因?yàn)锽DP就表示了網(wǎng)絡(luò)承載能力,最大接收窗口就表示了網(wǎng)絡(luò)承載能力內(nèi)可以不經(jīng)確認(rèn)發(fā)出的報(bào)文。如下圖所示:

 

linux高性能網(wǎng)絡(luò)編程之tcp連接的內(nèi)存使用

經(jīng)常提及的所謂長肥網(wǎng)絡(luò),“長”就是是時延長,“肥”就是帶寬大,這兩者任何一個大時,BDP就大,都應(yīng)導(dǎo)致最大窗口增大,進(jìn)而導(dǎo)致讀緩存上限增大。所以在長肥網(wǎng)絡(luò)中的服務(wù)器,緩存上限都是比較大的。(當(dāng)然,TCP原始的16位長度的數(shù)字表示窗口雖然有上限,但在RFC1323中定義的彈性滑動窗口使得滑動窗口可以擴(kuò)展到足夠大。)

發(fā)送窗口實(shí)際上就是TCP連接對方的接收窗口,所以大家可以按接收窗口來推斷,這里不再啰嗦。

三、linux的TCP緩存上限自動調(diào)整策略

那么,設(shè)置好最大緩存限制后就高枕無憂了嗎?對于一個TCP連接來說,可能已經(jīng)充分利用網(wǎng)絡(luò)資源,使用大窗口、大緩存來保持高速傳輸了。比如在長肥網(wǎng)絡(luò)中,緩存上限可能會被設(shè)置為幾十兆字節(jié),但系統(tǒng)的總內(nèi)存卻是有限的,當(dāng)每一個連接都全速飛奔使用到最大窗口時,1萬個連接就會占用內(nèi)存到幾百G了,這就限制了高并發(fā)場景的使用,公平性也得不到保證。我們希望的場景是,在并發(fā)連接比較少時,把緩存限制放大一些,讓每一個TCP連接開足馬力工作;當(dāng)并發(fā)連接很多時,此時系統(tǒng)內(nèi)存資源不足,那么就把緩存限制縮小一些,使每一個TCP連接的緩存盡量的小一些,以容納更多的連接。

linux為了實(shí)現(xiàn)這種場景,引入了自動調(diào)整內(nèi)存分配的功能,由tcp_moderate_rcvbuf配置決定,如下:

  1. net.ipv4.tcp_moderate_rcvbuf = 1 

默認(rèn)tcp_moderate_rcvbuf配置為1,表示打開了TCP內(nèi)存自動調(diào)整功能。若配置為0,這個功能將不會生效(慎用)。

另外請注意:當(dāng)我們在編程中對連接設(shè)置了SO_SNDBUF、SO_RCVBUF,將會使linux內(nèi)核不再對這樣的連接執(zhí)行自動調(diào)整功能!

那么,這個功能到底是怎樣起作用的呢?看以下配置:

 

  1. net.ipv4.tcp_rmem = 8192 87380 16777216 
  2. net.ipv4.tcp_wmem = 8192 65536 16777216 
  3. net.ipv4.tcp_mem = 8388608 12582912 16777216 

tcp_rmem[3]數(shù)組表示任何一個TCP連接上的讀緩存上限,其中 tcp_rmem[0]表示最小上限, tcp_rmem[1]表示初始上限(注意,它會覆蓋適用于所有協(xié)議的rmem_default配置), tcp_rmem[2]表示最大上限。

tcp_wmem[3]數(shù)組表示寫緩存,與tcp_rmem[3]類似,不再贅述。

tcp_mem[3]數(shù)組就用來設(shè)定TCP內(nèi)存的整體使用狀況,所以它的值很大(它的單位也不是字節(jié),而是頁--4K或者8K等這樣的單位!)。這3個值定義了TCP整體內(nèi)存的無壓力值、壓力模式開啟閥值、最大使用值。以這3個值為標(biāo)記點(diǎn)則內(nèi)存共有4種情況:

1、當(dāng)TCP整體內(nèi)存小于tcp_mem[0]時,表示系統(tǒng)內(nèi)存總體無壓力。若之前內(nèi)存曾經(jīng)超過了tcp_mem[1]使系統(tǒng)進(jìn)入內(nèi)存壓力模式,那么此時也會把壓力模式關(guān)閉。

這種情況下,只要TCP連接使用的緩存沒有達(dá)到上限(注意,雖然初始上限是tcp_rmem[1],但這個值是可變的,下文會詳述),那么新內(nèi)存的分配一定是成功的。

2、當(dāng)TCP內(nèi)存在tcp_mem[0]與tcp_mem[1]之間時,系統(tǒng)可能處于內(nèi)存壓力模式,例如總內(nèi)存剛從tcp_mem[1]之上下來;也可能是在非壓力模式下,例如總內(nèi)存剛從tcp_mem[0]以下上來。

此時,無論是否在壓力模式下,只要TCP連接所用緩存未超過tcp_rmem[0]或者tcp_wmem[0],那么都一定都能成功分配新內(nèi)存。否則,基本上就會面臨分配失敗的狀況。(注意:還有一些例外場景允許分配內(nèi)存成功,由于對于我們理解這幾個配置項(xiàng)意義不大,故略過。)

3、當(dāng)TCP內(nèi)存在tcp_mem[1]與tcp_mem[2]之間時,系統(tǒng)一定處于系統(tǒng)壓力模式下。其他行為與上同。

4、當(dāng)TCP內(nèi)存在tcp_mem[2]之上時,毫無疑問,系統(tǒng)一定在壓力模式下,而且此時所有的新TCP緩存分配都會失敗。

下圖為需要新緩存時內(nèi)核的簡化邏輯:

 

linux高性能網(wǎng)絡(luò)編程之tcp連接的內(nèi)存使用

當(dāng)系統(tǒng)在非壓力模式下,上面我所說的每個連接的讀寫緩存上限,才有可能增加,當(dāng)然最大也不會超過tcp_rmem[2]或者tcp_wmem[2]。相反,在壓力模式下,讀寫緩存上限則有可能減少,雖然上限可能會小于tcp_rmem[0]或者tcp_wmem[0]。

所以,粗略的總結(jié)下,對這3個數(shù)組可以這么看:

  1. 只要系統(tǒng)TCP的總體內(nèi)存超了 tcp_mem[2] ,新內(nèi)存分配都會失敗。
  2. tcp_rmem[0]或者tcp_wmem[0]優(yōu)先級也很高,只要條件1不超限,那么只要連接內(nèi)存小于這兩個值,就保證新內(nèi)存分配一定成功。
  3. 只要總體內(nèi)存不超過tcp_mem[0],那么新內(nèi)存在不超過連接緩存的上限時也能保證分配成功。
  4. tcp_mem[1]與tcp_mem[0]構(gòu)成了開啟、關(guān)閉內(nèi)存壓力模式的開關(guān)。在壓力模式下,連接緩存上限可能會減少。在非壓力模式下,連接緩存上限可能會增加,最多增加到tcp_rmem[2]或者tcp_wmem[2]。

 

責(zé)任編輯:未麗燕 來源: 今日頭條
相關(guān)推薦

2023-11-01 11:51:08

Linux性能優(yōu)化

2023-11-01 10:43:31

Linux高性能網(wǎng)絡(luò)編程

2024-03-18 13:43:20

Linux架構(gòu)

2023-11-01 11:59:13

2023-11-01 10:38:46

Linux高性能網(wǎng)絡(luò)編程

2015-04-24 09:48:59

TCPsocketsocket編程

2015-03-20 09:54:44

網(wǎng)絡(luò)編程面向連接無連接

2023-11-01 10:58:31

系統(tǒng)調(diào)用高性能網(wǎng)絡(luò)編程Linux

2023-11-01 11:40:46

Linux高性能網(wǎng)絡(luò)編程工具

2023-11-01 11:27:10

Linux協(xié)程

2024-10-16 11:03:30

Linux高性能編程

2024-08-06 08:22:18

2024-10-06 14:37:52

2024-09-03 09:15:37

2023-11-01 11:07:05

Linux高性能網(wǎng)絡(luò)編程線程

2019-11-08 14:47:49

TCPIP網(wǎng)絡(luò)

2023-11-01 11:20:57

2021-03-17 09:51:31

網(wǎng)絡(luò)編程TCP網(wǎng)絡(luò)協(xié)議

2023-11-01 11:13:58

Linux信號處理定時器

2022-03-21 14:13:22

Go語言編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號