apache-實戰FastCGI
1.1 令人頭痛的效率問題
1.2 一些解決之道
1.3 更好的方法 - FastCGI
2. 安裝 FastCGI
2.1 在apache伺服器上安裝 FastCGI 模組
2.1.1 標準安裝 (利用 APACI)
2.1.2 將 mod_fastcgi 安裝成一個 DSO
2.2 加入使用 mod_fastcgi 的相關設定
2.3 安裝 FastCGI 開發套件
2.4 測試 FastCGI
2.5 安裝 FCGI 模組 for Perl
3. 撰寫 FastCGI 應用程式
3.1 程式架構
3.2 引入 fcgi_stdio.h 標頭檔
3.3 FastCGI 處理迴圈
3.4 煉結 libfcgi.a 函式庫
3.5 撰寫 FastCGI 程式的注意事項
4. FastCGI 有多快?
4.1 評比工具 - ApacheBench
4.2 CGI vs. FastCGI
4.3 找出 Memory Leak
5. 參考
About this document ...
----------------------------------------------------------------------------
1. 克服 CGI 的瓶頸
1.1 令人頭痛的效率問題
拜 CGI 之賜,網站不再只有固定不變的圖形和文字,藉由程式動態產生的網頁可以讓網站好象『活』了起來。小從簡單的網頁計數器,留言版,大至處理眾多資料的搜尋引擎,可做線上實時交易的電子商務、網路下單等。CGI 簡單、開放、跨平臺、與程式語言獨立的特性,使得撰寫網站應用程式變得很容易。
但隨著網站使用量日增,這些 CGI 程式從原本動態網頁的功臣,突然成了網站效率的頭號殺手。由於 CGI 先天的限制1,突然湧入大量的聯機請求 (request) ,常會造成網站主機瞬間資源被佔用,彷佛『當機』一樣,或是處理速度變得很慢。
另一個常遇到的限制是和資料庫聯機的問題,如果 CGI 程式後端需要聯機至資料庫執行指令再取得結果,突然大量的聯機請求可能會超過資料庫系統容許聯機的上限 (例如資料庫系統使用者數目的限制)。
因此對一個主要以使用 CGI 程式製作動態網站的開發者而言,解決 CGI 執行效率瓶頸成了一個頭痛的問題。以一個股市實時行情報價的網站為例,每天的聯機請求將近八成集中在股市開盤的尖峰時段內,更是對網站應用程式極大的考驗。
1.2 一些解決之道
現在已經有許多方案被提出來以解決 CGI 執行效率上的瓶頸,在『用 FastCGI 加速你的網站』一文中也有簡單的說明,這裡僅就筆者在開發股市實時報價的網站應用程式時,所嘗試過的一些方法提出個人的經驗和意見。以筆者的案例而言,原本的 CGI 程式是以 C 語言寫的,並且用了其它的 C 函式庫所以下列的方法主要是以提供 C 語言開發環境的方案為主。
NSAPI
由於原先網站是在 Unix 系統上,網站伺服器使用網景 Enterprise Server,所以最早想到是用 NSAPI 來改寫網站應用程式。在網景的網站上有非常詳細的 NSAPI 使用手冊,不幸的是沒有中文手冊。要用 NSAPI 改寫網站應用程式最麻煩的是你要把所有程式編譯成動態函式庫 (share library),以供 Enterprise Server 在 run-time 時期可以動態呼叫這些程式。由於利用 NSAPI 所寫的程式是直接從 Web Server 的執行空間內被呼叫,所以速度最快,但是程式必須遵循 Enterprise Server 的撰寫規則,而且一旦程式發生錯誤, Web Server 也會受影響。
ISAPI
相較於 NSAPI ,在 Microsoft NT IIS (Internet Information Server) 平臺就是 ISAPI 了。類似 NSAPI ,利用 ISAPI 撰寫網站應用程式,必須把應用程式編譯成動態函式庫,也就是 DLL 檔。它的執行速度也很快,但要遵循 ISAPI 的撰寫規則和資料結構,程式發生錯誤時也會影響 IIS Server 的正常運作。
綜觀以上兩種以 Web Server API 為主的方案 (其實 Apache 也有相對應的 Server API,只是用的人可能更少) ,它們的執行速度都很快,就產生動態網頁而言比 CGI 快上好幾倍。但是就程式開發者的角度2來看,它們有一些缺點:
1. NSAPI 及 ISAPI 與網站平臺相依性太高 (Platform dependency),也就是說使用了 NSAPI 或 ISAPI 後,應用程式就完全受限於所使用的網站伺服器平臺,不能變換所使用的網站伺服器。不像 CGI 完全不受網站平臺的限制,可以在任何網站伺服器 (Netscape, Microsoft IIS, Apache, NCSA)上執行。另外像 ISAPI 更只能限制在 Windows NT 平臺上使用。
2. NSAPI 及 ISAPI 只提供 C 程式語言的介面,亦即開發者一定要使用 C 語言開發。不像 CGI 是與開發者所使用的程式語言完全無關,除了 C 之外,常用的還有 Perl,Tcl等。
3. Netscape Enterprise Server 和 Microsoft IIS 都是以多執行緒 (Multi-Threads) 的方式處理 NSAPI 及 ISAPI 的程式,所有執行緒共享同一塊變數空間,因此在變數資料的處理上要特別小心,以確保每一個執行緒內的變數資料的安全,不會互相影響。
4. NSAPI 和 ISAPI 應用程式都是直接在伺服器的執行行程 (process) 內被呼叫,如果程式當掉了,整個網站伺服器都會被影響。CGI 當掉伺服器會響應 Internal Server Error 的訊息,伺服器本身不受影響。
5. NSAPI 和 ISAPI 應用程式必須被伺服器呼叫才會被執行,偵錯 (debug) 相當不容易。
1.3 更好的方法 - FastCGI
如果你正飽受 CGI 效率不佳之苦,又不想受限於 NSAPI 及 ISAPI ,也沒有大筆銀子去買昂貴的 Application Server,我建議你試試看 FastCGI。
不同於 NSAPI 及 ISAPI 以及其它的網頁伺服器語言 (如 ASP, PHP3, mod_perl),FastCGI 比較類似 CGI,它只是一個網站應用程式設計的規格,因此先天上不受任何網站伺服器平臺,作業系統平臺,以及開發語言的限制,但又能大幅改善 CGI 效率不良的問題。FastCGI 的特色如下:
1. FastCGI 像是一個常駐 (long-live) 型的 CGI,它可以一直執行著,只要啟用後,不會每次都要花費時間去 fork 一次 (這是 CGI 最為人詬病的 fork-and-execute 模式)。
2. FastCGI 可在任何平臺上使用,Netscape Enterprise 及 IIS 都有 FastCGI 的模組可供使用,apache (Apache,以及利用 Apache 衍生出做的伺服器) 上也有 mod_fastcgi 可用。
3. FastCGI 支援 C/C++,Perl,Tcl,Java,Python 等程式語言。
4. FastCGI 的應用程式亦相容於 CGI。即 FastCGI 的應用程式也可以當成 CGI 來執行。
5. 現有的 CGI 程式要改寫成 FastCGI 非常簡單,最少可能只需要多加入三行程式程式碼。
6. FastCGI 的偵錯方式與 CGI 大同小異,只要帶入程式所需的環境變數及引數,即可在命令列模式執行或偵錯。
7. FastCGI 應用程式的寫作方式與 CGI 類似,除了幾項原則要特別注意外,FastCGI 的寫作方式跟 CGI 幾乎一樣,與學習 Web Server API 比較起來, FastCGI 簡單多了。
8. FastCGI 支授分散式運算 (distributed computing),即 FastCGI 程式可以在網站伺服器以外的主機上執行並且接受來自其它網站伺服器來的請求。
看到 FastCGI 這些特色後,是否躍躍欲試呢。下一章將介紹如何在 Apache 伺服器上安裝 FastCGI 的步驟。
----------------------------------------------------------------------------
2. 安裝 FastCGI
要使用 FastCGI 你必需有一個可供 FastCGI 程式執行的環境 (run-time environment),以及 撰寫 FastCGI 程式的開發環境。以下就以apache伺服器 (Apache Web Server) 做為 FastCGI 的執行平臺,簡述如何在apache伺服器使用 FastCGI。
由於apache伺服器自由、開放、跨平臺的特性,現今許多系統或發行套件 (distribution) 都內含apache伺服器,如果你直接用預先編譯好的apache程式,請自行找出符合該系統設定規則的安裝路徑。以下列出一些apache伺服器相關的路徑設定規則,後面的範例將以apache內定值為主,其它的系統請自行參考:
系統 執行檔案路徑 設定檔案路徑
apache內定值 /usr/local/apache/bin /usr/local/apache/etc
FreeBSD /usr/local/sbin /usr/local/etc/apache
Red Hat Linux /usr/sbin /usr/etc
2.1 在apache伺服器上安裝 FastCGI 模組
安裝 mod_fastcgi 這個模組,可以讓你的apache伺服器支援 FastCGI 協議。mod_fastcgi 現在最新版本為 2.2.2 版,此版主要適用於 Apache 1.3 版以上。如果你的 Apache 還是 1.2 版,請配合 mod_fastcgi 2.0.18 版使用。以下設定以 Apache 1.3.6 及 mod_fast 2.2.2 為示範。
2.1.1 標準安裝 (利用 APACI)
1. 首先下載 apache_1.3.6.tar.gz 及 mod_fastcgi_2.2.2.tar.gz ,解開:
$ gunzip -c apache_1.3.6.tar.gz | tar xvf -
$ gunzip -c mod_fastcgi_2.2.2.tar.gz | tar xvf -
2. 把 mod_fastcgi 的原始碼複製到 Apache 的目錄下:
$ cp -rp mod_fastcgi_2.2.2 apache_1.3.6/src/modules/fastcgi
3. 設定 Apache 加入 mod_fastcgi 模組:
$ cd apache_1.3.6
$ ./configure
-activate-module=src/modules/fastcgi/libfastcgi.a
-enable-module=info -enable-shared=info
[ more APACI options ]
4. 編譯及安裝
$ make
$ make install
5. 看一下編譯出來的執行檔案是否含有 mod_fastcgi 模組:
$ /usr/local/apache/sbin/httpd -l
Compiled-in modules:
http_core.c
...
mod_fastcgi.c
...
2.1.2 將 mod_fastcgi 安裝成一個 DSO
假設你已經在系統上安裝好 Apache 1.3 版以上,並且你的平臺支援 DSO (Dynamic Shared Object) 的方式動態加入模組,那麼你可以透過 apxs (APache eXtenSion tool) 將 mod_fastcgi 安裝成一個 DSO 模組。
1. 下載 mod_fastcgi_2.2.2.tar.gz 並且解開:
$ gunzip -c mod_fastcgi_2.2.2.tar.gz | tar xvf -
2. 編譯 mod_fastcgi 模組成 DSO:
$ cd mod_fastcgi_2.2.2
$ /usr/local/apache/sbin/apxs -o mod_fastcgi.so -c *.c
3. 安裝
$ /usr/local/apache/sbin/apxs -i -a -n fastcgi mod_fastcgi.so
2.2 加入使用 mod_fastcgi 的相關設定
為了讓 Apache 區分出那一些聯機請求屬於 FastCGI 來處理的,我們必需在apache的設定檔內加入讓 Apache 可以辨別 FastCGI 的設定。
1. mod_fastcgi 模組會向 Apache 登記一個 fastcgi-script 的處理型別 (handler type),我們可以設定所有以 fcg 以及 fpl (for perl) 為副檔名的程式都是符合 FastCGI 協議的應用程式:
AddHandler fastcgi-script .fcg .fpl
2. 接下來我們定義 /usr/local/www/fcgi-bin 這個目錄用來存放已經寫好的 FastCGI 程式:
ScriptAlias /fcgi-bin/ /usr/local/www/fcgi-bin/
3. 檢查設定檔文法是否正確:
$ /usr/local/apache/sbin/apachectl configtest
Syntax OK
4. 重新啟用apache伺服器,讓新設定生效:
$ /usr/local/apache/sbin/apachectl graceful
/usr/local/apache/bin/apachectl graceful: httpd gracefully restarted
Apache 1.3.4 版之後將原本的 httpd.conf 、srm.conf、access.conf 合併成一個檔案。所以你所使用的 Apache 如果是 1.3.4 版之後,請直接修改 httpd.conf3 這個檔,如果是 1.3.3 版之前,我建議把 mod_fastcgi 模組相關設定加在 srm.conf 這個檔裡頭。實際的設定檔案路徑和設定檔案檔名可能依每個人的環境不同而有差異,請根據您自己的環境做適當的調整。
2.3 安裝 FastCGI 開發套件
1. 下載 fcgi-devkit-2.1.tar.gz 並且解開:
$ gunzip -c fcgi-devkit-2.1.tar.gz | tar xvf -
2. 編譯
$ cd fcgi-devkit-2.1
$ ./configure
$ make
3. 將 C 的標頭檔 (header file) 及函式庫 (library) 安裝至系統:
$ cp -rp include /usr/local/include/fastcgi
$ cp libfcgi/libfcgi.a /usr/local/lib
2.4 測試 FastCGI
在 fcgi-devkit 套件中內含一個簡單的 FastCGI 範例程式 - echo.c,我們用它來做測試系統是否安裝正確。直接把已經編譯好的 echo.fcg 複製到預設放置 FastCGI 程式的目錄下:
$ cd fcgi-devkit-2.1/example
$ cp echo.fcg /usr/local/www/fcgi-bin
現在趕快用瀏覽器連到 http://localhost/fcgi-bin/echo.fcg 看看,如果看到以下結果表示您大功告成啦:
FastCGIecho
Requestnumber1,ProcessID:1013
Nodatafromstandardinput.
Requestenvironment:
FCGI_ROLE=RESPONDER
DOCUMENT_ROOT=/usr/local/apache/htdocs
HTTP_ACCEPT=text/html,text/plain,application/applefile,application/x-metamai
l-patch,sun-deskset-message,mail-file,default,postscript-file,audio-file,
x-sun-attachment,text/enriched,text/richtext,application/andrew-inset,x-be2
,application/postscript,message/external-body,message/partial,application/p
gp,application/pgp,video/mpeg,video/*,image/*,audio/*,audio/mod,text/sgm
l,video/mpeg,image/jpeg,image/tiff,image/x-rgb,image/png,image/x-xbitmap,
image/x-xbm,image/gif,application/postscript,*/*;q=0.01
HTTP_ACCEPT_ENCODING=gzip,compress
HTTP_ACCEPT_LANGUAGE=en
HTTP_HOST=localhost
HTTP_NEGOTIATE=trans
HTTP_USER_AGENT=Lynx/2.8.1pre.9libwww-FM/2.14
PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/sbin:/opt/kde/bin:/home/m
yhsu/bin:/usr/X11R6/bin:/usr/sbin:/opt/kde/bin:/usr/X11R6/bin:/usr/sbin:/opt/kd
e/bin
REMOTE_ADDR=127.0.0.1
REMOTE_PORT=1024
SCRIPT_FILENAME=/usr/local/www/fcgi-bin/echo.fcg
SERVER_ADMIN=myhsu@localhost.localdomain
SERVER_NAME=localhost.localdomain
SERVER_PORT=80
SERVER_SIGNATURE=
Apache/1.3.6Serveratlocalhost.localdomainPort80
SERVER_SOFTWARE=Apache/1.3.6(Unix)mod_fastcgi/2.2.2
UNIQUE_ID=N1ptln8AAAEAAAPdDRkGATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.0REQUEST_METHOD=GETQUERY_STRING=
REQUEST_URI=/fcgi-bin/echo.fcgSCRIPT_NAME=/fcgi-bin/echo.fcg
Initialenvironment:
請注意在以上程式所顯示的 Request number 和 Process ID 這兩個變數,當我們繼續重新載入這支程式時,Request number 會一直累加而 Process ID 的值都不會改變。這表示這支程式在第一次啟用之後就一直執行著沒有結束,而且在每次聯機請求中所參照到的變數空間是相同的 (所以 Request number 會不斷加一)。
到此為止,我們已經成功建立起一個可供發展 FastCGI 應用程式的環境及執行 FastCGI 應用程式的網站執行平臺。
2.5 安裝 FCGI 模組 for Perl
如果要使用 Perl 來撰寫 FastCGI 的程式,必須安裝 FCGI.pm 這個模組,安裝的方法如下:
1. 下載 FCGI-0.45.tar.gz 並且解開
$ gunzip -c FCGI-0.45.tar.gz | tar xvf -
2. 編譯及安裝
$ perl Makefile.PL
$ make
$ make install
3. 測試
$ cp echo.fpl /usr/local/www/fcgi-bin
$ lynx http://localhost/fcgi-bin/echo.fpl
如果順利的話,應該會看到如下的結果:
FastCGI echo (Perl)
Request number 1
No data from standard input.
Request environment:
DOCUMENT_ROOT=/usr/local/apache/htdocs
FCGI_ROLE=RESPONDER
GATEWAY_INTERFACE=CGI/1.1
HTTP_ACCEPT=text/html, text/plain, application/applefile, application/x-metamai
l-patch, sun-deskset-message, mail-file, default, postscript-file, audio-file,
x-sun-attachment, text/enriched, text/richtext, application/andrew-inset, x-be2
, application/postscript, message/external-body, message/partial, application/p
gp, application/pgp, video/mpeg, video/*, image/*, audio/*, audio/mod, text/sgm
l, video/mpeg, image/jpeg, image/tiff, image/x-rgb, image/png, image/x-xbitmap,
image/x-xbm, image/gif, application/postscript, */*;q=0.01
HTTP_ACCEPT_ENCODING=gzip, compress
HTTP_ACCEPT_LANGUAGE=en
HTTP_HOST=localhost
HTTP_NEGOTIATE=trans
HTTP_USER_AGENT=Lynx/2.8.1pre.9 libwww-FM/2.14
PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/sbin:/opt/kde/bin:/home/m
yhsu/bin:/usr/X11R6/bin:/usr/sbin:/opt/kde/bin:/usr/X11R6/bin:/usr/sbin:/opt/kd
e/bin
QUERY_STRING=
REMOTE_ADDR=127.0.0.1
REMOTE_PORT=1427
REQUEST_METHOD=GET
REQUEST_URI=/fcgi-bin/echo.fpl
SCRIPT_FILENAME=/usr/local/www/fcgi-bin/echo.fpl
SCRIPT_NAME=/fcgi-bin/echo.fpl
SERVER_ADMIN=myhsu@localhost.localdomain
SERVER_NAME=localhost.localdomain
SERVER_PORT=80
SERVER_PROTOCOL=HTTP/1.0
SERVER_SIGNATURE=
Apache/1.3.6 Server at localhost.localdomain Port 80
SERVER_SOFTWARE=Apache/1.3.6 (Unix) mod_fastcgi/2.2.2
UNIQUE_ID=N1VIbX8AAAEAAAQnKKo
More on its way ... wait a few seconds
Initial environment:
同樣的,如果持續連結 http://localhost/fcgi-bin/echo.fpl 可以看到 Request Number 不斷增加,表示 echo.fpl 已經被啟用而且持續執行著。
至此,一個可供執行 FastCGI 程式的網站伺服器以及撰寫 FastCGI 程式的開發環境已經建置完成,接下來就只等著新的程式放上去囉。
----------------------------------------------------------------------------
3. 撰寫 FastCGI 應用程式
撰寫全新的 FastCGI 應用程式,或是將舊有的 CGI 程式改寫成 FastCGI 應用程式都非常的簡單,只要使用 fcgi-devkit 所附的 fcgi_stdio 函式庫即可。
基本上,fcgi_stdio 函式庫已被設計成讓開發人員撰寫 FastCGI 應用程式就像寫一般 CGI 程式一樣,同時做到程式保有和 CGI 最大的相容度,又能享受到 FastCGI 所帶來的優點。使用 fcgi_stdio 函式庫的另一項好處是,編譯出來的執行檔可同時以 CGI 以及 FastCGI 的方式執行。
3.1 程式架構
對 CGI 程式而言,其生命期就是從一個聯機請求 (request) 開始到聯機結束。而 FastCGI 程式就像是比較『長命』的 CGI 程式,其生命期橫跨不同的聯機請求,可從 Web 伺服器啟用開始到 Web 伺服器停止。
由於 FastCGI 程式長命的特性,它和一般 CGI 程式主要的差異就在於把初始化 (initialization) 的部份和處理聯機請求的部份區分開來,程式的架構如下所示:
Initialization Code
Start of response loop
body of response loop
End of response loop
Initialization Code 的部份只會在 FastCGI 程式啟用時執行一次,程式初始化的部份像是記憶體的配置,建立和資料庫的聯機等都可以寫在這裡。
而 Start of response loop 到 End of response loop 之間的程式在每次發生聯機請求時就會執行,這部份的程式才是真正處理每次聯機請求要做的事情。例如接受使用者輸入的引數,從資料庫取出資料,執行運算動作,回覆結果給使用者等等。
一個簡單的 FastCGI 程式如下 (tiny-fcgi.c)
#include "fcgi_stdio.h"
#include <stdlib.h>
void main(void)
{
/* Initialization Code */
int count = 0;
/* Start of response loop */
while(FCGI_Accept() >= 0) {
/* body of response loop */
printf("Content-type: text/html "
" "
"<title>FastCGI Hello! (C, fcgi_stdio library)</title>"
"<h1>FastCGI Hello! (C, fcgi_stdio library)</h1>"
"Request number %d running on host <i>%s</i> "
"Process ID: %d ",
++count, getenv("SERVER_NAME"), getpid());
}
/* End of response loop */
}
3.2 引入 fcgi_stdio.h 標頭檔
開始撰寫一個新的 FastCGI 應用程式時,必須引入 fcgi_stdio.h 這個標頭檔:
#include ``fcgi_stdio.h
如果要改寫舊有的 CGI 程式成 FastCGI 程式,請把原本引入 stdio.h 的部份換掉:
#ifndef FASTCGI
#include <stdio.h>
#else
#include ``fcgi_stdio.h
#endif
上面的寫法是利用 C 的前置編譯器做處理,如果在編譯時加入 -DFASTCGI 的引數則引入 fcgi_stdio.h ,反之則否。假設我們把 fcgi_stdio.h 標標頭檔案放在 /usr/local/include/fastcgi 這個目錄下的話,為了在編譯時引入 fcgi_stdio.h 標頭檔,請加入 -I/usr/local/include/fastcgi 的引數。
$ cc -I/usr/local/include/fastcgi -c program.c
注意
如果你的程式使用到其它的函式庫,請務必檢查這些函式庫是否有使用到 stdio.h 這個檔,如果有的話必須一併換成 fcgi_stdio.h。舉例來說,假如你的程式用到 GD 函式庫4來產生 GIF 圖形,請記得把 gd.h 中引入 stdio.h 的部份換成 fcgi_stdio.h,否則遇到 IO 的函式時會發生錯誤 (Core Dump)。
3.3 FastCGI 處理迴圈
在 FastCGI 程式中負責處理聯機請求的程式,必須用一個 while 迴圈包起來,利用 fcgi_stdio 函式庫中的 FCGI_Accept() 函式庫來判斷是否有新的聯機請求發生。
FastCGI 程式被啟用後,首先進行初始化的動作,如變數宣告、記憶體配置、或是與資料庫建立聯機等。執行到 FCGI_Accept() 時程式就會暫停,等到當某一個聯機請求發生時,FCGI_Accept() 會傳回大於零的值,程式即刻進入迴圈的主體,可能是執行 SQL 指令,運算以及產生 HTML 的輸出。處理完後又回到 FCGI_Accept() 迴圈的開始,等待下一個聯機請求。
由此可見, FastCGI 啟用之後,省去原本 CGI 程式中 fork,記憶體配置,建立資料庫聯機等步驟,處理每個聯機請求的時間幾乎等於 FCGI_Accept() 這個迴圈中運算所需的時間。如果妥善將每次聯機請求時一樣的程式程式碼從 FCGI_Accept() 迴圈抽出來,只保留每次會產生不同結果的部份,則程式處理每次聯機請求的時間可以更短,整個網站的效率也可以大幅的提升。
瞭解 FastCGI 程式的基本架構後,可以發現一般的 CGI 程式,只要加入 FCGI_Accept() 這個 while 迴圈後,大概就可以變成 FastCGI 的程式了。
3.4 煉結 libfcgi.a 函式庫
FastCGI 程式要煉結到 libfcgi.a 函式庫才可正確產生出執行檔,假設 libfcgi.a 的位置在 /usr/local/lib 這個目錄下,我們在編譯 (煉結時) 要加入 -L/usr/local/lib -lfcgi 的引數。
$ cc -o program.fcg program.o -L/usr/local/lib -lfcgi
3.5 撰寫 FastCGI 程式的注意事項
由於 FastCGI 程式被啟用後就常駐在記憶體之中,對每個 FastCGI 的執行行程 (running process) 而言,每次聯機請求共享的變數空間是相同的,因些選寫 FastCGI 程式要特別留意一些注意事項。
1. 記住,對每個 FastCGI 程式而言,只有 FCGI_Accept() 迴圈內的程式才是真正處理每次聯機請求的主體,假設在程式中要讀取和 CGI 有關的環境變數,或是使用者透過窗體 (form) 所輸入的資料,必須在 FCGI_Accept() 迴圈內才處理。
2. 對每次聯機請求會改變的變數,別忘了在處理新的聯機請求前把變數初始化 (除非是有特殊用途) ,以避免變數值在不同的聯機請求之間互相影響。
3. 在 FCGI_Accept() 迴圈中配置 (allocate) 的記憶體別忘了釋放 (free)。在一般的 CGI 程式中, Memory Leak 不算是太大的問題,因為每處理完一次聯機請求,所有用到的記憶體隨著 CGI 程式的結束也被釋放。但是 FastCGI 程式會一直常駐在記憶體中,如果在 FCGI_Accept() 中有配置額外的記憶體,在迴圈結束前沒有釋放掉,則每處理一次聯機請求就吃掉一些系統的記憶體,多跑幾次下來,系統資源大概就被耗光了。Memory leak 的問題是 FastCGI 最常見的錯誤,要特別小心處理。
4. FastCGI 程式和其所要引用的子程式中,用到 stdio.h 函式的部份,記得要改成 fcgi_stdio.h 。否則當程式在遇到 IO 的動作時就會發生 Core Dump 的情況。
5. 在 FCGI_Accept() 迴圈中使用 FCGI_Finish() 函式以取代 exit() 函式。原本 CGI 程式中發生錯誤時,可能直接呼叫 exit() 函式結束 CGI 行程,FastCGI 程式也可以如此,因為 mod_fastcgi 模組在 FastCGI 程式發生錯誤而意外結束時,會自動再啟用另一個 FastCGI 的行程。但比較好的作法是呼叫 fcgi_stdio.h 中的 FCGI_Finish() 函式,呼叫 FCGI_Finish() 函式會跳出目前程式正在運算中的迴圈,回到
FCGI_Accept() 等待下一個聯機請求。如此可以省去一些網站伺服器啟用新的 FastCGI 行程的負擔。
----------------------------------------------------------------------------
4. FastCGI 有多快?
看完安裝 FastCGI 的apache模組,以及無聊的程式設計注意事項後,我們來看看一些可以讓人振奮精神的資料,效能比較 (bench mark) 總是計算機玩家的最愛 icon_smile.gif
4.1 評比工具 - ApacheBench
在apache伺服器的套件中,有一個叫做 ab (ApacheBench) 的工具。ApacheBench 主要是用來測試apache伺服器執行效率用的,我們就以 ApacheBench 做為 CGI vs. FastCGI 的評比工具。
ApacheBench 可以針對某個特定的 URL 模擬出連續的聯機請求,同時還可以模擬出同時間點數個相同的聯機請求,因此利用 ApacheBench 可幫助我們在網站開發期間模擬實際上線可能的情況,利用模擬出來的資料做為調整伺服器設定或程式的依據。 ApacheBench 的用法如下:
Usage: /usr/local/apache/bin/ab [options] [http://]hostname[:port]/path
Options are:
-n requests Number of requests to perform
-c concurrency Number of multiple requests to make
-t timelimit Seconds to max. wait for responses
-p postfile File containg data to POST
-T content-type Content-type header for POSTing
-v verbosity How much troubleshooting info to print
-w Print out results in HTML tables
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-V Print version number and exit
-k Use HTTP KeepAlive feature
-h Display usage information (this message)
假設我們要對 echo.fcg 做測試,模擬 1000 次的聯機請求,而且同一時間有 20 個並行的 (concurrent) 聯機請求的情況,只要在命令列模式下執行
$ ab -n 1000 -c 20 http://localhost/fcgi-bin/echo.fcg
稍等一會,ApacheBench 會把結果秀出來,
This is ApacheBench, Version 1.3
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd,
http://www.zeustech.net/
Copyright (c) 1998-1999 The Apache Group, http://www.apache.org/
Benchmarking localhost (be patient)... Server Software: Apache/1.3.6
Server Hostname: localhost
Server Port: 80
Document Path: /fcgi-bin/echo.fcg
Document Length: 995 bytes
Concurrency Level: 20
Time taken for tests: 6.859 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 1142000 bytes
HTML transferred: 995000 bytes
Requests per second: 145.79
Transfer rate: 166.50 kb/s received
Connnection Times (ms)
min avg max
Connect: 0 4 61
Processing: 62 128 771
Total: 62 132 832
以上結果指出,在同時間 20 個聯機請求 (Concurrency Level) 的情況下,完成 1000 次的聯機請求,共花了 6.859 秒 (Time taken for tests),因此這個程式每秒平均可以處理 (Requests per second) 145.79 個聯機請求。
在接下來的評比測試中,我們就以每秒可以處理的聯機請求數目來做為效能評比的依據。
4.2 CGI vs. FastCGI
前面提過利用 fcgi_stdio.h 函式庫編譯出來的 FastCGI 程式也相容於 CGI 模式,因此我們只要把 fcgi-devkit-2.1 套件附的範例程式 echo.fcg 複製到 /cgi-bin 目錄下,並且把檔名改成 echo.cgi,這支範例程式就可分別以 CGI 模式和 FastCGI 模式來執行,並且做比較。
首先分別對 CGI模式執行的 http://localhost/cgi-bin/echo.cgi 以及 FastCGI 模式的
http://localhost/fcgi-bin/echo.fcg 連續送出 10, 100, 1000, 10000 次的聯機請求,得到的平均每秒可處理的請求 (Requests per second) 結果為:
聯機數目 10 100 1000 10000
CGI 52.63 53.08 52.24 51.49
FastCGI (static mode) 204.08 224.22 146.78 207.14
接下來再分別以 Concurrency Level 為 10, 50, 100 的情況下做測試,得到 Requests per second 結果為:
Concurrency 聯機數目 10 100 1000 10000
10 CGI 38.31 46.55 53.61 55.09
10 FastCGI 185.19 208.33 162.63 177.14
50 CGI 27.25 33.16 50.72 53.99
50 FastCGI 92.59 176.37 196.58 196.88
100 CGI 17.92 24.84 48.14 52.84
100 FastCGI 86.21 187.27 195.54 193.17
由上述資料看來,對同一支程式 (echo.c) 而言,使用 FastCGI 模式來執行,速度提升了 3-4 倍。可見得 FastCGI 對網站程式的效能提升上具有相當大的助益,尤其當使用量很大時,其效益更加明顯。
以上的測試條件並不是十分嚴謹,主要在讓你瞭解使用 FastCGI 之後對於效能及速度上一個概括的比較,也提供一個可供網站開發者可以評量的依據。
實際上,對一個初始化動作複雜,例如要先和資料庫建立聯機,或是配置記憶體,做變數初始化的程式來說,使用 FastCGI 可以比原先 CGI 在效能上增加更多,速度更快。而對一個有資料庫可聯機數目限制的系統來說,使用 FastCGI 就好象一個 Application Server 一樣,不用擔心 CGI 一次 fork 太多,超過聯機數目上限 (FastCGI 可以設定一次跑幾支)。
4.3 找出 Memory Leak
善用 ApacheBench 這個工具,還可以幫助網站程式發展人員找出在 FastCGI 程式中隱藏的 Memory Leak 臭蟲。
每一支 FastCGI 程式在處理完一個聯機請求後的狀況都應該相同,我們利用 ApacheBench 對欲測試的程式送出上百次或上千次的聯機請求以模擬實際上線的狀況,如果發現程式佔用的記憶體愈來愈多而且不會減少的話,表示程式有 Memory Leak 的問題。
雖然對 FastCGI 程式而言,當它一直不斷吃掉系統資源到資料耗盡後會自動退出 (core dump 或是 exit) ,釋放所佔用的資源然後再重新啟用,但這將會影響系統其它功能的正常運作,所以撰寫 FastCGI 程式一定特別小心。
----------------------------------------------------------------------------
5. 參考
如果你想更進一步瞭解 FastCGI ,在 FastCGI 的首頁上可以找到相關的所有資料,包含 FastCGI 的發展規格,軟體下載,說明檔案等。FastCGI 首頁的網址是
http://www.fastcgi.com/
FastCGI 也有一個郵件討論區 (mailing list),你可以寄 email 至
fastcgi-developers-request@idle.com
在郵件的主旨 (Subject) 處填上 subscribe 即可。但是在發問之前,請先把說明檔案或安裝檔案看清楚,或是到郵件討論區的檔案 (mail archive) 中先找找問題是否已經有人問過而且有人回答過了,以免浪費重複的網路資源。如果你發現了 mod_fastcgi 模組或 fcgi-devkit 發展套件的錯誤或嚴重的問題,FastCGI 的發展小組會很熱心的幫你想辦法解決。
FastCGI 還有許多不同的用法,以及特別的應用,就有待您親自去探索了。希望本文能幫助有心使用 FastCGI 的朋友可以順利地安裝 FastCGI 相關模組和程式,讓你的網站都能全速前進。
相關文章
- 實戰Nginx與PHP(FastCGI)的安裝、配置與最佳化NginxPHPAST
- 寫了一個 FastCGI 實現AST
- fastcgi配置AST
- CGI與FastCGIAST
- FastCGI規範AST
- CGI和FastCGIAST
- apache-淺析apache優化的幾點建議Apache優化
- Nginx配置fastcgi cacheNginxAST
- Nginx 配置 fastcgi cacheNginxAST
- FastCGI協議分析AST協議
- nginx結合fastcgiNginxAST
- fastCGI研究記錄AST
- PHP FastCGI RCE VulPHPAST
- Nginx和fastcgi分離的實現以及注意問題NginxAST
- cgi,fastcgi,php-fpmASTPHP
- 深入理解 FastCGI 協議以及在 PHP 中的實現AST協議PHP
- PHP fastcgi_finish_request 方法PHPAST
- CGI / FASTCGI已停止工作 彩蛋?AST
- FastCGI 程式管理器(FPM)-配置AST
- fastcgi協議分析與例項AST協議
- 淺談PHP fastcgi和php-fpmPHPAST
- Nginx、fastCGI、php-fpm關係梳理NginxASTPHP
- Nginx的ngx_http_fastcgi_module模組NginxHTTPAST
- Ubuntu下的apache2 + fastcgi配置UbuntuApacheAST
- LNMP+FastCGI平臺搭建指令碼LNMPAST指令碼
- 解釋nginx.conf.default 中關於fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;NginxAST
- CGI,FastCGI,PHP-FPM,PHP-CLI,modPHPASTPHP
- 檢視nginx傳輸FASTCGI格式資料NginxAST
- FastCGI sent in stderr: "Primary script unknown" 報錯AST
- PHP之FastCGI與mod_php詳解PHPAST
- Linux上配置Nginx+PHP5(FastCGI)LinuxNginxPHPAST
- IIS下PHP的ISAPI和FastCGI比較PHPAPIAST
- Apache+Fastcgi模組+APC經典配置薦ApacheAST
- 方案一、Nginx+mono+ fastcgi-mono-serverNginxMonoASTServer
- Windows下IIS以FastCGI模式執行PHPWindowsAST模式PHP
- Windows下Apache以FastCGI模式執行PHPWindowsApacheAST模式PHP
- PHP 之 FastCGI 與 mod_php 詳解PHPAST
- 一文搞懂 CGI, FastCGI, WSGI, uWSGI, uwsgiAST