Apache 性能調優(yōu)

2021-10-13 18:21 更新

Apache 2.x是一個通用的Web服務器,旨在提供靈活性,可移植性和性能之間的平衡。雖然它沒有專門設計用于設置基準記錄,但Apache 2.x在許多實際情況下都具有高性能。

與Apache 1.3相比,版本2.x包含許多額外的優(yōu)化,以提高吞吐量和可伸縮性。默認情況下,大多數(shù)這些改進都已啟用。但是,存在可能顯著影響性能的編譯時和運行時配置選擇。本文檔介紹了服務器管理員可以配置的選項,以調整Apache 2.x安裝的性能。其中一些配置選項使httpd能夠更好地利用硬件和操作系統(tǒng)的功能,而其他配置選項則允許管理員交換功能以提高速度。

硬件和操作系統(tǒng)問題

影響Web服務器性能的最大硬件問題是服務器的內存(RAM)。網絡服務器永遠不應該交換,因為交換會增加每個請求的延遲超出用戶認為“足夠快”的點。這會導致用戶點擊停止并重新加載,從而進一步增加負載。您可以而且應該控制MaxRequestWorkers設置,以便您的服務器不會產生太多的子節(jié)點以便它開始交換。執(zhí)行此操作的過程很簡單:通過頂級工具查看流程列表,確定平均Apache流程的大小,并將其劃分為總可用內存,為其他流程留出一些空間。

除此之外,其余的是平凡的:獲得足夠快的CPU,足夠快的網卡和足夠快的磁盤,其中“足夠快”是需要通過實驗確定的東西。

操作系統(tǒng)的選擇主要取決于管理員的問題。但是一些經證明通用的指南是:

  • 運行選擇的操作系統(tǒng)的最新穩(wěn)定版本和修補程序級別。近年來,許多OS供應商已經為其TCP堆棧和線程庫引入了顯著的性能改進。
  • 如果操作系統(tǒng)支持sendfile(2)系統(tǒng)調用,請確保安裝啟用它所需的版本和/或修補程序。(例如,使用Linux,這意味著使用Linux 2.4或更高版本。對于Solaris 8的早期版本,您、可能需要應用補丁。)在可用的系統(tǒng)上,sendfile使Apache 2能夠以更低的速度更快地提供靜態(tài)內容 CPU利用率。

運行時配置問題

HostnameLookups和其他DNS注意事項

在Apache 1.3之前,HostnameLookups默認為On。這會增加每個請求的延遲,因為它需要在請求完成之前完成DNS查找。在Apache 1.3中,此設置默認為關閉。如果您需要將日志文件中的地址解析為主機名,請使用Apache附帶的logresolve程序,或者可用的眾多日志報告程序包之一。

建議您在生產Web服務器計算機以外的某臺計算機上對日志文件進行此類后處理,以使此活動不會對服務器性能產生負面影響。

如果使用域名允許或域名指令拒絕(即使用主機名或域名,而不是IP地址),那么您將需要付出兩次DNS查詢(反向,然后進行正向查找以確保反過來沒有被欺騙)。因此,為了獲得最佳性能,請在使用這些指令時使用IP地址而不是名稱(如果可能)。

請注意,可以對指令進行范圍限定,例如在<Location "/server-status">部分中。在這種情況下,DNS查找僅在符合條件的請求上執(zhí)行。這是一個禁用除.html和.cgi文件之外的查找的示例:

HostnameLookups off
<Files ~ "\.(html|cgi)$">
  HostnameLookups on
</Files>
Shell

但即使如此,如果只需要在一些CGI中使用DNS名稱,可以考慮在需要它的特定CGI中進行gethostbyname調用。

FollowSymLinks和SymLinksIfOwnerMatch無論您的URL空間中沒有Options FollowSymLinks,或者都有選項SymLinksIfOwnerMatch,Apache都需要發(fā)出額外的系統(tǒng)調用來檢查符號鏈接。(每個文件名組件一次額外調用。)例如,如果配置有:

DocumentRoot "/www/htdocs"
<Directory "/">
  Options SymLinksIfOwnerMatch
</Directory>
Shell

并且對URI /index.html發(fā)出請求,然后Apache將在/www,/www/htdocs和/www/htdocs/index.html上執(zhí)行l(wèi)stat(2)。這些lstats的結果永遠不會被緩存,因此它們將在每個請求中發(fā)生。如果真的想要符號鏈接安全檢查,可以這樣做:

DocumentRoot "/www/htdocs"
<Directory "/">
  Options FollowSymLinks
</Directory>

<Directory "/www/htdocs">
  Options -FollowSymLinks +SymLinksIfOwnerMatch
</Directory>
Shell

