作者:
ZDNET CHINA 特稿Monday, July 22 2002 4:12 PM
當(dāng)今國(guó)互聯(lián)網(wǎng)的飛速發(fā)展讓人們獲益匪淺,同時(shí)人們對(duì)于互聯(lián)網(wǎng)的期望值也變得越來(lái)越高。這就形成了一個(gè)矛盾,雖然互聯(lián)網(wǎng)的發(fā)展已經(jīng)是相當(dāng)迅猛的了,但 是人們還是期望從服務(wù)器到客戶終端的文件傳輸?shù)乃俣饶軌虮痊F(xiàn)在再快一些,這種要求(當(dāng)然是合理的要求)好像從來(lái)也滿足不了。在向人們?cè)儐?wèn)“一種什么樣的速 度對(duì)于數(shù)據(jù)傳輸來(lái)說(shuō)才是最理想的”問(wèn)題時(shí),幾乎每一次你都會(huì)得到一種不同的答案:有的人認(rèn)為數(shù)據(jù)傳輸?shù)乃俾试娇煸胶茫械娜藙t認(rèn)為數(shù)據(jù)傳輸?shù)乃俾手灰谀?夠容忍的限度之內(nèi)就可以了,而另一人則認(rèn)為一種數(shù)據(jù)傳輸時(shí)不會(huì)有數(shù)據(jù)損失的最快速率才是他們真正需要的,當(dāng)然,這里還有許多其它的回答,我就不一一贅述 了。
在現(xiàn)實(shí)中,對(duì)于這個(gè)有關(guān)數(shù)據(jù)傳輸?shù)膯?wèn)題是沒(méi)有一個(gè)統(tǒng)一的答案的。絕大多數(shù)的人在評(píng)價(jià)數(shù)據(jù)傳輸?shù)目炻龝r(shí),還是會(huì)使用“每秒鐘傳輸?shù)恼鬃止?jié)數(shù)”來(lái)作為一 種衡量的標(biāo)準(zhǔn)。但是,數(shù)據(jù)傳輸快慢的真正衡量標(biāo)準(zhǔn)卻是CPU對(duì)于每一個(gè)傳輸?shù)恼鬃止?jié)所花費(fèi)占用的時(shí)間。對(duì)于實(shí)時(shí)應(yīng)用軟件,尤其是那些傳輸視頻或者音頻數(shù)據(jù) 流的軟件,最不想出現(xiàn)的一種狀況就是所謂的“延遲”。CPU執(zhí)行任務(wù)的時(shí)候如果沒(méi)有任何的效率,那么,要實(shí)現(xiàn)對(duì)協(xié)議層(protocol-level)的負(fù)載平衡(load balancing)以及將主機(jī)的IP名私人化的支持(一種叫做Virtuozzo的操作系統(tǒng)虛擬化技術(shù))都是不太可能的。一個(gè)加裝了Virtuozzo的主機(jī)內(nèi)能夠裝得下數(shù)以千計(jì)的網(wǎng)絡(luò)站點(diǎn),所以,盡可能的減少用來(lái)處理數(shù)據(jù)傳輸占據(jù)的CPU時(shí)間是非常重要的。
Sendfile()是一種嶄新的操作系統(tǒng)核心,這種新核心能夠幫助人們解決上邊所描述的那些問(wèn)題。而且,這種新內(nèi)核對(duì)于UNIX, Linux, Solaris 8操作系統(tǒng)來(lái)說(shuō)都是適用的。從技術(shù)角度來(lái)看,sendfile()是磁盤和傳輸控制協(xié)議(TCP)之間的一種系統(tǒng)呼叫,但是sendfile()還能夠用 來(lái)在兩個(gè)文件夾之間移動(dòng)數(shù)據(jù)。在各種不同的操作系統(tǒng)上實(shí)現(xiàn)sendfile()都會(huì)有所不同,當(dāng)然這種不同只是極為細(xì)微的差別。通常來(lái)說(shuō),我們會(huì)假定所使 用的操作系統(tǒng)是Linux核心2.4版本。
系統(tǒng)呼叫的原型有如下幾種:
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count)
- in_fd是一種用來(lái)讀文件的文件描述符。
- out_fd是一種用來(lái)寫文件的描述符。
- Offset是一種指向被輸入文件變量位置的指針,sendfile()將會(huì)從它所指向的位置開始數(shù)據(jù)的讀取。
- Count表示的是兩個(gè)文件描述符之間數(shù)據(jù)拷貝的字節(jié)數(shù)。
sendfile()的威力在于,它為大家提供了一種訪問(wèn)當(dāng)前不斷膨脹的Linux網(wǎng)絡(luò)堆棧的機(jī)制。這種機(jī)制叫做“零拷貝(zero- copy)”,這種機(jī)制可以把“傳輸控制協(xié)議(TCP)”框架直接的從主機(jī)存儲(chǔ)器中傳送到網(wǎng)卡的緩存塊(network card buffers)中去。為了更好的理解“零拷貝(zero-copy)”以及sendfile(),讓我們回憶一下以前我們?cè)趥魉臀募r(shí)所需要執(zhí)行的那些 步驟。首先,一塊在用戶機(jī)器存儲(chǔ)器內(nèi)用于數(shù)據(jù)緩沖的位置先被確定了下來(lái)。然后,我們必須使用read()這條系統(tǒng)呼叫來(lái)把數(shù)據(jù)從文件中拷貝到前邊已經(jīng)準(zhǔn)備 好的那個(gè)緩沖區(qū)中去。(在通常的情況下,這個(gè)操做會(huì)把數(shù)據(jù)從磁盤上拷貝到操作系統(tǒng)的高速緩沖存儲(chǔ)器中去,然后才會(huì)把數(shù)據(jù)從高速緩沖存儲(chǔ)器中拷貝至用戶空間 中去,這種過(guò)程就是所謂的“上下文切換”。)在完成了上述的那些步驟之后,我們得使用write()系統(tǒng)呼叫來(lái)將緩沖區(qū)中的內(nèi)容發(fā)送到網(wǎng)絡(luò)上去,程序段如 下所示:
intout_fd, intin_fd;
char buffer[BUFLEN];
…
/* unsubstantial code skipped for clarity */
…
read(in_fd, buffer, BUFLEN); /* syscall, make context switch */
write(out_fd, buffer, BUFLEN); /* syscall, make context switch */
操作系統(tǒng)核心不得不把所有的數(shù)據(jù)至少都拷貝兩次:先是從核心空間到用戶空間的拷貝,然后還得再?gòu)挠脩艨臻g拷貝回核心空間。每一次操做都需要上下文切 換(context-switch)的這個(gè)步驟,其中包含了許多復(fù)雜的高度占用CPU的操作。系統(tǒng)自帶的工具vmstat能夠用來(lái)在絕大多數(shù)UNIX以及 與其類似的操作系統(tǒng)上顯示當(dāng)前的“上下文切換(context-switch)”速率。請(qǐng)看叫做“CS”的那一欄,有相當(dāng)一部分的上下文切換是發(fā)生在取樣 期間的。用不同類型的方式進(jìn)行裝載可以讓使用者清楚的看到使用這些參數(shù)進(jìn)行裝載時(shí)的不同效果。
關(guān)于切換過(guò)程的一些具體細(xì)節(jié)
讓我們向著有關(guān)上下文切換過(guò)程的更深層次挖掘,這樣做能夠讓我們更好的理解有關(guān)切換的一些問(wèn)題。這里有許多種有關(guān)從用戶空間呼叫系統(tǒng)的操作的方法。 舉個(gè)例子來(lái)說(shuō),將虛擬內(nèi)存頁(yè)面從用戶空間中切換到核心,然后再切換回去的這種操作是必不可少的。這種操作過(guò)程需要的系統(tǒng)花銷是相當(dāng)大的(尤其是在CPU周 期的占用方面)。這種操做是通過(guò)使用叫做全局描述符臺(tái)面(Global Descriptor Table)以及局部描述符臺(tái)面(Local Descriptor Table)的存儲(chǔ)器控制臺(tái)來(lái)實(shí)現(xiàn)的。另外的一種結(jié)構(gòu)被稱之為TSS (Task Status Segment任務(wù)狀態(tài)段)的工具也需要大家給予足夠的重視。
此外,還有一些隱藏的非常“昂貴”的操作沒(méi)有被上下文切換程序呼叫出來(lái)。我們能夠通過(guò)虛擬內(nèi)存需要虛擬物理地址翻譯操作的支持才能實(shí)現(xiàn)的例子來(lái)說(shuō)明 這些隱藏操作的存在。這種翻譯所需要的數(shù)據(jù)也是存儲(chǔ)于存儲(chǔ)器中的,所以,CPU每一次對(duì)這些數(shù)據(jù)存儲(chǔ)位置的請(qǐng)求都需要對(duì)主存儲(chǔ)器進(jìn)行一次或多次的訪問(wèn)(這 是為了讀取翻譯臺(tái)入口),這是除了需要獲取的那些數(shù)據(jù)外還要進(jìn)行的一些操作。現(xiàn)在的CPU通常都包含了一個(gè)翻譯緩存,其縮寫為TLB。TLB是作為頁(yè)面入 口來(lái)工作的,其中存儲(chǔ)了最近訪問(wèn)過(guò)的對(duì)象。(這是對(duì)TLB最為簡(jiǎn)單的解釋,其具體的解釋應(yīng)為:OLE庫(kù)文件,其中存放了OLE自動(dòng)化對(duì)象的數(shù)據(jù)類型、模塊 和接口定義,自動(dòng)化服務(wù)器通過(guò)TLB文件就能了解自動(dòng)化對(duì)象的使用方法。)TLB高速緩沖存儲(chǔ)器擁有巨大的潛在花費(fèi),其中包括了幾個(gè)存儲(chǔ)器的訪問(wèn)操作以及 頁(yè)面錯(cuò)誤處理器的實(shí)行操作。在拷貝大量數(shù)據(jù)的時(shí)候,會(huì)對(duì)TLB高速緩沖存儲(chǔ)器產(chǎn)生大量的消耗。在這個(gè)時(shí)候,TLB高速緩沖存儲(chǔ)器里邊會(huì)被要拷貝的頁(yè)面數(shù)據(jù) 占據(jù)的容不下任何別的其它數(shù)據(jù)。
在有了sendfile()零拷貝(zero-copy)之后,如果可能的話,通過(guò)使用直接存儲(chǔ)器訪問(wèn)(Direct Memory Access)的硬件設(shè)備,數(shù)據(jù)從磁盤讀取到操作系統(tǒng)高速緩沖存儲(chǔ)器中會(huì)變得非常之迅速。而TLB高速緩沖存儲(chǔ)器則被完整無(wú)缺的放在那里,沒(méi)有充斥任何有 關(guān)數(shù)據(jù)傳輸?shù)奈募?yīng)用軟件在使用sendfile() primitive的時(shí)候會(huì)有很高的性能表現(xiàn),這是因?yàn)橄到y(tǒng)呼叫沒(méi)有直接的指向存儲(chǔ)器,因此,就提高了傳輸數(shù)據(jù)的性能。通常來(lái)說(shuō),要被傳輸?shù)臄?shù)據(jù)都是從系 統(tǒng)緩沖存儲(chǔ)器中直接讀取的,其間并沒(méi)有進(jìn)行上下文切換的操作,也沒(méi)有垃圾數(shù)據(jù)占據(jù)高速緩沖存儲(chǔ)器。因此,在服務(wù)器應(yīng)用程序中使用sendfile()能夠 顯著的減少對(duì)CPU的占用。
在我們的這個(gè)例子中取代帶有mmap() 的read()不會(huì)讓事情有什么顯著的變化。然而,mmap系統(tǒng)呼叫的請(qǐng)求是從文件中(或者從其它的一些對(duì)象中)生成一些連接信息,這些都是從在虛擬內(nèi)存 中的文件描述符中指定的。試圖從虛擬內(nèi)存中讀取數(shù)據(jù)會(huì)產(chǎn)生一些磁盤操作。因?yàn)橄到y(tǒng)會(huì)把含有映射內(nèi)容的存儲(chǔ)器直接的寫入而不會(huì)調(diào)用read()以及緩存定 位,所以我們能夠消除磁盤讀取操作。然而,這種操作會(huì)導(dǎo)致TLB高速緩沖存儲(chǔ)器的過(guò)熱,所以CPU在裝載每個(gè)字節(jié)的傳輸時(shí)的負(fù)荷會(huì)稍微加大一些。
零拷貝的方法以及應(yīng)用軟件的開發(fā)
只要有可能,在任何時(shí)候,對(duì)性能要求非常苛刻的客戶服務(wù)器應(yīng)用程序的開發(fā)都會(huì)使用到零拷貝方法(The zero-copy approach)。設(shè)想一下,當(dāng)我們需要在一個(gè)單獨(dú)的服務(wù)器上運(yùn)行超過(guò)一千個(gè)分散的擁有私人IP地址的Apache網(wǎng)絡(luò)服務(wù)器時(shí),就可以使用到 Virtuozzo技術(shù)了。為了達(dá)到這個(gè)目的,我們每一秒鐘都不得不在傳輸控制協(xié)議的層面上處理數(shù)以千計(jì)的請(qǐng)求來(lái)列出客戶的請(qǐng)求并且把主機(jī)名頁(yè)開列出來(lái)。 如果沒(méi)有經(jīng)過(guò)最優(yōu)化的處理和零拷貝(zero-copy)支持的話,這對(duì)于處理器來(lái)說(shuō)將是一項(xiàng)繁重而且復(fù)雜的任務(wù),甚至都超過(guò)了網(wǎng)絡(luò)對(duì)它的負(fù)荷。零拷貝 sendfile()的實(shí)現(xiàn)是基于9K-http-requests-per-second速率的,甚至在速度相對(duì)較慢的Pentium II350 MHz的處理器上也是一樣。
然而,零拷貝(zero-copy) sendfile()并不是能解決所有問(wèn)題的“萬(wàn)能藥”。部分的,減少網(wǎng)絡(luò)操作的數(shù)量,sendfile()系統(tǒng)呼叫應(yīng)該和TCP/IP中的 TCP_CORK選項(xiàng)在一起共同的使用。在我們以后的文章中將會(huì)討論應(yīng)用軟件從此選項(xiàng)中獲得的好處,以及討論其它的一些相關(guān)問(wèn)題。
下面再附上兩篇文章供參考。。。
促進(jìn)高效數(shù)據(jù)傳輸?shù)腡CP/IP選項(xiàng)
作者: ZDNET CHINA 特稿
2002-07-22 04:11 PM
在前一篇文章里,我們討論了以下問(wèn)題:如何采用sendfile()系統(tǒng)函數(shù)降低從磁盤到網(wǎng)絡(luò)的數(shù)據(jù)傳輸負(fù)載。接下來(lái)我們繼續(xù)討論涉及網(wǎng)絡(luò)連接控制 的另一問(wèn)題,同時(shí)希望通過(guò)對(duì)這一問(wèn)題的討論能有助于在實(shí)際環(huán)境下把sendfile()的功能最大化,這就是如何設(shè)置TCP/IP選項(xiàng)來(lái)控制套接字的行 為。
TCP/IP數(shù)據(jù)傳輸
TCP/IP網(wǎng)絡(luò)的數(shù)據(jù)傳輸通常建立在數(shù)據(jù)塊的基礎(chǔ)之上。從程序員 的觀點(diǎn)來(lái)看,發(fā)送數(shù)據(jù)意味著發(fā)出(或者提交)一系列“發(fā)送數(shù)據(jù)塊”的請(qǐng)求。在系統(tǒng)級(jí),發(fā)送單個(gè)數(shù)據(jù)塊可以通過(guò)調(diào)用系統(tǒng)函數(shù)write() 或者sendfile() 來(lái)完成。在網(wǎng)絡(luò)級(jí)可以看到更多的數(shù)據(jù)塊,通常把它們叫做幀,幀再被包裝上一定字節(jié)長(zhǎng)度的報(bào)頭然后通過(guò)線路在網(wǎng)絡(luò)上傳輸。幀及其報(bào)頭內(nèi)部的信息是由若干協(xié)議 層定義的,從OSI參考模型的物理層到應(yīng)用層都可能會(huì)牽扯到。
因?yàn)樵诰W(wǎng)絡(luò)連接中是由程序員來(lái)選擇最適當(dāng)?shù)膽?yīng)用協(xié)議,所以網(wǎng)絡(luò)包的長(zhǎng)度和順序都在程序員的控制之下。同樣的,程序員還必須選擇這個(gè)協(xié)議在軟件中得以實(shí)現(xiàn)的方式。TCP/IP協(xié)議自身已經(jīng)有了多種可互操作的實(shí)現(xiàn),所以在雙方通信時(shí),每一方都有它自身的低級(jí)行為,這也是程序員所應(yīng)該知道的情況。
通常情況下,程序員不必關(guān)心操作系統(tǒng)和網(wǎng)絡(luò)協(xié)議棧發(fā)送和接收網(wǎng)絡(luò)數(shù)據(jù)的方法。系統(tǒng)內(nèi)置算法定義了低級(jí)的數(shù)據(jù)組織和傳輸方式;然而,影響這些算法的行為以及對(duì)網(wǎng)絡(luò)連接施加更大強(qiáng)度控制能力的方法也是有的。例如,如果某個(gè)應(yīng)用協(xié)議使用了超時(shí)和重發(fā)機(jī)制,程序員就可以采取一定措施設(shè)定或者獲取超時(shí)參數(shù)。他或她還可能需要增加發(fā)送和接收緩沖區(qū)的大小來(lái)保證網(wǎng)絡(luò)上的信息流動(dòng)不會(huì)中斷。改變TCP/IP協(xié)議棧行為的一般的方法是采用所謂的TCP/IP選項(xiàng)。下面就讓我們來(lái)看一看你該如何使用這些選項(xiàng)來(lái)優(yōu)化數(shù)據(jù)傳輸。
TCP/IP選項(xiàng)
有好幾種選項(xiàng)都能改變TCP/IP協(xié)議棧的行為。使用這些選擇能對(duì)在同一計(jì)算機(jī)上運(yùn)行的其他應(yīng)用程序產(chǎn)生不利的影響,因此普通用戶通常是不能使用這些選項(xiàng)的(除了root用戶以外)。我們?cè)谶@里主要討論能改變單個(gè)連接操作(用TCP/IP的術(shù)語(yǔ)來(lái)說(shuō)就是套接字)的選項(xiàng)。
ioctl風(fēng)格的getsockopt()和setsockopt()系統(tǒng)函數(shù)都提供了控制套接字行為的方式。比方說(shuō),為了在Linux上設(shè)置TCP_NODELAY選項(xiàng),你可以如下編寫代碼:
intfd, on = 1;
…
/* 此處是創(chuàng)建套接字等操作,出于篇幅的考慮省略*/
…
setsockopt (fd, SOL_TCP, TCP_NODELAY, &on, sizeof (on));
盡管有許多TCP選項(xiàng)可供程序員操作,而我們卻最關(guān)注如何處置其中的兩個(gè)選項(xiàng),它們是TCP_NODELAY 和 TCP_CORK,這兩個(gè)選項(xiàng)都對(duì)網(wǎng)絡(luò)連接的行為具有重要的作用。許多UNIX系統(tǒng)都實(shí)現(xiàn)了TCP_NODELAY選項(xiàng),但是,TCP_CORK則是Linux系統(tǒng)所獨(dú)有的而且相對(duì)較新;它首先在內(nèi)核版本2.4上得以實(shí)現(xiàn)。此外,其他UNIX系統(tǒng)版本也有功能類似的選項(xiàng),值得注意的是,在某種由BSD派生的系統(tǒng)上的TCP_NOPUSH選項(xiàng)其實(shí)就是TCP_CORK的一部分具體實(shí)現(xiàn)。
TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle化”,Nagle化在這里的含義是采用Nagle算法把較小的包組裝為更大的幀。John Nagle是Nagle算法的發(fā)明人,后者就是用他的名字來(lái)命名的,他在1984年首次用這種方法來(lái)嘗試解決福特汽車公司的網(wǎng)絡(luò)擁塞問(wèn)題(欲了解詳情請(qǐng)參看IETF RFC 896)。他解決的問(wèn)題就是所謂的silly window syndrome ,中文稱“愚蠢窗口癥候群”,具體含義是,因?yàn)槠毡榻K端應(yīng)用程序每產(chǎn)生一次擊鍵操作就會(huì)發(fā)送一個(gè)包,而典型情況下一個(gè)包會(huì)擁有一個(gè)字節(jié)的數(shù)據(jù)載荷以及40個(gè)字節(jié)長(zhǎng)的包頭,于是產(chǎn)生4000%的過(guò)載,很輕易地就能令網(wǎng)絡(luò)發(fā)生擁塞,。 Nagle化后來(lái)成了一種標(biāo)準(zhǔn)并且立即在因特網(wǎng)上得以實(shí)現(xiàn)。它現(xiàn)在已經(jīng)成為缺省配置了,但在我們看來(lái),有些場(chǎng)合下把這一選項(xiàng)關(guān)掉也是合乎需要的。
現(xiàn)在讓我們假設(shè)某個(gè)應(yīng)用程序發(fā)出了一個(gè)請(qǐng)求,希望發(fā)送小塊數(shù)據(jù)。我們可以選擇立即發(fā)送數(shù)據(jù)或者等待產(chǎn)生更多的數(shù)據(jù)然后再一次發(fā)送兩種策略。如果我們馬上發(fā)送數(shù)據(jù),那么交互性的 以及客戶/服務(wù)器型的應(yīng)用程序?qū)O大地受益。例如,當(dāng)我們正在發(fā)送一個(gè)較短的請(qǐng)求并且等候較大的響應(yīng)時(shí),相關(guān)過(guò)載與傳輸?shù)臄?shù)據(jù)總量相比就會(huì)比較低,而且, 如果請(qǐng)求立即發(fā)出那么響應(yīng)時(shí)間也會(huì)快一些。以上操作可以通過(guò)設(shè)置套接字的TCP_NODELAY選項(xiàng)來(lái)完成,這樣就禁用了Nagle算法。
另外一種情況則需要我們等到數(shù)據(jù)量達(dá)到最大時(shí)才通過(guò)網(wǎng)絡(luò)一次發(fā)送全部數(shù)據(jù),這種數(shù)據(jù)傳輸方式有益于大量數(shù)據(jù)的通信性能,典型的應(yīng)用就是文件服務(wù)器。應(yīng)用Nagle算法在這種情況下就會(huì)產(chǎn)生問(wèn)題。但是,如果你正在發(fā)送大量數(shù)據(jù),你可以設(shè)置TCP_CORK選項(xiàng)禁用Nagle化,其方式正好同TCP_NODELAY相反(TCP_CORK 和 TCP_NODELAY 是互相排斥的)。下面就讓我們仔細(xì)分析下其工作原理。
假設(shè)應(yīng)用程序使用sendfile()函數(shù)來(lái)轉(zhuǎn)移大量數(shù)據(jù)。應(yīng)用協(xié)議通常要求發(fā)送某些信息來(lái)預(yù)先解釋數(shù)據(jù),這些信息其實(shí)就是報(bào)頭內(nèi)容。典型情況下報(bào)頭很小,而且套接字上設(shè)置了TCP_NODELAY。有報(bào)頭的包將被立即傳輸,在某些情況下(取決于內(nèi)部的包計(jì)數(shù)器),因?yàn)檫@個(gè)包成功地被對(duì)方收到后需要請(qǐng)求對(duì)方確認(rèn)。這樣,大量數(shù)據(jù)的傳輸就會(huì)被推遲而且產(chǎn)生了不必要的網(wǎng)絡(luò)流量交換。
但是,如果我們?cè)谔捉幼稚显O(shè)置了TCP_CORK(可以比喻為在管道上插入“塞子”)選項(xiàng),具有報(bào)頭的包就會(huì)填補(bǔ)大量的數(shù)據(jù),所有的數(shù)據(jù)都根據(jù)大小自動(dòng)地通過(guò)包傳輸出去。當(dāng)數(shù)據(jù)傳輸完成時(shí),最好取消TCP_CORK 選項(xiàng)設(shè)置給連接“拔去塞子”以便任一部分的幀都能發(fā)送出去。這同“塞住”網(wǎng)絡(luò)連接同等重要。
總而言之,如果你肯定能一起發(fā)送多個(gè)數(shù)據(jù)集合(例如HTTP響應(yīng)的頭和正文),那么我們建議你設(shè)置TCP_CORK選項(xiàng),這樣在這些數(shù)據(jù)之間不存在延遲。能極大地有益于WWW、FTP以及文件服務(wù)器的性能,同時(shí)也簡(jiǎn)化了你的工作。示例代碼如下:
intfd, on = 1;
…
/* 此處是創(chuàng)建套接字等操作,出于篇幅的考慮省略*/
…
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */
write (fd, …);
fprintf (fd, …);
sendfile (fd, …);
write (fd, …);
sendfile (fd, …);
…
on = 0;
setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* 拔去塞子 */
不幸的是,許多常用的程序并沒(méi)有考慮到以上問(wèn)題。例如,Eric Allman編寫的sendmail就沒(méi)有對(duì)其套接字設(shè)置任何選項(xiàng)。
Apache HTTPD是因特網(wǎng)上最流行的Web服務(wù)器,它的所有套接字就都設(shè)置了TCP_NODELAY選項(xiàng),而且其性能也深受大多數(shù)用戶的滿意。這是為什么呢?答案就在于實(shí)現(xiàn)的差別之上。 由BSD衍生的TCP/IP協(xié)議棧(值得注意的是FreeBSD)在這種狀況下的操作就不同。當(dāng)在TCP_NODELAY 模式下提交大量小數(shù)據(jù)塊傳輸時(shí),大量信息將按照一次write()函數(shù)調(diào)用發(fā)送一塊數(shù)據(jù)的方式發(fā)送出去。然而,因?yàn)樨?fù)責(zé)請(qǐng)求交付確認(rèn)的記數(shù)器是面向字節(jié)而 非面向包(在Linux上)的,所以引入延遲的概率就降低了很多。結(jié)果僅僅和全部數(shù)據(jù)的大小有關(guān)系。而 Linux 在第一包到達(dá)之后就要求確認(rèn),F(xiàn)reeBSD則在進(jìn)行如此操作之前會(huì)等待好幾百個(gè)包。
在Linux系統(tǒng)上,TCP_NODELAY的效果同習(xí)慣于BSD TCP/IP協(xié)議棧的開發(fā)者所期望的效果有很大不同,而且在Linux上的Apache性能表現(xiàn)也會(huì)更差些。其他在Linux上頻繁采用TCP_NODELAY的應(yīng)用程序也有同樣的問(wèn)題。
相得益彰
你的數(shù)據(jù)傳輸并不需要總是準(zhǔn)確地遵守某一選項(xiàng)或者其它選擇。在那種情況下,你可能想要采取更為靈活的措施來(lái)控制網(wǎng)絡(luò)連接:在發(fā)送一系列當(dāng)作單一消息的數(shù)據(jù)之前設(shè)置TCP_CORK,而且在發(fā)送應(yīng)立即發(fā)出的短消息之前設(shè)置TCP_NODELAY。
把零拷貝和sendfile() 系統(tǒng)函數(shù)結(jié)合起來(lái)(前文有述)可以顯著地提升系統(tǒng)整體效率并且降低CPU負(fù)載。我們采用這一技術(shù)為Swsoft’s Virtuozzo公司開發(fā)了基于名稱的主機(jī)托管子系統(tǒng),實(shí)踐經(jīng)驗(yàn)表明,該技術(shù)可以在裝備350-MHz Pentium II CPU的PC上實(shí)現(xiàn)每秒9000個(gè)HTTP請(qǐng)求,這一成績(jī)?cè)谝郧皫缀跏遣豢赡軐?shí)現(xiàn)的。性能上的提高顯而易見。
利用TCP/IP選項(xiàng)優(yōu)化數(shù)據(jù)傳輸(第2部分)
作者: BUILDER.COM
2002-07-22 04:15 PM
上回,我們對(duì)TCP_CORK選項(xiàng)如何減少網(wǎng)絡(luò)傳輸包的數(shù)量做了一番原理性的解釋。減少網(wǎng)絡(luò)流量當(dāng)然是非常重要的優(yōu)化舉措之一,不過(guò)這種手段也僅僅是實(shí)現(xiàn)高性能網(wǎng)絡(luò)數(shù)據(jù)傳輸領(lǐng)域的一個(gè)方面。其他TCP選項(xiàng)也可能顯著提供傳輸性能同時(shí)在某些條件下減少服務(wù)器的響應(yīng)時(shí)間延遲。下面就讓我們來(lái)了解一些此類選項(xiàng)。
TCP_DEFER_ACCEPT
我們首先考慮的第1個(gè)選項(xiàng)是TCP_DEFER_ACCEPT(這是Linux系統(tǒng)上的叫法,其他一些操作系統(tǒng)上也有同樣的選項(xiàng)但使用不同的名字)。為了理解TCP_DEFER_ACCEPT選項(xiàng)的具體思想,我們有必要大致闡述一下典型的HTTP客戶/服務(wù)器交互過(guò)程。請(qǐng)回想下TCP是如何與傳輸數(shù)據(jù)的目標(biāo)建立連接的。在網(wǎng)絡(luò)上,在分離的單元之間傳輸?shù)男畔⒎Q為IP包(或IP 數(shù)據(jù)報(bào))。一個(gè)包總有一個(gè)攜帶服務(wù)信息的包頭,包頭用于內(nèi)部協(xié)議的處理,并且它也可以攜帶數(shù)據(jù)負(fù)載。服務(wù)信息的典型例子就是一套所謂的標(biāo)志,它把包標(biāo)記代表TCP/IP協(xié)議棧內(nèi)的特殊含義,例如收到包的成功確認(rèn)等等。通常,在經(jīng)過(guò)“標(biāo)記”的包里攜帶負(fù)載是完全可能的,但有時(shí),內(nèi)部邏輯迫使TCP/IP協(xié)議棧發(fā)出只有包頭的IP包。這些包經(jīng)常會(huì)引發(fā)討厭的網(wǎng)絡(luò)延遲而且還增加了系統(tǒng)的負(fù)載,結(jié)果導(dǎo)致網(wǎng)絡(luò)性能在整體上降低。
現(xiàn)在服務(wù)器創(chuàng)建了一個(gè)套接字同時(shí)等待連接。TCP/IP式的連接過(guò)程就是所謂“3次握手”。首先,客戶程序發(fā)送一個(gè)設(shè)置SYN標(biāo)志而且不帶數(shù)據(jù)負(fù)載 的TCP包(一個(gè)SYN包)。服務(wù)器則以發(fā)出帶SYN/ACK標(biāo)志的數(shù)據(jù)包(一個(gè)SYN/ACK包)作為剛才收到包的確認(rèn)響應(yīng)。客戶隨后發(fā)送一個(gè)ACK包 確認(rèn)收到了第2個(gè)包從而結(jié)束連接過(guò)程。在收到客戶發(fā)來(lái)的這個(gè)SYN/ACK包之后,服務(wù)器會(huì)喚醒一個(gè)接收進(jìn)程等待數(shù)據(jù)到達(dá)。當(dāng)3次握手完成后,客戶程序即 開始把“有用的”的數(shù)據(jù)發(fā)送給服務(wù)器。通常,一個(gè)HTTP請(qǐng)求的量是很小的而且完全可以裝到一個(gè)包里。但是,在以上的情況下,至少有4個(gè)包將用來(lái)進(jìn)行雙向 傳輸,這樣就增加了可觀的延遲時(shí)間。此外,你還得注意到,在“有用的”數(shù)據(jù)被發(fā)送之前,接收方已經(jīng)開始在等待信息了。
為了減輕這些問(wèn)題所帶來(lái)的影響,Linux(以及其他的一些操作系統(tǒng))在其TCP實(shí)現(xiàn)中包括了TCP_DEFER_ACCEPT選項(xiàng)。它們?cè)O(shè)置在偵 聽套接字的服務(wù)器方,該選項(xiàng)命令內(nèi)核不等待最后的ACK包而且在第1個(gè)真正有數(shù)據(jù)的包到達(dá)才初始化偵聽進(jìn)程。在發(fā)送SYN/ACK包之后,服務(wù)器就會(huì)等待 客戶程序發(fā)送含數(shù)據(jù)的IP包。現(xiàn)在,只需要在網(wǎng)絡(luò)上傳送3個(gè)包了,而且還顯著降低了連接建立的延遲,對(duì)HTTP通信而言尤其如此。
這一選項(xiàng)在好些操作系統(tǒng)上都有相應(yīng)的對(duì)等物。例如,在FreeBSD上,同樣的行為可以用以下代碼實(shí)現(xiàn):
/* 為明晰起見,此處略去無(wú)關(guān)代碼 */
struct accept_filter_arg af = { "dataready", "" };
setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af));
這個(gè)特征在FreeBSD上叫做“接受過(guò)濾器”,而且具有多種用法。不過(guò),在幾乎所有的情況下其效果與TCP_DEFER_ACCEPT是一樣的: 服務(wù)器不等待最后的ACK包而僅僅等待攜帶數(shù)據(jù)負(fù)載的包。要了解該選項(xiàng)及其對(duì)高性能Web服務(wù)器的重要意義的更多信息請(qǐng)參考Apache文檔上的有關(guān)內(nèi) 容。
就HTTP客戶/服務(wù)器交互而言,有可能需要改變客戶程序的行為。客戶程序?yàn)槭裁匆l(fā)送這種“無(wú)用的”ACK包呢?這是因?yàn)椋琓CP協(xié)議棧無(wú)法知道 ACK包的狀態(tài)。如果采用FTP而非HTTP,那么客戶程序直到接收了FTP服務(wù)器提示的數(shù)據(jù)包之后才發(fā)送數(shù)據(jù)。在這種情況下,延遲的ACK將導(dǎo)致客戶/ 服務(wù)器交互出現(xiàn)延遲。為了確定ACK是否必要,客戶程序必須知道應(yīng)用程序協(xié)議及其當(dāng)前狀態(tài)。這樣,修改客戶行為就成為必要了。
對(duì)Linux客戶程序來(lái)說(shuō),我們還可以采用另一個(gè)選項(xiàng),它也被叫做TCP_DEFER_ACCEPT。我們知道,套接字分成兩種類型,偵聽套接字和 連接套接字,所以它們也各自具有相應(yīng)的TCP選項(xiàng)集合。因此,經(jīng)常同時(shí)采用的這兩類選項(xiàng)卻具有同樣的名字也是完全可能的。在連接套接字上設(shè)置該選項(xiàng)以后, 客戶在收到一個(gè)SYN/ACK包之后就不再發(fā)送ACK包,而是等待用戶程序的下一個(gè)發(fā)送數(shù)據(jù)請(qǐng)求;因此,服務(wù)器發(fā)送的包也就相應(yīng)減少了。
TCP_QUICKACK
阻止因發(fā)送無(wú)用包而引發(fā)延遲的另一個(gè)方法是使用TCP_QUICKACK選項(xiàng)。這一選項(xiàng)與 TCP_DEFER_ACCEPT不同,它不但能用作管理連接建立過(guò)程而且在正常數(shù)據(jù)傳輸過(guò)程期間也可以使用。另外,它能在客戶/服務(wù)器連接的任何一方設(shè) 置。如果知道數(shù)據(jù)不久即將發(fā)送,那么推遲ACK包的發(fā)送就會(huì)派上用場(chǎng),而且最好在那個(gè)攜帶數(shù)據(jù)的數(shù)據(jù)包上設(shè)置ACK 標(biāo)志以便把網(wǎng)絡(luò)負(fù)載減到最小。當(dāng)發(fā)送方肯定數(shù)據(jù)將被立即發(fā)送(多個(gè)包)時(shí),TCP_QUICKACK選項(xiàng)可以設(shè)置為0。對(duì)處于“連接”狀態(tài)下的套接字該選 項(xiàng)的缺省值是1,首次使用以后內(nèi)核將把該選項(xiàng)立即復(fù)位為1(這是個(gè)一次性的選項(xiàng))。
在某些情形下,發(fā)出ACK包則非常有用。ACK包將確認(rèn)數(shù)據(jù)塊的接收,而且,當(dāng)下一塊被處理時(shí)不至于引入延遲。這種數(shù)據(jù)傳輸模式對(duì)交互過(guò)程是相當(dāng)?shù)湫偷模驗(yàn)榇祟惽闆r下用戶的輸入時(shí)刻無(wú)法預(yù)測(cè)。在Linux系統(tǒng)上這就是缺省的套接字行為。
在上述情況下,客戶程序在向服務(wù)器發(fā)送HTTP請(qǐng)求,而預(yù)先就知道請(qǐng)求包很短所以在連接建立之后就應(yīng)該立即發(fā)送,這可謂HTTP的典型工作方式。既 然沒(méi)有必要發(fā)送一個(gè)純粹的ACK包,所以設(shè)置TCP_QUICKACK為0以提高性能是完全可能的。在服務(wù)器方,這兩種選項(xiàng)都只能在偵聽套接字上設(shè)置一 次。所有的套接字,也就是被接受呼叫間接創(chuàng)建的套接字則會(huì)繼承原有套接字的所有選項(xiàng)。
通過(guò)TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK選項(xiàng)的組合,參與每一HTTP交互的數(shù)據(jù)包數(shù)量將被降低到最小 的可接受水平(根據(jù)TCP協(xié)議的要求和安全方面的考慮)。結(jié)果不僅是獲得更快的數(shù)據(jù)傳輸和請(qǐng)求處理速度而且還使客戶/服務(wù)器雙向延遲實(shí)現(xiàn)了最小化。
小結(jié)
網(wǎng)絡(luò)程序的性能優(yōu)化顯然是一項(xiàng)復(fù)雜的任務(wù)。優(yōu)化技術(shù)包括:盡可能使用零拷貝、用TCP_CORK及其等價(jià)選項(xiàng)組裝適當(dāng)?shù)臄?shù)據(jù) 包、把傳輸數(shù)據(jù)包的數(shù)量最小化以及延遲優(yōu)化等。為了提升網(wǎng)絡(luò)、系統(tǒng)的性能和可伸縮性,有必要在程序代碼中聯(lián)合一致地采用以上各種可用方法。當(dāng)然,清楚了解 TCP/IP協(xié)議棧和操作系統(tǒng)的內(nèi)部工作原理也是必要的。