进程的建立
在Apache1.3以前,MinSpareServers, MaxSpareServers, StartServers的设置对性能都有很大的影响。尤其是为了应对负载而建立足够的子进程时,Apache需要有一个"渐进"的过程。在最初建立StartServers数量的子进程后,为了满足MinSpareServers设置的需要,每一秒钟只能建立一个子进程。所以,对一个需要同时处理100个客户端的服务器,如果StartServers使用默认的设置5,则为了应对负载而建立足够多的子进程需要95秒。在实际应用中,如果不频繁重新启动服务器,这样还可以,但是如果仅仅为了提供10分钟的服务,这样就很糟糕了。
" 一秒钟一个"的规定是为了避免在创建子进程过程中服务器对请求的响应停顿,但是它对服务器性能的影响太大了,必须予以改变。在Apache1.3中,这个 "一秒钟一个"的规定变得宽松了,创建一个进程,等待一秒钟,继续创建第二个,再等待一秒钟,继而创建四个,如此按指数级增加创建的进程数,最多达到每秒 32个,直到满足MinSpareServers设置的值为止。
从多数反映看来,似乎没有必要调整MinSpareServers, MaxSpareServers, StartServers 。如果每秒钟创建的进程数超过4个,则会在ErrorLog中产生一条消息,如果产生大量此消息,则可以考虑修改这些设置。可以使用mod_status的输出作为参考。
与进程创建相关的是由MaxRequestsPerChild引发的进程的销毁。其默认值是"0",意味着每个进程所处理的请求数是不受限制的。如果此值设置得很小,比如30,则可能需要大幅增加。在SunOS或者Solaris的早期版本上,其最大值为10000以免内存泄漏。
如果启用了持久链接,子进程将保持忙碌状态以等待被打开连接上的新请求。为了最小化其负面影响,KeepAliveTimeout的默认值被设置为5秒,以谋求网络带宽和服务器资源之间的平衡。在任何情况下此值都不应当大于60秒,参见most of the benefits are lost。
编译时的配置
选择一个MPM
Apache 2.x 支持插入式并行处理模块,称为多路处理模块(MPM)。在编译Apache时你必须选择也只能选择一个MPM,这里有几个针对非UNIX系统的MPM:beos, mpm_netware, mpmt_os2, mpm_winnt。对类UNIX系统,有几个不同的MPM可供选择,他们都会影响到httpd的速度和可伸缩性:
* workerMPM使用多个子进程,每个子进程中又有多个线程。每个线程处理一个请求。该MPM通常对高流量的服务器是一个不错的选择。因为它比preforkMPM需要更少的内存且更具有伸缩性。
* preforkMPM使用多个子进程,但每个子进程并不包含多线程。每个进程只处理一个链接。在许多系统上它的速度和workerMPM一样快,但是需要更多的内存。这种无线程的设计在某些情况下优于workerMPM:它可以应用于不具备线程安全的第三方模块(比如php3/4/5),且在不支持线程调试的平台上易于调试,而且还具有比workerMPM更高的稳定性。
关于MPM的更多内容,请参考其文档。
模块
既然内存用量是影响性能的重要因素,你就应当尽量去除你不需要的模块。如果你将模块编译成DSO ,取消不必要的模块就是一件非常简单的事情:注释掉LoadModule指令中不需要的模块。
如果你已经将模块静态链接进Apache二进制核心,你就必须重新编译Apache并去掉你不想要的模块。
增减模块牵涉到的一个问题是:究竟需要哪些模块、不需要哪些模块?这取决于服务器的具体情况。一般说来,至少要包含下列模块:mod_mime, mod_dir, mod_log_config 。你也可以不要mod_log_config ,但是一般不推荐这样做。
原子操作
一些模块,比如mod_cache和worker使用APR(Apache可移植运行时)的原子API。这些API提供了能够用于轻量级线程同步的原子操作。
默认情况下,APR在每个目标OS/CPU上使用其最有效的特性执行这些操作。比如许多现代CPU的指令集中有一个原子的比较交换(compare-and -swap, CAS)操作指令。在一些老式平台上,APR默认使用一种缓慢的、基于互斥执行的原子API以保持对没有CAS指令的老式CPU的兼容。如果你只打算在新式的CPU上运行Apache,你可以在编译时使用 --enable-nonportable-atomics 选项:
./buildconf
./configure --with-mpm=worker --enable-nonportable-atomics=yes
--enable-nonportable-atomics 选项只和下列平台相关:
* SPARC上的Solaris
默认情况下,APR使用基于互斥执行的原子操作。如果你使用 --enable-nonportable-atomics 选项,APR将使用SPARC v8plus操作码来加快基于硬件的CAS操作。注意,这仅对UltraSPARC CPU有效。
* x86上的Linux
默认情况下,APR在Linux上使用基于互斥执行的原子操作。如果你使用 --enable-nonportable-atomics 选项,APR将使用486操作码来加快基于硬件的CAS操作。注意,这仅对486以上的CPU有效。
mod_status 和 "ExtendedStatus On"
如果Apache在编译时包含了mod_status ,而且在运行时设置了"ExtendedStatus On",那么Apache会对每个请求调用两次gettimeofday()(或者根据操作系统的不同,调用times())以及(1.3版之前)几个额外的time()调用,使状态记录带有时间标志。为了得到最佳性能,可以设置"ExtendedStatus off"(这也是默认值)。
多socket情况下的串行accept
警告
这部分内容尚未完全根据Apache2.0中的变化进行更新 。一些信息依然有效,使用中请注意。
这里要说的是 Unix socket API 的一个缺点。假设web服务器使用了多个Listen语句监听多个端口或者多个地址,Apache会使用select()以检测每个socket是否就绪。select()会表明一个socket有零或至少一个连接正等候处理。由于Apache的模型是多子进程的,所有空闲进程会同时检测新的连接。一个很天真的实现方法是这样的(这些例子并不是源代码,只是为了说明问题而已):
for (;;) {
for (;;) {
fd_set accept_fds;
FD_ZERO (&accept_fds);
for (i = first_socket; i