# 性能方面的提示
Apache2.0是一個多用途的web服務器,其設計在靈活性、可移植性和性能中求得平衡。雖然沒有在設計上刻意追求性能指標,但是Apache2.0仍然在許多現實環境中擁有很高的性能。
相比于Apache 1.3 ,2.0版本作了大量的優化來提升處理能力和可伸縮性,而且大多數的改進在默認狀態下就可以生效。但是,在編譯時和運行時,都有許多可以顯著提高性能的選擇。本文闡述在安裝Apache2.0時,服務器管理員可以改善性能的各種方法。其中,部分配置選擇可以使httpd更好地利用硬件和操作系統的兼容性,其他則是以功能換取速度。
## 硬件和操作系統
影響web服務器性能的最大的因素是內存。一個web服務器應該從不使用交換機制,因為交換產生的滯后使用戶總感覺"不夠快",所以用戶就可能去按"停止"和"刷新",從而帶來更大的負載。你可以,也應該,控制`MaxClients`的設置,以避免服務器產生太多的子進程而發生交換。這個過程很簡單:通過`top`命令計算出每個Apache進程平均消耗的內存,然后再為其它進程留出足夠多的內存。
其他因素就很普通了,裝一個足夠快的CPU,一個足夠快的網卡,幾個足夠快的硬盤,這里說的"足夠快"是指能滿足實際應用的需求。
操作系統是很值得關注的又一個因素,已經被證實的很有用的經驗有:
* 選擇能夠得到的最新最穩定的版本并打好補丁。近年來,許多操作系統廠商都提供了可以顯著改善性能的TCP協議棧和線程庫。
* 如果你的操作系統支持`sendfile()`系統調用,則務必安裝帶有此功能的版本或補丁(對Linux來說,就是使用Linux2.4或更高版本,對Solaris8的早期版本,則需要安裝補丁)。在支持`sendfile`的系統中,Apache2可以更快地發送靜態內容而且占用較少的CPU時間。
## 運行時的配置
相關模塊
* `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`",因為所有需要的信息都在一個單獨的文件中,而無須搜索目錄。請參考[內容協商](#calibre_link-258)文檔以獲得更詳細的協商方法和創建`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](http://www.research.digital.com/wrl/techreports/abstracts/95.4.html)。
## 編譯時的配置
### 選擇一個MPM
Apache 2.x 支持插入式并行處理模塊,稱為[多路處理模塊(MPM)](#calibre_link-252)。在編譯Apache時你必須選擇也只能選擇一個MPM,這里有幾個針對非UNIX系統的MPM:`beos`, `mpm_netware`, `mpmt_os2`, `mpm_winnt`。對類UNIX系統,有幾個不同的MPM可供選擇,他們都會影響到httpd的速度和可伸縮性:
* `worker`MPM使用多個子進程,每個子進程中又有多個線程。每個線程處理一個請求。該MPM通常對高流量的服務器是一個不錯的選擇。因為它比`prefork`MPM需要更少的內存且更具有伸縮性。
* `prefork`MPM使用多個子進程,但每個子進程并不包含多線程。每個進程只處理一個鏈接。在許多系統上它的速度和`worker`MPM一樣快,但是需要更多的內存。這種無線程的設計在某些情況下優于`worker`MPM:它可以應用于不具備線程安全的第三方模塊(比如php3/4/5),且在不支持線程調試的平臺上易于調試,而且還具有比`worker`MPM更高的穩定性。
關于MPM的更多內容,請參考其[文檔](#calibre_link-252)。
### 模塊
既然內存用量是影響性能的重要因素,你就應當盡量去除你不需要的模塊。如果你將模塊編譯成[DSO](#calibre_link-259) ,取消不必要的模塊就是一件非常簡單的事情:注釋掉`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](http://bugs.apache.org/index/full/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](http://www.ics.uci.edu/pub/ietf/http/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`已經成為絕對必須。而且,[管道化連接速度更快](http://www.w3.org/Protocols/HTTP/Performance/Pipeline.html),應該考慮予以支持。
### 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` ,這樣可以節省為支持動態加載模塊而分配的內存。
## 附錄:蹤跡的詳細分析
在Solaris8的MPM中,Apache2.0.38使用一個系統調用以收集蹤跡:
```
truss -l -p <var class="calibre40">httpd_child_pid</var>.
```
`-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()`會(在高負荷的情況下更會)立即并行產生。
- Apache HTTP Server Version 2.2 文檔 [最后更新:2006年3月21日]
- 版本說明
- 從1.3升級到2.0
- 從2.0升級到2.2
- Apache 2.2 新特性概述
- Apache 2.0 新特性概述
- The Apache License, Version 2.0
- 參考手冊
- 編譯與安裝
- 啟動Apache
- 停止和重啟
- 配置文件
- 配置段(容器)
- 緩沖指南
- 服務器全局配置
- 日志文件
- 從URL到文件系統的映射
- 安全方面的提示
- 動態共享對象(DSO)支持
- 內容協商
- 自定義錯誤響應
- 地址和端口的綁定(Binding)
- 多路處理模塊
- Apache的環境變量
- Apache處理器的使用
- 過濾器(Filter)
- suEXEC支持
- 性能方面的提示
- URL重寫指南
- Apache虛擬主機文檔
- 基于主機名的虛擬主機
- 基于IP地址的虛擬主機
- 大批量虛擬主機的動態配置
- 虛擬主機示例
- 深入研究虛擬主機的匹配
- 文件描述符限制
- 關于DNS和Apache
- 常見問題
- 經常問到的問題
- Apache的SSL/TLS加密
- SSL/TLS高強度加密:緒論
- SSL/TLS高強度加密:兼容性
- SSL/TLS高強度加密:如何...?
- SSL/TLS Strong Encryption: FAQ
- 如何.../指南
- 認證、授權、訪問控制
- CGI動態頁面
- 服務器端包含入門
- .htaccess文件
- 用戶網站目錄
- 針對特定平臺的說明
- 在Microsoft Windows中使用Apache
- 在Microsoft Windows上編譯Apache
- Using Apache With Novell NetWare
- Running a High-Performance Web Server on HPUX
- The Apache EBCDIC Port
- 服務器和支持程序
- httpd - Apache超文本傳輸協議服務器
- ab - Apache HTTP服務器性能測試工具
- apachectl - Apache HTTP服務器控制接口
- apxs - Apache 擴展工具
- configure - 配置源代碼樹
- dbmmanage - 管理DBM格式的用戶認證文件
- htcacheclean - 清理磁盤緩沖區
- htdbm - 操作DBM密碼數據庫
- htdigest - 管理用于摘要認證的用戶文件
- httxt2dbm - 生成RewriteMap指令使用的dbm文件
- htpasswd - 管理用于基本認證的用戶文件
- logresolve - 解析Apache日志中的IP地址為主機名
- rotatelogs - 滾動Apache日志的管道日志程序
- suexec - 在執行外部程序之前切換用戶
- 其他程序
- 雜項文檔
- 與Apache相關的標準
- Apache模塊
- 描述模塊的術語
- 描述指令的術語
- Apache核心(Core)特性
- Apache MPM 公共指令
- Apache MPM beos
- Apache MPM event
- Apache MPM netware
- Apache MPM os2
- Apache MPM prefork
- Apache MPM winnt
- Apache MPM worker
- Apache模塊 mod_actions
- Apache模塊 mod_alias
- Apache模塊 mod_asis
- Apache模塊 mod_auth_basic
- Apache模塊 mod_auth_digest
- Apache模塊 mod_authn_alias
- Apache模塊 mod_authn_anon
- Apache模塊 mod_authn_dbd
- Apache模塊 mod_authn_dbm
- Apache模塊 mod_authn_default
- Apache模塊 mod_authn_file
- Apache模塊 mod_authnz_ldap
- Apache模塊 mod_authz_dbm
- Apache模塊 mod_authz_default
- Apache模塊 mod_authz_groupfile
- Apache模塊 mod_authz_host
- Apache模塊 mod_authz_owner
- Apache模塊 mod_authz_user
- Apache模塊 mod_autoindex
- Apache模塊 mod_cache
- Apache模塊 mod_cern_meta
- Apache模塊 mod_cgi
- Apache模塊 mod_cgid
- Apache模塊 mod_charset_lite
- Apache模塊 mod_dav
- Apache模塊 mod_dav_fs
- Apache模塊 mod_dav_lock
- Apache模塊 mod_dbd
- Apache模塊 mod_deflate
- Apache模塊 mod_dir
- Apache模塊 mod_disk_cache
- Apache模塊 mod_dumpio
- Apache模塊 mod_echo
- Apache模塊 mod_env
- Apache模塊 mod_example
- Apache模塊 mod_expires
- Apache模塊 mod_ext_filter
- Apache模塊 mod_file_cache
- Apache模塊 mod_filter
- Apache模塊 mod_headers
- Apache模塊 mod_ident
- Apache模塊 mod_imagemap
- Apache模塊 mod_include
- Apache模塊 mod_info
- Apache模塊 mod_isapi
- Apache模塊 mod_ldap
- Apache模塊 mod_log_config
- Apache模塊 mod_log_forensic
- Apache模塊 mod_logio
- Apache模塊 mod_mem_cache
- Apache模塊 mod_mime
- Apache模塊 mod_mime_magic
- Apache模塊 mod_negotiation
- Apache模塊 mod_nw_ssl
- Apache模塊 mod_proxy
- Apache模塊 mod_proxy_ajp
- Apache模塊 mod_proxy_balancer
- Apache模塊 mod_proxy_connect
- Apache模塊 mod_proxy_ftp
- Apache模塊 mod_proxy_http
- Apache模塊 mod_rewrite
- Apache模塊 mod_setenvif
- Apache模塊 mod_so
- Apache模塊 mod_speling
- Apache模塊 mod_ssl
- Apache模塊 mod_status
- Apache模塊 mod_suexec
- Apache模塊 mod_unique_id
- Apache模塊 mod_userdir
- Apache模塊 mod_usertrack
- Apache模塊 mod_version
- Apache模塊 mod_vhost_alias
- Developer Documentation for Apache 2.0
- Apache 1.3 API notes
- Debugging Memory Allocation in APR
- Documenting Apache 2.0
- Apache 2.0 Hook Functions
- Converting Modules from Apache 1.3 to Apache 2.0
- Request Processing in Apache 2.0
- How filters work in Apache 2.0
- Apache 2.0 Thread Safety Issues
- 詞匯和索引
- 詞匯表
- 指令索引
- 指令速查
- 模塊索引
- 站點導航