当网站日益走红,联机人数直线上升而心中暗自窃喜之时,突然客服中心涌来大批反应电话:『网站连不上去』、『按下去等好久画面才出来』、『一直出现 Server Too Busy…』...。看来又要把硬件升级了,但是再加更多的内存,更多 CPU、换更贵的机器真的能解决问题吗?有没有比较省钱的方法呢?本文将介绍如何在阿帕契服务器上安装 FastCGI 的模块,如何设定及使用 FastCGI 网站应用程序,让你的网站程序在现有的架构上以全速执行。
----------------------------------------------------------------------------
1. 克服 CGI 的瓶颈
1.1 令人头痛的效率问题
1.2 一些解决之道
1.3 更好的方法 - FastCGI
2. 安装 FastCGI
2.1 在阿帕契服务器上安装 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 衍生出做的服务器) 上也有 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 Web Server) 做为 FastCGI 的执行平台,简述如何在阿帕契服务器使用 FastCGI。
由于阿帕契服务器自由、开放、跨平台的特性,现今许多系统或发行套件 (distribution) 都内含阿帕契服务器,如果你直接用预先编译好的阿帕契程序,请自行找出符合该系统设定规则的安装路径。以下列出一些阿帕契服务器相关的路径设定规则,后面的范例将以阿帕契内定值为主,其它的系统请自行参考:
系统 执行文件路径 设定文件路径
阿帕契内定值 /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 在阿帕契服务器上安装 FastCGI 模块
安装 mod_fastcgi 这个模块,可以让你的阿帕契服务器支持 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 可以辨别 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. 重新激活阿帕契服务器,让新设定生效:
$ /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/htmlrn"
"rn"
"<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: %dn",
++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 的阿帕契模块,以及无聊的程序设计注意事项后,我们来看看一些可以让人振奋精神的数据,效能比较 (bench mark) 总是计算机玩家的最爱 :-)
4.1 评比工具 - ApacheBench
在阿帕契服务器的套件中,有一个叫做 ab (ApacheBench) 的工具。ApacheBench 主要是用来测试阿帕契服务器执行效率用的,我们就以 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 ? 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright ? 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 程序一定特别小心。
FastCGI 也有一个邮件讨论区 (mailing list),你可以寄 email 至
fastcgi-developers-request@idle.com
在邮件的主旨 (Subject) 处填上 subscribe 即可。但是在发问之前,请先把说明文件或安装文件看清楚,或是到邮件讨论区的档案 (mail archive) 中先找找问题是否已经有人问过而且有人回答过了,以免浪费重复的网络资源。如果你发现了 mod_fastcgi 模块或 fcgi-devkit 发展套件的错误或严重的问题,FastCGI 的发展小组会很热心的帮你想办法解决。
FastCGI 还有许多不同的用法,以及特别的应用,就有待您亲自去探索了。希望本文能帮助有心使用 FastCGI 的朋友可以顺利地安装 FastCGI 相关模块和程序,让你的网站都能全速前进。