簡介:使用 Apache Web 服務器作為示例,以了解如何分析公共配置的性能含義。使用應用程序跟蹤 來觀察應用程序運行過程中進行的系統調用。通過統計調用的次數和發生的時間,您可以輕松地了解性能 改變的影響。
您可以對應用程序進行跟蹤,以找出它們暫停或不運行的原因。並且可以使用同樣 的方法,了解更多關於應用程序的信息並理解某些配置的性能含義。因為 Apache 非常流行並且大多數讀 者對它都比較熟悉,所以本文使用 Apache 作為示例。Apache 所進行的每次系統調用都會對 Web 頁面的 提供帶來延遲,通過跟蹤不同配置下的 Web 服務器,您可以確定具體配置的影響。
應用程序跟蹤 概述
在應用程序的執行過程中,當需要打開文件、發送數據包或者使用系統資源時,它會對基礎 操作系統進行相應的系統調用。跟蹤應用程序意味著可以在調用發生時觀察到這些系統調用,這將使得您 能夠深入地了解該應用程序的行為。在 Solaris 和 IBM AIX® 操作系統 (AIX) 中,使用 truss 命 令完成這項任務,而在 Linux® 中則使用 strace。清單 1 顯示了對 pwd 命令進行跟蹤的示例。
清單 1. 跟蹤 pwd 命令
-bash-3.00$ truss pwd
...
getcwd ("/export/home/sean", 1025) = 0
/export/home/sean
write(1, " / e x p o r t / h o m e".., 18) = 18
_exit(0)
在刪除開始 處與加載該應用程序相關的輸出內容後,您可以看到所進行的三次系統調用:
getcwd 返回當前工作目錄。輸出內容中顯示了字符串“/export/home/sean”返回到緩沖 區。
write 可以顯示給定的字符串。因為在其執行之後才顯示這個系統調用,所以先輸出了它的執行結果 。還可以注意到,write 系統調用的結果是寫入字符的個數數目,在這個示例中為 17 加上一個回車。
_exit 使用錯誤代碼 0 退出該程序,這個錯誤代碼通常表示成功結束。
盡管這是一個很簡單的示例,但它演示了通過應用程序跟蹤可以觀察程序內部機制的程度。有關跟蹤 方面更深入的信息,請參見參考資料部分。
Apache 配置的簡要介紹
可以通過一個名為 httpd.conf 的文件對 Apache Web 服務器進行配置。清單 2 顯示了一個簡單配置中的部分內容。
清單 2. 示例 httpd.conf
DocumentRoot "/var/apache/htdocs"
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory "/var/apache/htdocs">
Options Indexes FollowSymLinks MultiViews
</Directory>
第一行定義了在何處可 以找到 HTML 文件。將所有的請求都映射到這個目錄。如果請求 /project/charter.html,將使用 /var/apache/htdocs/project/charter.html 提供該頁面。httpd.conf 中剩余的部分由兩個 Directory 節組成。<Directory ...> 和 </Directory> 之間的任何內容都將應用於指定目錄及其所有 的子目錄。在本例中,第一節將兩項設置應用於根目錄,而第二節則指向 /var/apache/htdocs。
如果多個節應用於單個請求,那麼將會對這些命令進行組合,並且最適合的目錄具有高優先級。例如,將 使用 /var/apache/htdocs/project/charter.html 提供 /project/charter.html 請求的頁 面。/var/apache/htdocs 是 / 的子目錄,所以 Options Indexes FollowSymLinks MultiViews 來自第 二節,AllowOverride None 來自第一節。
可以對許多內容進行配置,並且每項內容都具有其性能 含義。本文余下的內容重點關注於如何對這些改變的影響進行量化分析。
建立基准
在您進 行任何調整之前,務必要了解系統當前的運行方式。使用 -X 參數啟動 Apache,這個參數將強制 Apache 進入單進程調試模式。這樣做可以確保將請求發送到正在進行跟蹤的進程,並且消除常規進程間通信所帶 來的開銷。
在守護進程啟動之後,通過運行 ps -ef 找到相應的進程 ID,並查找 httpd 守護進 程。在找到該進程之後,使用 truss -c -p PID 附加到該進程。-c 選項表示對系統調用進行計數,而不 是逐個顯示它們,而 -p 則表示將跟蹤器附加於一個正在運行的進程。
使用 Web 浏覽器請求文檔 。在頁面加載之後,回到 truss 應用程序,然後按 Ctrl-C 以結束計數。對於靜態 HTML 頁面,您應該 看到如清單 3 所示的內容(為使這些數字變得更有趣,本示例進行了 100 次相同的請求)。
清 單 3. 系統調用基准
sunbox# truss -c -p 15026
(make the Web request 100 times)
^C
syscall seconds calls errors
read .009 200
write .020 200
close .020 200
time .004 300
alarm .018 1100
fcntl .009 300
sigaction .007 400
munmap .007 100
llseek .001 100
pollsys .005 100
mmap64 .008 100
stat64 .007 100
open64 .006 100
accept .019 100
getsockname .002 100
setsockopt .002 100
-------- ------ ----
sys totals: .149 3600 0
usr time: .120
elapsed: 8.960
truss 返回系統調用的列表、執行調用耗費的總時間 、調用的次數和任何發現的問題。在這個報表的結尾處,返回了這些系統調用耗費的總時間,以及執行應 用程序所耗費的時間。對於這些目的來說,所耗時間是沒有意義的,因為它指的是從啟動 truss 開始到 其結束的時間,而與 Web 請求沒有任何關系。
清單 3 顯示了最簡單的情況。在來自 Web 浏覽器 的連接請求到達後,accept 系統調用完成該連接。Web 服務器使用 read 調用獲得請求的內容,將請求 的內容映射到磁盤上的文件。Web 服務器首先使用 stat64 驗證該文件是否存在,使用 open64 打開該文 件以便進行讀取,然後使用 mmap64 將該文件的內容映射到內存中。然後使用 write 將這個文件發送回 客戶端,使用另一個 write 生成日志文件,並且服務器執行來自浏覽器的最後一個 read。該列表中其余 的調用都是系統開銷,並且當配置發生改變時,不會有顯著的變化。
解釋這些數值
100 次 請求總共耗費 0.269 秒 (0.149 + 0.120),這樣的性能相當不錯,並且該服務器每秒鐘應該可以提供大 約 370 個頁面 (100/0.269)。但是不能完全相信這些數值,因為它們僅表示進程耗費在 CPU 上的時間, 而不是其真正的執行時間(也稱為時鐘時間)。還有更多的因素需要考慮,如磁盤和網絡的速度、計算機 上正運行的其他內容、該守護進程運行於調試模式的事實。您還需要考慮系統調用跟蹤本身的開銷。
本文中使用的方法重點關注這些操作的相對計時和使用應用程序跟蹤消除浪費掉的操作。如果您 需要了解 Web 服務器每秒可以提供的頁面數目,參考資料部分中有相應的軟件鏈接,它可以幫助您確定 該數值。
跟蹤 AllowOverride 范圍
Apache 允許管理員通過 .htaccess 機制將配置權委 托給個別的用戶。.htaccess 是一個包含附加配置指令的文件,如果在 httpd.conf 中通過 AllowOverride 配置了請求的目錄,那麼 Web 服務器將搜索這個文件。清單 4 顯示了前面的配置了 AllowOverride Limit 的配置信息,它允許用戶獲取訪問 Web 頁面的用戶名和密碼。
清單 4. 配 置了 AllowOverride 的示例 httpd.conf
DocumentRoot "/var/apache/htdocs"
<Directory />
Options FollowSymLinks
AllowOverride Limit
</Directory>
<Directory "/var/apache/htdocs">
Options Indexes FollowSymLinks MultiViews
</Directory>
重新啟動 httpd 守護進程並再次運行這些測試,其結果如清單 5 所示。
清單 5. 開啟了 AllowOverride Limit 的 100 次請求的結果
sunbox# truss -c -t write,read,open64,stat64,mmap64 -p 21136
^C
syscall seconds calls errors
read .012 200
write .021 200
mmap64 .007 100
stat64 .007 100
open64 .022 500 400
- ------- ------ ----
sys totals: .072 1100 400
usr time: .141
elapsed: 16.660
初看起來,系統調用耗費的時 間下降了,但這是因為使用了 -t 選項將跟蹤任務限制於一些有意義的系統調用。大多數系統調用並沒有 發生變化,但現在有 500 次 open64 調用,其中有 400 次返回了錯誤。執行 open64 的時間增加了(從 0.006 秒增加到 0.22 秒),同時用戶空間部分的時間也增加了(從 0.12 秒增加到 0.141 秒)。
時間增加是因為 Apache 現在必須完成附加的工作以處理該請求,即使沒有配置重寫。單獨的 AllowOverride Limit 配置明顯地增加了開銷。問題依然存在,即什麼導致了這些錯誤? 要回答這個問 題,可以跟蹤單個 Web 請求,如清單 6 所示。
清單 6. 確定 open64 調用失敗的原因
sunbox# truss -t open64 -p 21136
open64("/.htaccess", O_RDONLY) Err#2 ENOENT
open64("/var/.htaccess", O_RDONLY) Err#2 ENOENT
open64("/var/apache/.htaccess", O_RDONLY) Err#2 ENOENT
open64("/var/apache/htdocs/.htaccess", O_RDONLY) Err#2 ENOENT
open64("/var/apache/htdocs/test.html", O_RDONLY) = 5
清單 6 顯示了 當請求進入時,Apache 對每個指向 /var/apache/htdocs 的目錄進行檢查並嘗試打開 .htaccess 文件, 但是因為 AllowOverride 配置為根目錄,所以這個文件並不存在。Apache 必須在每個子目錄中查找 .htaccess 文件的重寫信息,並對它們進行處理。這樣一來,由於額外的系統調用、更多的用戶空間開銷 和額外的磁盤活動,從而進一步增加了延遲。對於 100 次請求來說,增加零點幾秒的時間看起來並不是 很明顯,但是對於一台繁忙的服務器,就會增加更長的延遲。
既然您了解了重寫的范圍,那麼理 想的解決方案是不允許重寫,並且強制在 httpd.conf 中對所有的內容進行配置。如果失敗,可以將配置 的范圍限制於需要它的目錄。在這個研究示例中,把 AllowOverride Limit 放到第二個 Directory 節中 ,這將僅添加一個額外的 open64 調用,以便在 /var/apache/htdocs 中查找 .htaccess。對所有的父目 錄進行搜索是浪費時間,因為在這個配置中,不會使用其中的任何文件來提供頁面。
研究主機名 查找
當 Web 服務器接收到一個請求時,它所知道的關於客戶端的信息只有其 IP 地址,如 129.42.42.212 對應於 IBM.com。然而,Web 服務器並不知道這個地址來自於 IBM.com,因此,它必須進 行反向 DNS 查找。這樣做需要耗費一定的時間,如果在發送請求之前需要這個名稱,那麼將會延遲對客 戶端的響應。過去,Apache 在缺省情況下會執行這些反向查找,但現在這種行為已經有了改變。
還有另一種情況,其中必須進行反向 DNS 搜索。當基於主機名(而不是 IP 地址)配置訪問限制時, Apache 必須首先將 IP 地址反向解析為相應的主機名,然後再次將主機名解析為 IP 地址,以確保它們 正確匹配。因為反向域名搜索可以由地址塊所有者確定,所以要防止 IP 欺騙的發生,必須進行第二次查 找。可以通過應用程序跟蹤來確定 DNS 解析的影響嗎?
要對其進行測試,可以從前面的示例中刪 除 AllowOverride Limit,然後添加 Allow from ibm.com 代替缺省的 Allow from all。然後,對 DNS 服務器進行更改以便向您的工作站返回 something.ibm.com,確保初始反向檢查能夠成功並且隨後的正向 查找必須通過 Internet 完成。在示例運行過程中,對保護的 Web 服務器的請求耗費了 15 秒的時間。 相反,使用 IP 地址代替 ibm.com 所耗費的時間小於半秒鐘。清單 7 顯示了在使用 DNS 確保安全時, 對一些系統調用進行統計。
清單 7. 通過主機名進行限制的 Web 請求的 truss 輸出
bash-3.00# truss -c -p 26089
^C
syscall seconds calls errors
read .000 5
write .000 3
open .000 2
close .001 10
time .000 3
stat .000 5
alarm .000 8
fcntl .000 7 2
sigaction .000 3
sysconfig .000 5
pollsys .001 5
door_info .000 2
stat64 .000 1
open64 .000 2
so_socket .001 5
accept .000 1
connect .002 5
recvfrom .000 2
send .001 5
getsockname .000 1
setsockopt .000 1
-------- ------ ----
sys totals: .011 81 2
usr time: .004
truss 報告了該進程所耗費的時間遠遠小於客戶端感覺到的時間(0.015 秒與 15 秒)。這是因為大多數套接字操作都是異步地 進行的,其中套接字進行輪詢以檢查數據是否出現,而不 是使得應用程序處於阻塞(等待)狀態以等待響應。如此一來,應用程序在等待結果的時候不會消耗 CPU 時間。這就解釋了 truss 報告的時間和客戶端感覺到的時間之間出現差異的原因。
truss 並沒有 忘記所做的更改,0.015 秒比本文中研究的第一個簡單示例要高一個數量級。從系統調用計數中可以看出 ,有一些以前沒有出現過的調用,包括 read、write、close 和 stat。以及還有 send、connect、 so_socket 和 pollsys,這些系統調用用於進行名稱請求。因為名稱解析可以來自於不同的來源,包括本 地文件系統和名稱緩存守護進程,所以必須對這幾個位置進行檢查。在隨後的調用中,請求時間小於 1 秒,這是因為對 DNS 信息進行了緩存。
最後,大部分的延遲來自於遠程名稱服務器和正向解析。 這個事實進一步強調了,如果您希望依賴於名稱查找,那麼就需要使用 DNS 緩存和快速 DNS 服務器。然 而,最佳的解決方案是使用一種可選的方法來處理這個問題。一種比較簡單的解決方案是指定 IP 地址塊 (如 Allow from 10.0.0.0/8),這種方法比 DNS 查找要快得多。因為反向和正向查找必須匹配,所以 對名稱進行的操作,很可能可以用於網段。另外,Apache 可以集成各種身份驗證系統,所以基於用戶的 身份驗證是另一種可選方法。
系統調用與庫調用
如果熟悉套接字編程,那麼您可能會尋找 gethostbyname 和其他類似的調用,這些調用都可以用來執行主機查找功能。有一些庫調用,由 /usr/lib 中的系統庫 libc 和 libsocket 提供。庫調用封裝了一個或多個系統調用,以及額外的邏輯。 您可以將它們作為程序員友好的接口來進行系統調用。例如,gethostbyname 庫調用將執行許多步驟,以 便根據服務器的配置查找相應的名稱,如檢查 /etc/hosts 或搜索網絡信息系統 (NIS) 或 DNS。這些步 驟包含了內核所提供的更小的構件,即系統調用。在大多數這樣的這種情況下,兩者之間的差別通常很小 。truss 還可以跟蹤庫調用,但是無法提供像系統調用那樣詳細的信息。
結束語
監視和統 計系統調用的功能不僅有助於進行故障排除,還有助於理解應用程序配置如何對性能產生影響。與系統的 其他部分之間的每個交互操作都會調用一個或多個系統調用,並且每次調用都會增加系統開銷。這並不是 說系統調用非常糟糕,如果程序員不與其他的部分進行交互,那將是很乏味的。相反,在改變應用程序時 ,您可以監視系統調用的使用,以便更好地了解這些改變對總體性能的影響。