這至少避免了對DocumentRoot路徑的額外檢查。請注意,如果文檔根目錄之外有任何Alias或RewriteRule路徑,則需要添加類似的部分。為了獲得最高性能,并且沒有符號鏈接保護,請在任何地方設置FollowSymLinks,并且永遠不要設置SymLinksIfOwnerMatch。

AllowOverride無論您在URL空間中允許覆蓋(通常是.htaccess文件),Apache都會嘗試為每個文件名組件打開.htaccess。例如,

DocumentRoot "/www/htdocs"
<Directory "/">
  AllowOverride all
</Directory>
Shell

并且請求URI /index.html。然后Apache將嘗試打開/.htaccess,/www/.htaccess和/www/htdocs/.htaccess。解決方案類似于之前的Options FollowSymLinks案例。為獲得最高性能,請在文件系統(tǒng)中的所有位置使用AllowOverride None。

協(xié)商

盡可能避免內容協(xié)商。在實踐中,協(xié)商的好處超過了性能帶來的好處。有一種情況可以加快服務器的速度。使用如下的通配符并不是一個好的方法:

DirectoryIndex index
Shell

應該使用完整的選項列表:

DirectoryIndex index.cgi index.pl index.shtml index.html
Shell

內存映射在Apache 2.x需要查看正在傳遞的文件的內容的情況下 - 例如,在執(zhí)行服務器端包含處理時 - 如果操作系統(tǒng)支持某種形式的mmap,它通常會對文件進行內存映射(2)。

在某些平臺上,此內存映射可提高性能。但是,有些情況下內存映射會損害httpd的性能甚至穩(wěn)定性:

  • 在某些操作系統(tǒng)上,當CPU數(shù)量增加時,mmap不會像read(2)那樣擴展。例如,在多處理器Solaris服務器上,當禁用mmap時,Apache 2.x有時會更快地提供服務器解析的文件。
  • 如果內存映射位于NFS掛載的文件系統(tǒng)上的文件,并且另一個NFS客戶端計算機上的進程刪除或截斷該文件,則下次嘗試訪問映射文件內容時,進程可能會收到總線錯誤。

對于適用這些因素之一的安裝,應使用EnableMMAP off禁用已傳遞文件的內存映射。(注意:可以在每個目錄的基礎上覆蓋此指令。)

Sendfile在Apache 2.x可以忽略要傳遞的文件內容的情況下 - 例如,在提供靜態(tài)文件內容時 - 如果操作系統(tǒng)支持sendfile(2)操作,它通常會對文件使用內核sendfile支持。

在大多數(shù)平臺上,使用sendfile通過消除單獨的讀取和發(fā)送機制來提高性能。但是,有些情況下使用sendfile會損害httpd的穩(wěn)定性:

某些平臺可能已經破壞了構建系統(tǒng)未檢測到的sendfile支持,特別是如果二進制文件是在另一個盒子上構建并移動到這樣一臺具有損壞的sendfile支持的機器上的話。

使用NFS掛載的文件系統(tǒng),內核可能無法通過其自己的緩存可靠地提供網絡文件。

進程創(chuàng)建在Apache 1.3之前,MinSpareServers,MaxSpareServers和StartServers設置都對基準測試結果產生了極大的影響。特別是,Apache需要一個“加速”期,以便達到足以服務于所應用的負載的多個子項。初始產生StartServers子項后,每秒只會創(chuàng)建一個子項來滿足MinSpareServers設置。因此,一個服務器被100個并發(fā)客戶端訪問,使用默認的StartServers為5將需要95秒的時間來產生足夠的子進程來處理負載。這在實際服務器上的實際工作正常,因為它們不會經常重啟。但它的基準測試確實很差,可能只運行十分鐘。

實施每秒一次的規(guī)則是為了避免在新子項啟動的情況下淹沒機器。如果機器忙于產生子項,則無法提供服務請求。但它對Apache的感知性能產生了如此巨大的影響,必須予以取代。從Apache 1.3開始,代碼將放寬每秒一次的規(guī)則。它將產生一個,等待一秒,然后產生兩個,等待一秒,然后產生四個,它將以指數(shù)方式繼續(xù),直到它每秒產生32個子項。只要滿足MinSpareServers設置,它就會停止。

這似乎足夠響應,幾乎沒有必要扭轉MinSpareServers,MaxSpareServers和StartServers旋鈕。當每秒生成4個以上的子節(jié)點時,將向ErrorLog發(fā)送一條消息。

與進程創(chuàng)建相關的是由MaxConnectionsPerChild設置引起的進程死亡。默認情況下,它的值是0,這意味著每個孩子處理的連接數(shù)沒有限制。如果您的配置當前設置為某個非常低的數(shù)字,例如30,您可能希望顯著提高它。如果運行的是SunOS或舊版本的Solaris,請將此限制為10000左右,因為太高可能導致內存泄漏。

