網域查詢: www.
返回首頁
當前位置: 首頁 > 站長學院 > 服務器 >

Apache 性能最優化分析(上)

時間:2010-02-17 16:01來源: 作者: 點擊:
一. 簡介 Apache是把正確性放在首位、把速度放在其次的通用Web服務器。即使這樣,它的性能十分令人滿意。許多站點只有不到10M的出口帶寬。Apache能夠在這些站點的低端Pentium服務器上全
  一. 簡介

  Apache是把正確性放在首位、把速度放在其次的通用Web服務器。即使這樣,它的性能十分令人滿意。許多站點只有不到10M的出口帶寬。Apache能夠在這些站點的低端Pentium服務器上全速工作。實際上,擁有更多帶寬的站點出于一些原因(比如大量的CGI和數據庫事務處理)需要用一台以上的機器滿足帶寬需求。這些原因導致了以往的Apache開發工作集中在正確性和可配置性。

  不幸的是許多人過于重視某些指標,並把它們的原始數據當作評價Web服務器優劣的標準。被普遍接受標準的是"原始最低性能(bare minimum performance)",而在這以外的其他速度指標只適用于很小部分的市場需求。但為了避免Apache在一些市場中受到排擠,我們在Apache1.3上盡了相當的努力,將它與高端服務器的差距減至最小。

  另有一些人只是想試試這些東東能運行得多快。這些人竭力把Apache最後一滴性能擠出來,他們也想看看究竟是什麼影響了Apache的性能。這篇文章的其余部分就是針對他們而撰的。

  請注意本文適用于Unix上的Apache1.3,部分內容適用于NT平台。目前的Apache尚未在NT上進行優化。事實上,不同的編程模型使它在NT上的性能表現相當不好。(即POSIX模型。NT借助POSIX子系統模擬這種編程標準,因此效率很低。Apache2.0拋棄了POSIX直接與操作系統打交道,性能將有所飛躍--譯者注)

二. 關于硬件平台和操作系統

  最直接影響Web服務器性能的硬件要數RAM。一台Web服務器從不應該訪問內存交換區。交換增加了每次請求的延時,用戶將因此認為"不夠快"。他們會點擊[停止]並重新裝載網頁,這將進一步增加服務器的負擔。您能夠也有必要調節MaxClients,使您的服務器不會衍生太多的子進程而導致交換。

  除此之外的事情就沒那麼關鍵了。擁有快速的CPU、快速的網卡和硬盤都可以讓您的服務器"足夠快"。其實這足夠快個詞是需要憑經驗去體會的。

  操作系統的選用也是值得斟酌的大問題。普遍的準則是︰及時得到操作系統提供商的最新TCP/IP補丁。迅速涌現的HTTP服務打破了截止到1994年乃至95年的Unix內核中設定的許多假設情況。理想的選擇包括目前的FreeBSD和Linux。

三. 關于運行時設置(Run-Time Configuration)

  1) HostnameLookups

  1.3版以前的Apache中,HostnameLookups的缺省值是On,這將導致每次請求時服務器都要進行NDS查詢,從而增加了延遲。Apache1.3將此缺省值設為Off。在1.3及以後的版本中,如果您使用了任何allow from domain或deny from domain命令,所付出的代價將是兩次DNS查詢帶來的延時(在一次逆向查詢後跟著一次正向查詢,以保證前者得到的結果是真實的)。因此為了得到最理想的性能應避免使用HostnameLookups(使用IP地址而非域名也是個好主意)。


  限制命令的使用範圍是可行的,比如使用類似的容器。這種情況下,DNS查詢只發生在符合條件的請求中。下面的例子使查詢只發生在.html和.cgi文件的請求中︰

    HostnameLookups off
    
  
