Apache2.0是一個多用途的web服務器,其設計在靈活性、可移植性和性能中求得平衡。雖然沒有在設計上刻意追求性能指標,但是Apache2.0仍然在許多現實環境中擁有很高的性能。
相比於Apache 1.3 ,2.0版本作了大量的優化來提升處理能力和可伸縮性,而且大多數的改進在默認狀態下就可以生效。但是,在編譯時和運行時,都有許多可以顯著提高性能的選擇。本文闡述在安裝Apache2.0時,服務器管理員可以改善性能的各種方法。其中,部分配置選擇可以使httpd更好地利用硬件和操作系統的兼容性,其他則是以功能換取速度。
top
硬件和操作系統
影響web服務器性能的最大的因素是內存。一個web服務器應該從不使用交換機制,因為交換產生的滯後使用戶總感覺"不夠快",所以用戶就可能去按"停止"和"刷新",從而帶來更大的負載。你可以,也應該,控制MaxClients的設置,以避免服務器產生太多的子進程而發生交換。這個過程很簡單:通過top命令計算出每個Apache進程平均消耗的內存,然後再為其它進程留出足夠多的內存。
其他因素就很普通了,裝一個足夠快的CPU,一個足夠快的網卡,幾個足夠快的硬盤,這裡說的"足夠快"是指能滿足實際應用的需求。
操作系統是很值得關注的又一個因素,已經被證實的很有用的經驗有:
*
選擇能夠得到的最新最穩定的版本並打好補丁。近年來,許多操作系統廠商都提供了可以顯著改善性能的TCP協議棧和線程庫。
*
如果你的操作系統支持sendfile()系統調用,則務必安裝帶有此功能的版本或補丁(對Linux來說,就是使用Linux2.4或更高版本,對Solaris8的早期版本,則需要安裝補丁)。在支持sendfile的系統中,Apache2可以更快地發送靜態內容而且占用較少的CPU時間。
top
運行時的配置
相關模塊 相關指令
* mod_dir
* mpm_common
* mod_status
* AllowOverride
* DirectoryIndex
* HostnameLookups
* EnableMMAP
* EnableSendfile
* KeepAliveTimeout
* MaxSpareServers
* MinSpareServers
* Options
* StartServers
HostnameLookups 和其他DNS考慮
在Apache1.3以前的版本中,HostnameLookups默認被設為 On 。它會帶來延遲,因為對每一個請求都需要作一次DNS查詢。在Apache1.3中,它被默認地設置為 Off 。如果需要日志文件提供主機名信息以生成分析報告,則可以使用日志後處理程序logresolve ,以完成DNS查詢,而客戶端無須等待。
推薦你最好是在其他機器上,而不是在web服務器上執行後處理和其他日志統計操作,以免影響服務器的性能。
如果你使用了任何"Allow from domain"或"Deny from domain"指令(也就是domain使用的是主機名而不是IP地址),則代價是要進行兩次DNS查詢(一次正向和一次反向,以確認沒有作假)。所以,為了得到最高的性能,應該避免使用這些指令(不用域名而用IP地址也是可以的)。
注意,可以把這些指令包含在<Location /server-status>段中使之局部化。在這種情況下,只有對這個區域的請求才會發生DNS查詢。下例禁止除了.html和.cgi以外的所有DNS查詢:
HostnameLookups off
<Files ~ "\.(html|cgi)$">
HostnameLookups on
</Files>
如果在某些CGI中偶爾需要DNS名稱,則可以調用gethostbyname來解決。
FollowSymLinks 和 SymLinksIfOwnerMatch
如果網站空間中沒有使用 Options FollowSymLinks ,或使用了 Options SymLinksIfOwnerMatch ,Apache就必須執行額外的系統調用以驗證符號連接。文件名的每一個組成部分都需要一個額外的調用。例如,如果設置了:
DocumentRoot /www/htdocs
<Directory />
Options SymLinksIfOwnerMatch
</Directory>
在請求"/index.html"時,Apache將對"/www"、"/www/htdocs"、"/www/htdocs/index.html"執行lstat()調用。而且lstat()的執行結果不被緩存,因此對每一個請求都要執行一次。如果確實需要驗證符號連接的安全性,則可以這樣:
DocumentRoot /www/htdocs
<Directory />
Options FollowSymLinks
</Directory>
<Directory /www/htdocs>
Options -FollowSymLinks +SymLinksIfOwnerMatch
</Directory>
這樣,至少可以避免對DocumentRoot路徑的多余的驗證。注意,如果Alias或RewriteRule中含有DocumentRoot以外的路徑,那麼同樣需要增加這樣的段。為了得到最佳性能,應當放棄對符號連接的保護,在所有地方都設置FollowSymLinks ,並放棄使用SymLinksIfOwnerMatch 。
AllowOverride
如果網站空間允許覆蓋(通常是用.htaccess文件),則Apache會試圖對文件名的每一個組成部分都打開.htaccess ,例如:
DocumentRoot /www/htdocs
<Directory />
AllowOverride all
</Directory>
如果請求"/index.html",則Apache會試圖打開"/.htaccess"、"/www/.htaccess"、"/www/htdocs/.htaccess"。其解決方法和前面所述的 Options FollowSymLinks 類似。為了得到最佳性能,應當對文件系統中所有的地方都使用 AllowOverride None 。
內容協商
實踐中,內容協商的好處大於性能的損失,如果你很在意那一點點的性能損失,則可以禁止使用內容協商。但是仍然有個方法可以提高服務器的速度,就是不要使用通配符,如:
DirectoryIndex index
而使用完整的列表,如:
DirectoryIndex index.cgi index.pl index.shtml index.html
其中最常用的應該放在前面。
還有,建立一個明確的type-map文件在性能上優於使用"Options MultiViews",因為所有需要的信息都在一個單獨的文件中,而無須搜索目錄。請參考內容協商文檔以獲得更詳細的協商方法和創建type-map文件的指導。
內存映射
在Apache2.0需要搜索被發送文件的內容時,比如處理服務器端包含時,如果操作系統支持某種形式的mmap() ,則會對此文件執行內存映射。
在某些平台上,內存映射可以提高性能,但是在某些情況下,內存映射會降低性能甚至影響到httpd的穩定性:
*
在某些操作系統中,如果增加了CPU,mmap還不如read()迅速。比如,在多處理器的Solaris服務器上,關閉了mmap ,Apache2.0傳送服務端解析文件有時候反而更快。
*
如果你對作為NFS裝載的文件系統中的一個文件進行了內存映射,而另一個NFS客戶端的進程刪除或者截斷了這個文件,那麼你的進程在下一次訪問已經被映射的文件內容時,會產生一個總線錯誤。
如果有上述情況發生,則應該使用 EnableMMAP off 關閉對發送文件的內存映射。注意:此指令可以被針對目錄的設置覆蓋。
Sendfile
在Apache2.0能夠忽略將要被發送的文件的內容的時候(比如發送靜態內容),如果操作系統支持sendfile() ,則Apache將使用內核提供的sendfile()來發送文件。
在大多數平台上,使用sendfile可以通過免除分離的讀和寫操作來提升性能。然而在某些情況下,使用sendfile會危害到httpd的穩定性
*
一些平台可能會有Apache編譯系統檢測不到的有缺陷的sendfile支持,特別是將在其他平台上使用交叉編譯得到的二進制文件運行於當前對sendfile支持有缺陷的平台時。
*
對於一個掛載了NFS文件系統的內核,它可能無法可靠的通過自己的cache服務於網絡文件。
如果出現以上情況,你應當使用"EnableSendfile off"來禁用sendfile 。注意,這個指令可以被針對目錄的設置覆蓋。
進程的建立
在Apache1.3以前,MinSpareServers, MaxSpareServers, StartServers的設置對性能都有很大的影響。尤其是為了應對負載而建立足夠的子進程時,Apache需要有一個"漸進"的過程。在最初建立StartServers數量的子進程後,為了滿足MinSpareServers設置的需要,每一秒鐘只能建立一個子進程。所以,對一個需要同時處理100個客戶端的服務器,如果StartServers使用默認的設置5,則為了應對負載而建立足夠多的子進程需要95秒。在實際應用中,如果不頻繁重新啟動服務器,這樣還可以,但是如果僅僅為了提供10分鐘的服務,這樣就很糟糕了。
" 一秒鐘一個"的規定是為了避免在創建子進程過程中服務器對請求的響應停頓,但是它對服務器性能的影響太大了,必須予以改變。在Apache1.3中,這個 "一秒鐘一個"的規定變得寬松了,創建一個進程,等待一秒鐘,繼續創建第二個,再等待一秒鐘,繼而創建四個,如此按指數級增加創建的進程數,最多達到每秒 32個,直到滿足MinSpareServers設置的值為止。
從多數反映看來,似乎沒有必要調整MinSpareServers, MaxSpareServers, StartServers 。如果每秒鐘創建的進程數超過4個,則會在ErrorLog中產生一條消息,如果產生大量此消息,則可以考慮修改這些設置。可以使用mod_status的輸出作為參考。
與進程創建相關的是由MaxRequestsPerChild引發的進程的銷毀。其默認值是"0",意味著每個進程所處理的請求數是不受限制的。如果此值設置得很小,比如30,則可能需要大幅增加。在SunOS或者Solaris的早期版本上,其最大值為10000以免內存洩漏。
如果啟用了持久鏈接,子進程將保持忙碌狀態以等待被打開連接上的新請求。為了最小化其負面影響,KeepAliveTimeout的默認值被設置為5秒,以謀求網絡帶寬和服務器資源之間的平衡。在任何情況下此值都不應當大於60秒,參見most of the benefits are lost。
top
編譯時的配置
選擇一個MPM
Apache 2.x 支持插入式並行處理模塊,稱為多路處理模塊(MPM)。在編譯Apache時你必須選擇也只能選擇一個MPM,這裡有幾個針對非UNIX系統的MPM:beos, mpm_netware, mpmt_os2, mpm_winnt。對類UNIX系統,有幾個不同的MPM可供選擇,他們都會影響到httpd的速度和可伸縮性:
* workerMPM使用多個子進程,每個子進程中又有多個線程。每個線程處理一個請求。該MPM通常對高流量的服務器是一個不錯的選擇。因為它比preforkMPM需要更少的內存且更具有伸縮性。
* preforkMPM使用多個子進程,但每個子進程並不包含多線程。每個進程只處理一個鏈接。在許多系統上它的速度和workerMPM一樣快,但是需要更多的內存。這種無線程的設計在某些情況下優於workerMPM:它可以應用於不具備線程安全的第三方模塊(比如php3/4/5),且在不支持線程調試的平台上易於調試,而且還具有比workerMPM更高的穩定性。
關於MPM的更多內容,請參考其文檔。
模塊
既然內存用量是影響性能的重要因素,你就應當盡量去除你不需要的模塊。如果你將模塊編譯成DSO ,取消不必要的模塊就是一件非常簡單的事情:注釋掉LoadModule指令中不需要的模塊。
如果你已經將模塊靜態鏈接進Apache二進制核心,你就必須重新編譯Apache並去掉你不想要的模塊。
增減模塊牽涉到的一個問題是:究竟需要哪些模塊、不需要哪些模塊?這取決於服務器的具體情況。一般說來,至少要包含下列模塊:mod_mime, mod_dir, mod_log_config 。你也可以不要mod_log_config ,但是一般不推薦這樣做。
原子操作
一些模塊,比如mod_cache和worker使用APR(Apache可移植運行時)的原子API。這些API提供了能夠用於輕量級線程同步的原子操作。
默認情況下,APR在每個目標OS/CPU上使用其最有效的特性執行這些操作。比如許多現代CPU的指令集中有一個原子的比較交換(compare-and -swap, CAS)操作指令。在一些老式平台上,APR默認使用一種緩慢的、基於互斥執行的原子API以保持對沒有CAS指令的老式CPU的兼容。如果你只打算在新式的CPU上運行Apache,你可以在編譯時使用 --enable-nonportable-atomics 選項:
./buildconf
./configure --with-mpm=worker --enable-nonportable-atomics=yes
--enable-nonportable-atomics 選項只和下列平台相關:
* SPARC上的Solaris
默認情況下,APR使用基於互斥執行的原子操作。如果你使用 --enable-nonportable-atomics 選項,APR將使用SPARC v8plus操作碼來加快基於硬件的CAS操作。注意,這僅對UltraSPARC CPU有效。
* x86上的Linux
默認情況下,APR在Linux上使用基於互斥執行的原子操作。如果你使用 --enable-nonportable-atomics 選項,APR將使用486操作碼來加快基於硬件的CAS操作。注意,這僅對486以上的CPU有效。
mod_status 和 "ExtendedStatus On"
如果Apache在編譯時包含了mod_status ,而且在運行時設置了"ExtendedStatus On",那麼Apache會對每個請求調用兩次gettimeofday()(或者根據操作系統的不同,調用times())以及(1.3版之前)幾個額外的time()調用,使狀態記錄帶有時間標志。為了得到最佳性能,可以設置"ExtendedStatus off"(這也是默認值)。
多socket情況下的串行accept
警告
這部分內容尚未完全根據Apache2.0中的變化進行更新 。一些信息依然有效,使用中請注意。
這裡要說的是 Unix socket API 的一個缺點。假設web服務器使用了多個Listen語句****多個端口或者多個地址,Apache會使用select()以檢測每個socket是否就緒。select()會表明一個socket有零或至少一個連接正等候處理。由於Apache的模型是多子進程的,所有空閒進程會同時檢測新的連接。一個很天真的實現方法是這樣的(這些例子並不是源代碼,只是為了說明問題而已):
for (;;) {
for (;;) {
fd_set accept_fds;
FD_ZERO (&accept_fds);
for (i = first_socket; i <= last_socket; ++i) {
FD_SET (i, &accept_fds);
}
rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
if (rc < 1) continue;
new_connection = -1;
for (i = first_socket; i <= last_socket; ++i) {
if (FD_ISSET (i, &accept_fds)) {
new_connection = accept (i, NULL, NULL);
if (new_connection != -1) break;
}
}
if (new_connection != -1) break;
}
process the new_connection;
}
這種天真的實現方法有一個嚴重的"饑餓"問題。如果多個子進程同時執行這個循環,則在多個請求之間,進程會被阻塞在select ,隨即進入循環並試圖accept此連接,但是只有一個進程可以成功執行(假設還有一個連接就緒),而其余的則會被阻塞在accept 。這樣,只有那一個socket可以處理請求,而其他都被鎖住了,直到有足夠多的請求將它們喚醒。此"饑餓"問題在PR#467中有專門的講述。目前至少有兩種解決方案。
一種方案是使用非阻塞型socket ,不阻塞子進程並允許它們立即繼續執行。但是這樣會浪費CPU時間。設想一下,select有10個子進程,當一個請求到達的時候,其中9個被喚醒,並試圖accept此連接,繼而進入select循環,無所事事,並且其間沒有一個子進程能夠響應出現在其他socket上的請求,直到退出select循環。總之,這個方案效率並不怎麼高,除非你有很多的CPU,而且開了很多子進程。
另一種也是Apache所使用的方案是,使內層循環的入口串行化,形如(不同之處以高亮顯示):
for (;;) {
accept_mutex_on ();
for (;;) {
fd_set accept_fds;
FD_ZERO (&accept_fds);
for (i = first_socket; i <= last_socket; ++i) {
FD_SET (i, &accept_fds);
}
rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
if (rc < 1) continue;
new_connection = -1;
for (i = first_socket; i <= last_socket; ++i) {
if (FD_ISSET (i, &accept_fds)) {
new_connection = accept (i, NULL, NULL);
if (new_connection != -1) break;
}
}
if (new_connection != -1) break;
}
accept_mutex_off ();
process the new_connection;
}
函數accept_mutex_on和accept_mutex_off實現了一個互斥信號燈,在任何時刻只被為一個子進程所擁有。實現互斥的方法有多種,其定義位於src/conf.h(1.3以前的版本)或src/include/ap_config.h(1.3或以後的版本)中。在一些根本沒有鎖定機制的體系中,使用多個Listen指令就是不安全的。
AcceptMutex指令被用來改變在運行時使用的互斥方案。
AcceptMutex flock
這種方法調用系統函數flock()來鎖定一個加鎖文件(其位置取決於LockFile指令)。
AcceptMutex fcntl
這種方法調用系統函數fcntl()來鎖定一個加鎖文件(其位置取決於LockFile指令)。
AcceptMutex sysvsem
(1.3及更新版本)這種方案使用SysV風格的信號燈以實現互斥。不幸的是,SysV風格的信號燈有一些副作用,其一是,Apache有可能不能在結束以前釋放這種信號燈(見ipcs()的man page),另外,這種信號燈API給與網絡服務器有相同uid的CGI提供了拒絕服務攻擊的機會(所有CGI,除非用了類似suexec或cgiwrapper)。鑒於此,在多數體系中都不用這種方法,除了IRIX(因為前兩種方法在IRIX中代價太高)。
AcceptMutex pthread
(1.3 及更新版本)這種方法使用了POSIX互斥,按理應該可以用於所有完整實現了POSIX線程規范的體系中,但是似乎只能用在Solaris2.5及更新版本中,甚至只能在某種配置下才正常運作。如果遇到這種情況,則應該提防服務器的掛起和失去響應。只提供靜態內容的服務器可能不受影響。
AcceptMutex posixsem
(2.0及更新版本)這種方法使用了POSIX信號燈。如果一個運行中的線程占有了互斥segfault ,則信號燈的所有者將不會被恢復,從而導致服務器的掛起和失去響應。
如果你的系統提供了上述方法以外的串行機制,那就可能需要為APR增加代碼(或者提交一個補丁給Apache)。
還有一種曾經考慮過但從未予以實施的方案是使循環部分地串行化,即只允許一定數量的進程進入循環。這種方法僅在多個進程可以同時進行的多處理器的系統中才是有價值的,而且這樣的串行方法並沒有占用整個帶寬。它也許是將來研究的一個領域,但是由於高度並行的網絡服務器並不符合規范,所以其被優先考慮的程度會比較低。
當然,為了得到最佳性能,最後就根本不使用多個Listen語句。但是上述內容還是值得讀一讀。
單socket情況下的串行accept
上述對多socket的服務器進行了一流的講述,那麼對單socket的服務器又怎樣呢?理論上似乎應該沒有什麼問題,因為所有進程在連接到來的時候可以由accept()阻塞,而不會產生進程"饑餓"的問題,但是在實際應用中,它掩蓋了與上述非阻塞方案幾乎相同的問題。按大多數TCP棧的實現方法,在單個連接到來時,內核實際上喚醒了所有阻塞在accept的進程,但只有一個能得到此連接並返回到用戶空間,而其余的由於得不到連接而在內核中處於休眠狀態。這種休眠狀態為代碼所掩蓋,但的確存在,並產生與多socket中采用非阻塞方案相同的負載尖峰的浪費。
同時,我們發現在許多體系結構中,即使在單socket的情況下,實施串行化的效果也不錯,因此在幾乎所有的情況下,事實上就都這樣處理了。在Linux (2.0.30,雙Pentium pro 166/128M RAM)下的測試顯示,對單socket,串行化比不串行化每秒鐘可以處理的請求少了不到3%,但是,不串行化對每一個請求多了額外的100ms的延遲,此延遲可能是因為長距離的網絡線路所致,並且僅發生在LAN中。如果需要改變對單socket的串行化,可以定義SINGLE_LISTEN_UNSERIALIZED_ACCEPT ,使單socket的服務器徹底放棄串行化。
延遲的關閉
正如draft-ietf-http-connection-00.txt section 8所述,HTTP服務器為了可靠地實現此協議,需要單獨地在每個方向上關閉通訊(重申一下,一個TCP連接是雙向的,兩個方向之間是獨立的)。在這一點上,其他服務器經常敷衍了事,但從1.2版本開始被Apache正確實現了。
但是增加了此功能以後,由於一些Unix版本的短見,隨之也出現了許多問題。TCP規范並沒有規定FIN_WAIT_2必須有一個超時,但也沒有明確禁止。在沒有超時的系統中,Apache1.2經常會陷於FIN_WAIT_2狀態中。多數情況下,這個問題可以用供應商提供的TCP/IP補丁予以解決。而如果供應商不提供補丁(指SunOS4 -- 盡管用戶們持有允許自己修補代碼的許可證),那麼只能關閉此功能。
實現的方法有兩種,其一是socket選項SO_LINGER ,但是似乎命中注定,大多數TCP/IP棧都從未予以正確實現。即使在正確實現的棧中(指Linux2.0.31),此方法也被證明其代價比下一種方法高昂。
Apache對此的實現代碼大多位於函數lingering_close(位於http_main.c)中。此函數大致形如:
void lingering_close (int s)
{
char junk_buffer[2048];
/* shutdown the sending side */
shutdown (s, 1);
signal (SIGALRM, lingering_death);
alarm (30);
for (;;) {
select (s for reading, 2 second timeout);
if (error) break;
if (s is ready for reading) {
if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {
break;
}
/* just toss away whatever is here */
}
}
close (s);
}
此代碼在連接結束時多了一些開銷,但這是可靠實現所必須的。由於HTTP/1.1越來越流行,而且所有連接都是穩定的,此開銷將由更多的請求共同分擔。如果你要玩火去關閉這個功能,可以定義NO_LINGCLOSE ,但絕不推薦這樣做。尤其是,隨著HTTP/1.1中管道化穩定連接的啟用,lingering_close已經成為絕對必須。而且,管道化連接速度更快,應該考慮予以支持。
Scoreboard 文件
Apache父進程和子進程通過scoreboard進行通訊。通過共享內存來實現當然是最理想的。在我們曾經實踐過或者提供了完整移植的操作系統中,都使用共享內存,其余的則使用磁盤文件。磁盤文件不僅速度慢,而且不可靠(功能也少)。仔細閱讀你的體系所對應的src/main/conf.h文件,並查找USE_MMAP_SCOREBOARD或USE_SHMGET_SCOREBOARD 。定義其中之一(或者分別類似HAVE_MMAP和HAVE_SHMGET),可以使共享內容的相關代碼生效。如果你的系統提供其他類型的共享內容,則需要修改src/main/http_main.c文件,並把必需的掛鉤添加到服務器中。(也請發送一個補丁給我們)
注意:在對Linux的Apache1.2移植版本之前,沒有使用內存共享,此失誤使Apache的早期版本在Linux中表現很差。
DYNAMIC_MODULE_LIMIT
如果你不想使用動態加載模塊(或者是因為看見了這段話,或者是為了獲得最後一點點性能上的提高),可以在編譯服務器時定義 -DDYNAMIC_MODULE_LIMIT=0 ,這樣可以節省為支持動態加載模塊而分配的內存。
top
附錄:蹤跡的詳細分析
在Solaris8的MPM中,Apache2.0.38使用一個系統調用以收集蹤跡:
truss -l -p httpd_child_pid.
-l 參數使truss記錄每個執行系統調用的LWP(lightweight process--Solaris核心級線程)的ID。
其他系統可能使用不同的系統調用追蹤工具,諸如strace, ktrace, par ,其輸出都是相似的。
下例中,一個客戶端向httpd請求了一個10KB的靜態文件。對非靜態或內容協商請求的記錄會有很大不同(有時也很難看明白)。
/67: accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
/67: accept(3, 0x00200BEC, 0x00200C0C, 1) = 9
下例中,****線程是 LWP #67 。
注意對accept()串行化支持的匮乏。與這個特殊平台對應的MPM在默認情況下使用非串行的accept ,除了在****多個端口的時候。
/65: lwp_park(0x00000000, 0) = 0
/67: lwp_unpark(65, 1) = 0
接受了一個連接後,****線程喚醒一個工作線程以處理此請求。下例中,處理請求的那個工作線程是 LWP #65 。
/65: getsockname(9, 0x00200BA4, 0x00200BC4, 1) = 0
為了實現虛擬主機,Apache需要知道接受連接的本地socket地址。在許多情況下,有可能無須執行此調用(比如沒有虛擬主機,或者Listen指令中沒有使用通配地址),但是目前並沒有對此作優化處理。
/65: brk(0x002170E8) = 0
/65: brk(0x002190E8) = 0
此brk()調用是從堆中分配內存的,它在系統調用記錄中並不多見,因為httpd在多數請求處理中使用了自己的內存分配器(apr_pool和apr_bucket_alloc)。下例中,httpd剛剛啟動,所以它必須調用malloc()以分配原始內存塊用於自己的內存分配器。
/65: fcntl(9, F_GETFL, 0x00000000) = 2
/65: fstat64(9, 0xFAF7B818) = 0
/65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0
/65: fstat64(9, 0xFAF7B818) = 0
/65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0
/65: setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0
/65: fcntl(9, F_SETFL, 0x00000082) = 0
接著,工作線程使客戶端連接處於非阻塞模式。setsockopt()和getsockopt()調用是Solaris的libc對socket執行fcntl()所必須的。
/65: read(9, " G E T / 1 0 k . h t m".., 8000) = 97
工作線程從客戶端讀取請求。
/65: stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
/65: open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10
這裡,httpd被配置為"Options FollowSymLinks"和"AllowOverride None"。所以,無須對每個被請求文件路徑中的目錄執行lstat(),也不需要檢查.htaccess文件,它簡單地調用stat()以檢查此文件是否存在,以及是一個普通的文件還是一個目錄。
/65: sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C) = 10269
此例中,httpd可以通過單個系統調用sendfilev()發送HTTP響應頭和被請求的文件。Sendfile因操作系統會有所不同,有些系統中,在調用sendfile()以前,需要調用write()或writev()以發送響應頭。
/65: write(4, " 1 2 7 . 0 . 0 . 1 - ".., 78) = 78
此write()調用在訪問日志中對請求作了記錄。注意,其中沒有對time()的調用的記錄。與Apache1.3不同,Apache2.0使用gettimeofday()以查詢時間。在有些操作系統中,比如Linux和Solaris,gettimeofday有一個優化的版本,其開銷比一個普通的系統調用要小一點。
/65: shutdown(9, 1, 1) = 0
/65: poll(0xFAF7B980, 1, 2000) = 1
/65: read(9, 0xFAF7BC20, 512) = 0
/65: close(9) = 0
工作線程對連接作延遲的關閉。
/65: close(10) = 0
/65: lwp_park(0x00000000, 0) (sleeping...)
最後,工作線程關閉發送完的文件和塊,直到****進程把它指派給另一個連接。
/67: accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)
其間,****進程可以在把一個連接指派給一個工作進程後立即接受另一個連接(但是如果所有工作進程都處於忙碌狀態,則會受MPM中的一些溢出控制邏輯的制約)。雖然在此例中並不明顯,在工作線程剛接受了一個連接之後,下一個accept()會(在高負荷的情況下更會)立即並行產生。