編譯時配置問題

選擇MPMApache 2.x支持可插入的并發(fā)模型,稱為多處理模塊(MPM)。構建Apache時,必須選擇要使用的MPM。某些平臺有特定于平臺的MPM:mpm_netware,mpmt_os2和mpm_winnt。對于一般的Unix類型系統(tǒng),有幾個MPM可供選擇。MPM的選擇會影響httpd的速度和可擴展性:

  • worker MPM使用多個子進程,每個進程有多個線程。每個線程一次處理一個連接。對于高流量服務器,worker通常是一個不錯的選擇,因為它比prefork MPM具有更小的內存占用。
  • 事件MPM像Worker MPM一樣具有線程,但旨在允許通過將一些處理工作傳遞給支持線程來同時提供更多請求,從而釋放主線程以處理新請求。
  • prefork MPM使用多個子進程,每個進程只有一個線程。每個進程一次處理一個連接。在許多系統(tǒng)上,prefork的速度與worker相當,但它使用更多的內存。在某些情況下,Prefork的無線設計優(yōu)于worker:它可以與非線程安全的第三方模塊一起使用,并且在具有較差線程調試支持的平臺上更容易調試。

模塊

由于內存使用是性能中非常重要的考慮因素,因此您應該嘗試消除實際上未使用的模塊。如果您已將模塊構建為DSO,則消除模塊只需注釋掉該模塊的相關LoadModule指令即可。這使您可以嘗試刪除模塊,并查看您的網站是否仍然在沒有這些模塊的情況下運行。

另一方面,如果您將模塊靜態(tài)鏈接到Apache二進制文件中,則需要重新編譯Apache以刪除不需要的模塊。

當然,這里出現(xiàn)的一個相關問題是,您列出需要哪些模塊,哪些模塊不需要。當然,這里的答案因網站而異。但是,您可以獲得的最小模塊列表往往包括mod_mime,mod_dir和mod_log_config。mod_log_config當然是可選的,因為可以運行沒有日志文件的網站。但是,不建議這樣做。

原子操作

一些模塊,例如mod_cache和worker MPM的最新開發(fā)版本,使用APR的原子API。此API提供可用于輕量級線程同步的原子操作。

默認情況下,APR使用每個目標OS/CPU平臺上可用的最有效機制來實現(xiàn)這些操作。例如,許多現(xiàn)代CPU具有在硬件中執(zhí)行原子比較和交換(CAS)操作的指令。但是,在某些平臺上,APR默認使用較慢的基于互斥鎖的原子API實現(xiàn),以確保與缺少此類指令的舊CPU模型兼容。如果要為其中一個平臺構建Apache,并且計劃僅在較新的CPU上運行,則可以通過使用--enable-nonportable-atomics選項配置Apache來在構建時選擇更快的原子實現(xiàn):

./buildconf
./configure --with-mpm=worker --enable-nonportable-atomics=yes
Shell

mod_status和ExtendedStatus On

如果包含mod_status并且在構建和運行Apache時也設置了ExtendedStatus On,那么在每次請求時,Apache都會執(zhí)行兩次調用gettimeofday(2)(或者根據您的操作系統(tǒng)的時間(2))和(1.3之前)幾次額外的調用time(2)。這一切都已完成,以便狀態(tài)報告包含時間指示。為獲得最高性能,請關閉ExtendedStatus(這是默認設置)。

接受序列化 - 單Socket

以上對于多個套接字服務器來說很好,但是單個套接字服務器呢?從理論上講,他們不應該遇到任何同樣的問題,因為所有的子線程都可以阻止accept()直到連接到來,并且不會產生饑餓。在實踐中,這隱藏了上面在非阻塞解決方案中討論的幾乎相同的“旋轉”行為。大多數(shù)TCP堆棧的實現(xiàn)方式,內核實際上喚醒了單個連接到達時阻塞的所有進程。其中一個進程獲取連接并返回用戶空間。其余的東西在內核中旋轉,當他們發(fā)現(xiàn)沒有連接時再回到睡眠狀態(tài)。這種旋轉對用戶土地代碼是隱藏的,但它仍然存在。這可能導致相同的負載尖峰浪費行為,多個插座盒的非阻塞解決方案可以。

出于這個原因,我們發(fā)現(xiàn)如果我們甚至序列化單個插槽的情況,許多架構表現(xiàn)得更“漂亮”。所以這實際上是幾乎所有情況下的默認值。Linux下的粗略實驗(雙Pentium pro 166 w / 128Mb RAM上的2.0.30)表明,單插槽的串行化使得非串行化單插槽的每秒請求數(shù)減少不到3%。但是,非序列化的單插槽在每個請求上顯示額外的100ms延遲。這種延遲可能是長途線路上的沖洗,而且只是局域網上的一個問題。如果要覆蓋單個套接字序列化,可以定義SINGLE_LISTEN_UNSERIALIZED_ACCEPT,然后單個套接字服務器根本不會序列化。