一秒一個的規則防止服務器在生成子進程時過于忙碌。如果它忙于繁殖進程,請求將被擱置。但這個規則對直觀性能的影響太大了,它必須有所改觀。在Apache 1.3中,一秒一個的規則被廢棄了。它首先衍生一個子進程,等一秒,衍生兩個,等一秒,再衍生兩個,直到一秒衍生32個子進程。隨後它將保持這個速度直到滿足MinSpareServers的要求。

  這看起來足夠好了。幾乎不用在MinSpareServers、MaxSpareServers或StartServers上費工夫了。當每秒鐘衍生的進程數超過4時,ErrorLog中會增加一條相應的記錄。如果您看到了很多這樣的提示,請調整這些參數。mod_status的輸出會給您一些提示。

  于進程相關的問題是由MaxRequestsPerChild導致的進程終止。MaxRequestsPerChild缺省地設置為0,意味每個子進程處理的請求數不受限制。如果當前的設置值非常小,您可能希望大幅度提升這個值。為了防止內存泄露,在SunOS或者低版本的Solaris上,應把此值設為10000左右。

  如果使用了持續連接(keep-alives),子進程將繁忙等待(busy waiting)已打開連接的後續請求而不能做其他的事。缺省的15秒種試圖使影響將至最底。您需要在網絡帶寬和服務器資源之間作出權衡。任何情況下,不應設置持續連接時間超過60秒。否則大部分好處將變成損失。

四. 關于編譯時設置

  1) mod_status 和 ExtendedStatus On

  如果在編譯Apache時您包含了mod_status並且將ExtendedStatus設置為On,Apache將為每個請求進行兩次gettimeofday(2)系統調用(或者針對不同的系統調用times(2))及(在1.3以前的版本)許多次time(2)。這些都是為了在報告中含有時間戳。為了得到最佳性能,請將ExtendedStatus設為Off(這是缺省的設置)。

  2) 多socket中的accept 串行化

  這部分文章將討論Unix socket API不利的一方面。假設您的服務器用多個Listen命令偵听多個端口或者多個IP地址。Apache使用select(2)檢測每個socket連接(connection)是否就緒。select(2)示意有零個或至少一個連接等待某個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中阻塞。當任何socket上出現一個請求時,所有被阻塞的進程將復甦,並從select返回(甦醒進程的數量取決于操作系統和時間)。它們將繼續執行並試圖接受這個連接,但只有一個進程會成功(假設目前仍只有一個連接),其余進程將阻塞在accept中。這將把所有失敗的進程鎖定,使它們只為一個socket上的請求服務。它們會一直被阻塞,直到在那個socket上出現足夠的請求把它們喚醒。這一饑餓問題首先在PR#467被提出。至少有兩種解決它的方法

  一種方案是使用非阻塞的socket。這種情況下,accept不會阻塞子進程,它們將會立即返回。但這種方案會造成CPU時間的浪費。假設有十個在select中的空閑進程,而後到來了一個連接請求。九個進程將甦醒、試圖接受連接、失敗,並返回select,這些進程實際什麼都沒做。而且如果在這期間,其他socket上出現請求,沒有哪個進程會為它服務。總而言之,這種方案不是十分有效,除非您擁有和空閑子進程數目相當的CPU--恐怕不切實際。

  另一種方案被Apache采納。這種方案串行化(serialize)對內層循環的調用。代碼如下所示(改進的部分被加粗顯示)︰

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 兩個函數實現了互斥量(mutual exclusion semaphore),在任意時刻只能有一個子進程擁有互斥量。多種方法可以實現互斥量。在src/conf.h(1.3版之前)或src/include/ap_config.h(1.3版及以後)可以作出以下選擇。一些系統不提供任何互斥方法。在這些系統上使用多個Listen命令是不保險的。

  USE_FLOCK_SERIALIZED_ACCEPT

  此方法用flock(2)系統調用對一個鎖文件加鎖。(此文件在LockFile命令中指定)

  USE_FCNTL_SERIALIZED_ACCEPT

  此方法用flock(2)系統調用對一個鎖文件加鎖。(此文件在LockFile命令中指定)

  USE_SYSVSEM_SERIALIZED_ACCEPT (1.3版及以後)

  此方法借助SysV的信號量(semaphores)實現互斥。但不巧的是SysV信號量有一些負面作用。一是Apache可能在清除信號量之前非正常終止;二是在使用信號量API時需要考慮到任何與服務器UID相同的CGI程序可以進行拒絕服務攻擊(就是說所有的CGI程序都可以這樣做,除非使用suexec或cgiwrapper之類的方法)。所以,這種方法並不被IRIX之外的系統廣泛采納(由于大多數IRIX系統上,使用前兩種方法的代價太大)。

  USE_USLOCK_SERIALIZED_ACCEPT

  (1.3版及以後)此方法僅在IRIX上可用。它調用usconfig(2)創建互斥量。雖然這種方法避免了對SysV信號量的種種爭議,但它不是IRIX的缺省方案。這是由于在單處理器的IRIX系統 (5.3或6.2)上,uslock代碼比SysV信號量慢兩個數量級;但在多處理器的IRIX中前者比後者快一個數量級。這無非使問題復雜化了。所以在多處理器IRIX系統上,您需要用如下的附加參數編譯Apache︰

    在EXTRA_CFLAGS中添加-DUSE_USLOCK_SERIALIZED_ACCEPT
    USE_PTHREAD_SERIALIZED_ACCEPT


  
