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