附錄:跟蹤的詳細分析

以下是Apache 2.0.38的系統(tǒng)調用跟蹤以及Solaris 8上的worker MPM。此跟蹤是使用以下方法收集的:

truss -l -p httpd_child_pid.
Shell

-l選項告訴truss記錄調用每個系統(tǒng)調用的LWP(輕量級進程 - Solaris形式的內核級線程)的ID。

其他系統(tǒng)可能具有不同的系統(tǒng)調用跟蹤實用程序,例如strace,ktrace或par。它們都產生類似的輸出。

在此跟蹤中,客戶端已從httpd請求了一個10KB的靜態(tài)文件。具有內容協(xié)商的非靜態(tài)請求或請求的痕跡看起來非常不同。

/67:    accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
/67:    accept(3, 0x00200BEC, 0x00200C0C, 1)            = 9
Shell

在此跟蹤中,偵聽器線程在LWP#67中運行。

/65:    lwp_park(0x00000000, 0)                         = 0
/67:    lwp_unpark(65, 1)                               = 0
Shell

在接受連接時,偵聽器線程喚醒工作線程以執(zhí)行請求處理。在此跟蹤中,處理請求的工作線程將映射到LWP#65。

/65:    getsockname(9, 0x00200BA4, 0x00200BC4, 1)       = 0
Shell

為了實現(xiàn)虛擬主機,Apache需要知道用于接受連接的本地套接字地址。在許多情況下(例如,當沒有虛擬主機,或者使用沒有通配符地址的Listen指令時),可以消除此調用。但是還沒有做出這些優(yōu)化的努力。

/65:    brk(0x002170E8)                                 = 0
/65:    brk(0x002190E8)                                 = 0
Shell

brk()調用從堆中分配內存。在系統(tǒng)調用跟蹤中很少見到這些,因為httpd使用自定義內存分配器(apr_pool和apr_bucket_alloc)進行大多數(shù)請求處理。在此跟蹤中,httpd剛剛啟動,因此必須調用malloc()來獲取用于創(chuàng)建自定義內存分配器的原始內存塊。

/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
Shell

接下來,worker 線程以非阻塞模式將連接放入客戶端(文件描述符9)。setsockopt()和getsockopt()調用是Solaris的libc如何在套接字上處理fcntl()的副作用。

/65:    read(9, " G E T   / 1 0 k . h t m".., 8000)     = 97
Shell

worker線程從客戶端讀取請求。

/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
Shell

此httpd已使用Options FollowSymLinks和AllowOverride None進行配置。因此,它不需要lstat()導致所請求文件的路徑中的每個目錄,也不需要檢查.htaccess文件。它只是調用stat()來驗證文件:1)是否存在,2)是常規(guī)文件,而不是目錄。

/65:    sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C)      = 10269
Shell

在此示例中,httpd能夠使用單個sendfilev()系統(tǒng)調用發(fā)送HTTP響應頭和所請求的文件。Sendfile語義因操作系統(tǒng)而異。在某些其他系統(tǒng)上,必須執(zhí)行write()或writev()調用以在調用sendfile()之前發(fā)送標頭。

/65:    write(4, " 1 2 7 . 0 . 0 . 1   -  ".., 78)      = 78
Shell

此write()調用在訪問日志中記錄請求。請注意,此跟蹤中缺少的一件事是time()調用。與Apache 1.3不同,Apache 2.x使用gettimeofday()來查找時間。在某些操作系統(tǒng)(如Linux或Solaris)上,gettimeofday具有優(yōu)化的實現(xiàn),不需要像典型系統(tǒng)調用那樣多的開銷。

/65:    shutdown(9, 1, 1)                               = 0
/65:    poll(0xFAF7B980, 1, 2000)                       = 1
/65:    read(9, 0xFAF7BC20, 512)                        = 0
/65:    close(9)                                        = 0
Shell

worker 線程會延遲關閉連接。

/65:    close(10)                                       = 0
/65:    lwp_park(0x00000000, 0)         (sleeping...)
Shell

最后,工作線程關閉它剛剛傳遞的文件并阻塞,直到偵聽器為其分配另一個連接。

/67:    accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)
Shell

同時,監(jiān)聽器線程一旦將此連接分派給工作線程,就能夠接受另一個連接(受制于工作者MPM中的某些流控制邏輯,如果所有可用工作者都忙,則會限制監(jiān)聽器)。雖然從這個跟蹤中看不出來,但是下一個accept()可以(并且通常在高負載條件下)與工作線程處理剛剛接受的連接并行發(fā)生。




以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號