(1.3版及以後)此方法實現了POSIX標準互斥量。它理應可以工作在任何實現了全部POSIX線程規範的系統上,但事實是只有在Solaris 2.5或以上的系統及特定的配置中才能工作。如果您嘗試這種方法的話,需要小心服務器掛起或者沒有響應。服務器在只輸出靜態網頁的情況下運行得很好。

  如果您的系統上有其他串行化的方法,為它書寫代碼(並把補丁寄給Apache)是值得的。

  有一個考慮到但從未實現的方案是對循環部分地串行化--即允許一定數目的進程進入循環。在同一時刻可運行若干進程的多處理器系統上,這個主意是滿不錯的。而且前面提到的方案並沒有充分利用帶寬。可由于高度並行化的服務器實在少見,這個方案的優先級比較低。

  為了得到最佳性能,不用多偵听命令是最理想的。請繼續往下看。

  3) 單socket中的accept串行化

  以上言及的方案對多socket服務器是相當不錯的,但只有一個socket的情況又如何呢?理論上,由于在連接請求到來之前所有子進程將阻塞在accept中,單個socket不會產生上述種種問題。但實際上,上述非阻塞解決方案所帶來的"回旋(spinning)"問題在這里只不過被掩蓋起來了。在絕大多數TCP協議棧的實現中,一個接請求到來時內核將喚醒所有阻塞在accept中的進程。它們之一將得到此請求並返回用戶空間,其余的進程將返回內核重新休眠。這將帶來與多socket非阻塞解決方案相同的資源浪費。

  由于這點原因,我們發現如果為socket串行化,許多系統表現得更"友好"--即使是一個socket的情況。這是單個socket串行化作為絕大多數情況的缺省配置的原因。在Linux上不甚精確的(Linux 2.0.30 / 雙Pentium Pro 166 w / 128Mb內存)實驗表明,對每次請求而言,串行化的單個socket僅比沒有串行化的socket損失不到3%的性能。但未串行化的socket顯示出每次連接請求100毫秒的延時。這也可能僅僅由于過長的通訊距離造成的。如果您不想串行化單個socket,可以定義宏SINGLE_LISTEN_UNSERIALIZED_ACCEPT。這樣,僅有一個socket的服務器將不會串行化。

  4) 延遲關閉(Lingering Close)

  就象draft-ietf-http-connection-00.txt第8節討論的那樣,為了使服務器能夠可靠地實現HTTP協議,有必要獨立地關閉每個方向上的通訊(每個TCP連接有兩個方向,每個方向是分別獨立的)。這個事實往往被其他服務器所忽視,而Apache 1.2就已經正確地處理了。

  當這個特性增加到Apache中時卻在許多版本的Unix中引起了問題。這是TCP規範的短見造成的--它沒有聲明FIN_WAIT_2有超時,但也沒有阻止這樣的實現。在沒有超時的系統中,Apache 1.2將導致許多socket將永遠處于FIN_WAIT_2的狀態。這可以簡單地用打最新TCP/IP補丁的方法避免。然而在提供商從不發行補丁的系統上(也就是SunOS4--雖然得到源代碼許可證的人可以自己打補丁),我們決定不直接使用這一特性。

頂一下
(0)
0%
踩一下
(0)
0%
------分隔線----------------------------
最新評論 查看所有評論
發表評論 查看所有評論
請自覺遵守互聯網相關的政策法規,嚴禁發佈色情、暴力、反動的言論。
評價:
表情:
用戶名: 密碼: 驗證碼:
推薦內容