Apache 性能最優化分析(下)
發表于:2007-05-25來源:作者:點擊數:
標簽:
有兩種實現這個特性的辦法:一是socket的SO_LINGER選項。但似乎是命中注定,在多數TCP/IP協議棧中它從來不能正確地實現。即使是在提供了正確實現的平臺(即 Linux 2.0.31)上,這種方法也要比第二種方法代價(指CPU時間)高得多。 大多數情況下,Apache在一
有兩種實現這個特性的辦法:一是socket的SO_LINGER選項。但似乎是命中注定,在多數TCP/IP協議棧中它從來不能正確地實現。即使是在提供了正確實現的平臺(即
Linux 2.0.31)上,這種方法也要比第二種方法代價(指CPU時間)高得多。
大多數情況下,Apache在一個叫lingering_close的函數中實現了它(在 http_main.c)。這個函數大致如下所示:
void lingering_close (int s)
{
char junk_buffer[2048];
/* shutdown the sending side */
shutdown (s, 1);
signal (SIGA
LRM, lingering_death);
alarm (30);
for (;;) {
select (s for reading, 2 second timeout);
if (error) break;
if (s is ready for reading) {
read (s, junk_buffer, sizeof (junk_buffer));
/* just toss away whatever is here */
}
}
close (s);
}
這自然增加了連接結束時的開銷,但它是可靠的實現所必需的。隨著HTTP/1.1的日益盛行,所有連接都是持久的,這種開銷將被眾多的連接請求抵消。如果您想冒險禁止這一特性的話,可以定義宏NO_LINGCLOSE,但這顯然是不被推薦的。實際上,由于在HTTP/1.0中持久的管道式連接越來越普遍,lingering_close幾乎是必須的選擇。(管道式連接非常高效,所以您還是希望支持它的吧)
5) 記分板文件 Apache利用一種叫做記分板(scoreboard)的技術在父、子進程間通訊。它的理想實現是在共享內存中。有的操作系統允許我們直接訪問共享內存,或者提供它們的確切端口。在這些系統中的典型實現就是共享內存記分板。其他的系統則將磁盤上的文件作為缺省實現。磁盤文件不僅低效而且不穩定(又沒有什么優勢)。請為您的操作系統仔細閱讀src/main/conf.h文件,并在其中尋找USE_MMAP_SCOREBOARD或者USE_SHMGET_SCOREBOARD。定義它們之一(以及相應的HAVE_MMAP和HAVE_SHMGET)將允許Apache使用共享內存。如果您系統的內存共享機制與眾不同,請編輯src/main/http_main.c并增加Apache所需的掛鉤函數(同時請把補丁寄給我們)
注:直到1.2版,Apache的Linux版才開始使用共享內存。這一疏忽使得以前版本的Apache在Linux上表現得很不理想。
DYNAMIC_MODULE_LIMIT 如果您不打算支持動態加載模塊的話(準備榨出最后一滴
性能的您可能希望如此),編譯
服務器時請設定參數-DDYNAMIC_MODULE_LIMIT=0。這將節省出為動態加載模塊而分配的內存。
附錄:對某次跟蹤狀況的詳細分析 本附錄描述了運行在Linux上的Apache 1.3系統調用的跟蹤情況。運行時(run-time)配置文件中除了必要的缺省選項外還增加了:
AllowOverride none
Options FollowSymLinks
被請求的文件是一個6K的靜態網頁,其中不包含特殊內容。對于非靜態或者伴隨有內容協商的請求,跟蹤結果將有明顯的不同(一些情況下會十分晦澀)。我們將首先列出完整的跟蹤結果,然后逐條進行分析。(它是由strace跟蹤程序生成的,其他類似的程序包括truss、ktrace和par)
accept(15, {sin_family=AF_INET, sin_port=htons(22283), sin_addr=inet_addr("127.0.0.1")}, [16]) = 3
flock(18, LOCK_UN) = 0
sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTER
RUPT}) = 0
getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
read(3, "GET /6k HTTP/1.0\r\nUser-Agent: "..., 4096) = 60
sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
time(NULL) = 873959960
gettimeofday({873959960, 404935}, NULL) = 0
stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
open("/home/dgaudet/ap/apachen/htdocs/6k", O_RDONLY) = 4
mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400ee000
writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389
close(4) = 0
time(NULL) = 873959960
write(17, "127.0.0.1 - - [10/Sep/1997:23:39"..., 71) = 71
gettimeofday({873959960, 417742}, NULL) = 0
times({tms_utime=5, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 446747
shutdown(3, 1 /* send */) = 0
oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
read(3, "", 2048) = 0
close(3) = 0
sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0
munmap(0x400ee000, 6144) = 0
flock(18, LOCK_EX) = 0
accept串行化: flock(18, LOCK_UN) = 0
...
flock(18, LOCK_EX) = 0 這兩個調用可以被上文提到的宏SINGLE_LISTEN_UNSERIALIZED_A
CCEPT去掉。
對信號SIGUSR1的處理: sigaction(SIGUSR1, {SIG_IGN}, {0x8059954, [], SA_INTERRUPT}) = 0
...
sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
...
sigaction(SIGUSR1, {0x8059954, [], SA_INTERRUPT}, {SIG_IGN}) = 0 引起它們的原因是"溫和重啟"(graceful restart --用SIGUSR1而不是SIGHUP使Apache重新啟動。這種方式為Apache留出了自行處理的余地--譯者注)。當父進程收到SIGUSR1后,它將把這一信號傳遞給所有子進程(同時遞增在共享內存中更新計數器的值"generation counter")。所有空閑的子進程(即在兩次連接之間的子進程)收到信號后將立即終止。所有處于持續連接(keep-alive)但在兩次請求之間的子進程也將立即終止。但處于連接中并等待第一次請求的子進程將不會立即終止。
為了說明它的必要性,請考慮一個瀏覽器對已關閉連接的處理。如果已關閉的連接是持續連接,而且下一個請求不是該連接的第一個請求,瀏覽器將不動聲色地建立另一個連接并重新發出請求。由于服務器任何時候都可能關閉一個持續連接(可能因為超時或者超過了最大請求數目),這樣處理是有必要的。但是,如果在回應第一個請求前連接就被關閉,瀏覽器通常會顯?quot;文檔中無數據"的對話框(或者顯示被折斷的圖片)。這是在假設服務器非正常終止(或者太忙)的情況下作出的反應。因此,Apache力圖避免在作出任何回應前就關閉連接。這就是處理SIGUSR1的原因。
盡管在理論上避免那三個調用是可行的,但在粗略的
測試中這樣的改進是微不足道的。
為了實現虛擬主機,Apache需要用本地socket地址接受連接:
getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0 在許多情況下都有可能去掉這個調用(比如沒有虛擬主機,或者在Listen命令中的地址不含通配符)。但目前尚未對此進行優化。
Apache關掉了Nagle算法: setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
考慮到John Heidemann的論文中提及的問題,請關注兩個time調用:
time(NULL) = 873959960
...
time(NULL) = 873959960 第一個出現在請求的開始,另一個用于寫入日志。前者是正確實現HTTP協議所必須的,后者的出現是因為通用日志格式指定了記錄中包含時間戳。自定義日志模塊能夠去掉這個調用?;蛘吣梢杂媚承┓椒ò褧r間移到共享內存中。參見補丁一節。
正如前文描述的那樣,ExtendedStatus On將引發兩次gettimeofday調用和一次times調用:
gettimeofday({873959960, 404935}, NULL) = 0
...
gettimeofday({873959960, 417742}, NULL) = 0
times({tms_utime=5, tms_stime=0, tms_cutime=0, tms_cstime=0}) = 446747 ExtendedStatus Off (即缺省值)將避免這些調用。
這個調用看起來也許有些奇怪:
stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
它用于生成CGI程序所需的PATH_INFO環境變量。事實上,對于指向/cgi-bin/printenv/foobar的某個請求,stat將被調用兩次。第一次調用查詢/home/dgaudet/ap/apachen/cgi-bin/printenv/foobar,但它并不存在;第二次調用查詢/home/dgaudet/ap/apachen/cgi-bin/printenv,它是存在的。無論如何,對于靜態請求而言,至少需要一次調用。文件尺寸和修改時間被用來生成HTTP頭標(比如Content-Length和Last-Modified)并用來實現協議的特色部分(比如If-Modified-Since)。一個聰明的服務器能夠對非靜態的請求避免調用stat,但是這樣的實現對于模塊化的Apache來說太難了。
所有靜態文件使用mmap: mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400ee000
...
munmap(0x400ee000, 6144) = 0 在一些系統上mmap小文件的效率不如直接讀取該文件。宏MMAP_THRESHOLD用來設置應用mmap時的最小文件尺寸。缺省值是0(但在SunOS4上的缺省值是8129。實驗證明這個值在該系統上比較理想)類似lmbench的工具可以幫助您在您的系統上進行優化設置。
您也許樂意在MMAP_SEGMENT_SIZE上做個實驗(缺省值32768)。它決定了被mmap的文件將以一次多少個字節寫出。Apache只在每次write之間重置客戶的超時時間,因此把這個值設得過大容易把帶寬較窄的用戶拒之門外--除非同時增加Timeout。
您的系統有可能根本不用mmap。如果是這樣的話,定義USE_MMAP_FILES和HAVE_MMAP也許會奏效(如果它真的有效請告訴我們)。
Apache盡全力避免在內存中拷貝數據。對任何請求的首次寫出都將借助writev合并頭標及第一塊數據:
writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245},{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389 當進行HTTP/1.1塊狀編碼時,Apache將生成最多為4個元素的writev。它的目標是將字節拷貝至內核,這是典型情況下必須做的事情(為了組裝
網絡數據包)。2.0.31之前的Linux并不進行合并,而是為每個元素生成一個數據包。因此升級系統是一個好主意。定義NO_WRITEV將阻止這種合并,但將使得塊狀編碼的性能很差。
日志文件的寫入工作 write(17, "127.0.0.1 - - [10/Sep/1997:23:39"..., 71] = 71 能夠被宏定義BUFFERED_LOGS推遲。這種情況下,在真正寫入文件之前,最多PIPE_BUF個字節(POSIX標準定義的常量)的日志信息將被緩存。由于寫入條目不是atomic的(就是說來自不同子進程的信息將混合在一起),因此跨越PIPE_BUF邊界的條目不會被分割。當子進程終止時,Apache用出色的方式將緩存排空。
延遲關閉引發了四個系統調用: shutdown(3, 1 /* send */) = 0
oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
read(3, "", 2048) = 0
close(3) = 0 這些在前文已經提及。
當我 們使用了-DSINGLE_LISTEN_UNSERIALIZED_ACCEPT、-DBUFFERED_LOGS 和 ExtendedStatus Off 的優化參數后,最終得到的跟蹤結果如下:
accept(15, {sin_family=AF_INET, sin_port=htons(22286), sin_addr=inet_addr("127.0.0.1")}, [16]) = 3
sigaction(SIGUSR1, {SIG_IGN}, {0x8058c98, [], SA_INTERRUPT}) = 0
getsockname(3, {sin_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("127.0.0.1")}, [16]) = 0
setsockopt(3, IPPROTO_TCP1, [1], 4) = 0
read(3, "GET /6k HTTP/1.0\r\nUser-Agent: "..., 4096) = 60
sigaction(SIGUSR1, {SIG_IGN}, {SIG_IGN}) = 0
time(NULL) = 873961916
stat("/home/dgaudet/ap/apachen/htdocs/6k", {st_mode=S_IFREG|0644, st_size=6144, ...}) = 0
open("/home/dgaudet/ap/apachen/htdocs/6k", O_RDONLY) = 4
mmap(0, 6144, PROT_READ, MAP_PRIVATE, 4, 0) = 0x400e3000
writev(3, [{"HTTP/1.1 200 OK\r\nDate: Thu, 11"..., 245}, {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 6144}], 2) = 6389
close(4) = 0
time(NULL) = 873961916
shutdown(3, 1 /* send */) = 0
oldselect(4, [3], NULL, [3], {2, 0}) = 1 (in [3], left {2, 0})
read(3, "", 2048) = 0
close(3) = 0
sigaction(SIGUSR1, {0x8058c98, [], SA_INTERRUPT}, {SIG_IGN}) = 0
munmap(0x400e3000, 6144) = 0
只剩下19個系統調用了。其中有四個很容易被移走,但沒有什么必要了。
附錄:可用的補丁 這里有一些1.3版的性能補丁。但隨著1.3.0版的發行它們可能有些過時。對掌握一點C語言的
知識的人來說,升級它們并不是難事。特別地:
- 有一個補丁去掉所有time(2)系統調用。
- 有一個補丁去掉mod_include中的許多系統調用,它們僅出于向后兼容而被少數站點所使用。
- 有一個補丁結合了上面兩個特性,并通過去掉一些特性使服務器加速。
附錄:預分支(Pre-Forking)模型 Unix上的Apache是應用了預分支模型的服務器。父進程的責任僅在于繁衍子進程,它從不響應來自socket的任何請求。真正處理連接的是子進程,每個子進程在終止之前會(逐一地)為多個連接服務。父進程根據服務器負載的變化(通過監視記分板,記分板由子進程負責保持同步)生成新的或者殺掉舊的子進程。
這種模型為服務器提供了其他模型所不具備的健壯。父進程的代碼非常簡單,它有足夠的信心保證在不出現錯誤的情況下持續運行。子進程就很復雜了,而且當您加入了第三方提供的模塊后,將冒segmentation fault和其他崩潰的危險。即便這樣的事情發生了,也只會影響到一個連接。父進程將繼續為請求服務,并迅速替換掉已經死亡的子進程。
預分支在不同的Unix之間有良好的可移植性。Apache向來將它作為重要的目標之一,并且將保持下去。
但預分支模型由于各種各樣的性能問題而飽受批判。主要的因素是分支進程帶來的負擔、上下文切換帶來的負擔和多個進程為內存帶來的負擔。另外它不能為請求提供有效的緩存機制(比如mmap文件池)。另有一些模型。JAWS project的論文對它們進行了詳細的分析。實際上,所有這些模型帶來的性能損失在不同操作系統上的差別迥異。
Apache的內核代碼已經支持多線程。NT上的Apache 1.3就是多線程的。至少有另外兩種實驗性的多線程Apache:一個基于1.3內核并運行在DCE上;另一個基于1.0內核,它使用了一套自定義的用戶級線程庫。它們都不是對公眾發行的。有一個已經發行了的Apache實驗版本:運行于Netscape運行時可移植(Portable Run Time)平臺上的1.3版,可以在此
下載(如果您準備使用它的話,歡迎您加入new-httpd郵件列表)。被重新設計的Apache2.0將包含抽象化的服務器模型,它使我們可以既支持預分支模型,又支持多種線程模型。
原文轉自:http://www.kjueaiud.com
老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月
|