魅惑磁戟 發表於 2008-9-8 19:06:19

APACHE性能方面的提示

<p><span>Apache2.0是一个多用途的web服务器,其设计在灵活性、可移植性和性能中求得平衡。虽然没有在设计上刻意追求性能指标,但是Apache2.0仍然在许多现实环境中拥有很高的性能。<br /><br />相比于Apache 1.3 ,2.0版本作了大量的优化来提升处理能力和可伸缩性,而且大多数的改进在默认状态下就可以生效。但是,在编译时和运行时,都有许多可以显著提高性能的选择。本文阐述在<a class="UBBWordLink" href="#" target="_blank">安装</a>Apache2.0时,<a class="UBBWordLink" href="#" target="_blank">服务</a>器管理员可以改善性能的各种<a class="UBBWordLink" href="#" target="_blank">方法</a>。其中,部分配置选择可以使httpd更好地利用硬件和<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>的兼容性,其他则是以功能换取速度。<br />top<br />硬件和<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a><br /><br />影响web服务器性能的最大的因素是内存。一个web服务器应该从不使用交换机制,因为交换产生的滞后使用户总感觉&quot;不够快&quot;,所以用户就可能去按&quot;停止&quot;和&quot;刷新&quot;,从而带来更大的负载。你可以,也应该,控制MaxClients的<a class="UBBWordLink" href="#" target="_blank">设置</a>,以避免<a class="UBBWordLink" href="#" target="_blank">服务</a>器产生太多的子进程而发生交换。这个过程很简单:通过top命令计算出每个Apache进程平均消耗的内存,然后再为其它进程留出足够多的内存。<br /><br />其他因素就很普通了,装一个足够快的CPU,一个足够快的网卡,几个足够快的硬盘,这里说的&quot;足够快&quot;是指能满足实际应用的需求。<br /><br /><a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>是很值得关注的又一个因素,已经被证实的很有用的经验有:<br /><br />&nbsp; *<br /><br />&nbsp; &nbsp; 选择能够得到的最新最稳定的版本并打好补丁。近年来,许多<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>厂商都提供了可以显著改善性能的TCP协议栈和线程库。<br />&nbsp; *<br /><br />&nbsp; &nbsp; 如果你的<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>支持sendfile()<a class="UBBWordLink" href="#" target="_blank">系统</a>调用,则务必<a class="UBBWordLink" href="#" target="_blank">安装</a>带有此功能的版本或补丁(对Linux来说,就是使用Linux2.4或更高版本,对Solaris8的早期版本,则需要<a class="UBBWordLink" href="#" target="_blank">安装</a>补丁)。在支持sendfile的<a class="UBBWordLink" href="#" target="_blank">系统</a>中,Apache2可以更快地发送静态内容而且占用较少的CPU时间。<br /><br />top<br />运行时的配置<br />相关模块 &nbsp; 相关指令<br /><br />&nbsp; * mod_dir<br />&nbsp; * mpm_common<br />&nbsp; * mod_status<br /><br />&nbsp;<br /><br />&nbsp; * AllowOverride<br />&nbsp; * DirectoryIndex<br />&nbsp; * HostnameLookups<br />&nbsp; * EnableMMAP<br />&nbsp; * EnableSendfile<br />&nbsp; * KeepAliveTimeout<br />&nbsp; * MaxSpareServers<br />&nbsp; * MinSpareServers<br />&nbsp; * Options<br />&nbsp; * StartServers<br /><br />HostnameLookups 和其他DNS考虑<br /><br />在Apache1.3以前的版本中,HostnameLookups默认被设为 On 。它会带来延迟,因为对每一个请求都需要作一次DNS查询。在Apache1.3中,它被默认地<a class="UBBWordLink" href="#" target="_blank">设置</a>为 Off 。如果需要日志文件提供主机名信息以生成分析报告,则可以使用日志后处理程序logresolve ,以完成DNS查询,而客户端无须等待。<br /><br />推荐你最好是在其他机器上,而不是在web服务器上执行后处理和其他日志统计<a class="UBBWordLink" href="#" target="_blank">操作</a>,以免影响<a class="UBBWordLink" href="#" target="_blank">服务</a>器的性能。<br /><br />如果你使用了任何&quot;Allow from domain&quot;或&quot;Deny from domain&quot;指令(也就是domain使用的是主机名而不是IP地址),则代价是要进行两次DNS查询(一次正向和一次反向,以确认没有作假)。所以,为了得到最高的性能,应该避免使用这些指令(不用域名而用IP地址也是可以的)。<br /><br />注意,可以把这些指令包含在&lt;Location /server-status&gt;段中使之局部化。在这种情况下,只有对这个区域的请求才会发生DNS查询。下例禁止除了.html和.cgi以外的所有DNS查询:<br /><br />HostnameLookups off<br />&lt;Files ~ &quot;\.(html|cgi)$&quot;&gt;<br />HostnameLookups on<br />&lt;/Files&gt;<br /><br />如果在某些CGI中偶尔需要DNS名称,则可以调用gethostbyname来<a class="UBBWordLink" href="#" target="_blank">解决</a>。<br />FollowSymLinks 和 SymLinksIfOwnerMatch<br /><br />如果网站空间中没有使用 Options FollowSymLinks ,或使用了 Options SymLinksIfOwnerMatch ,Apache就必须执行额外的<a class="UBBWordLink" href="#" target="_blank">系统</a>调用以验证符号连接。文件名的每一个组成部分都需要一个额外的调用。例如,如果<a class="UBBWordLink" href="#" target="_blank">设置</a>了:<br /><br />DocumentRoot /www/htdocs<br />&lt;Directory /&gt;<br />Options SymLinksIfOwnerMatch<br />&lt;/Directory&gt;<br /><br />在请求&quot;/index.html&quot;时,Apache将对&quot;/www&quot;、&quot;/www/htdocs&quot;、&quot;/www/htdocs/index.html&quot;执行lstat()调用。而且lstat()的执行结果不被缓存,因此对每一个请求都要执行一次。如果确实需要验证符号连接的安全性,则可以这样:<br /><br />DocumentRoot /www/htdocs<br />&lt;Directory /&gt;<br />Options FollowSymLinks<br />&lt;/Directory&gt;<br /><br />&lt;Directory /www/htdocs&gt;<br />Options -FollowSymLinks +SymLinksIfOwnerMatch<br />&lt;/Directory&gt;<br /><br />这样,至少可以避免对DocumentRoot路径的多余的验证。注意,如果Alias或RewriteRule中含有DocumentRoot以外的路径,那么同样需要增加这样的段。为了得到最佳性能,应当放弃对符号连接的保护,在所有地方都<a class="UBBWordLink" href="#" target="_blank">设置</a>FollowSymLinks ,并放弃使用SymLinksIfOwnerMatch 。<br />AllowOverride<br /><br />如果网站空间允许覆盖(通常是用.htaccess文件),则Apache会试图对文件名的每一个组成部分都打开.htaccess ,例如:<br /><br />DocumentRoot /www/htdocs<br />&lt;Directory /&gt;<br />AllowOverride all<br />&lt;/Directory&gt;<br /><br />如果请求&quot;/index.html&quot;,则Apache会试图打开&quot;/.htaccess&quot;、&quot;/www/.htaccess&quot;、&quot;/www/htdocs/.htaccess&quot;。其<a class="UBBWordLink" href="#" target="_blank">解决</a><a class="UBBWordLink" href="#" target="_blank">方法</a>和前面所述的 Options FollowSymLinks 类似。为了得到最佳性能,应当对文件<a class="UBBWordLink" href="#" target="_blank">系统</a>中所有的地方都使用 AllowOverride None 。<br />内容协商<br /><br />实践中,内容协商的好处大于性能的损失,如果你很在意那一点点的性能损失,则可以禁止使用内容协商。但是仍然有个<a class="UBBWordLink" href="#" target="_blank">方法</a>可以提高<a class="UBBWordLink" href="#" target="_blank">服务</a>器的速度,就是不要使用通配符,如:<br /><br />DirectoryIndex index<br /><br />而使用完整的列表,如:<br /><br />DirectoryIndex index.cgi index.pl index.shtml index.html<br /><br />其中最常用的应该放在前面。<br /><br />还有,建立一个明确的type-map文件在性能上优于使用&quot;Options MultiViews&quot;,因为所有需要的信息都在一个单独的文件中,而无须搜索目录。请参考内容协商文档以获得更详细的协商<a class="UBBWordLink" href="#" target="_blank">方法</a>和创建type-map文件的指导。<br />内存映射<br /><br />在Apache2.0需要搜索被发送文件的内容时,比如处理<a class="UBBWordLink" href="#" target="_blank">服务</a>器端包含时,如果<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>支持某种形式的mmap() ,则会对此文件执行内存映射。<br /><br />在某些平台上,内存映射可以提高性能,但是在某些情况下,内存映射会降低性能甚至影响到httpd的稳定性:<br /><br />&nbsp; *<br /><br />&nbsp; &nbsp; 在某些<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>中,如果增加了CPU,mmap还不如read()迅速。比如,在多处理器的Solaris服务器上,关闭了mmap ,Apache2.0传送<a class="UBBWordLink" href="#" target="_blank">服务</a>端解析文件有时候反而更快。<br />&nbsp; *<br /><br />&nbsp; &nbsp; 如果你对作为NFS装载的文件<a class="UBBWordLink" href="#" target="_blank">系统</a>中的一个文件进行了内存映射,而另一个NFS客户端的进程删除或者截断了这个文件,那么你的进程在下一次访问已经被映射的文件内容时,会产生一个总线错误。<br /><br />如果有上述情况发生,则应该使用 EnableMMAP off 关闭对发送文件的内存映射。注意:此指令可以被针对目录的<a class="UBBWordLink" href="#" target="_blank">设置</a>覆盖。<br />Sendfile<br /><br />在Apache2.0能够忽略将要被发送的文件的内容的时候(比如发送静态内容),如果<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>支持sendfile() ,则Apache将使用内核提供的sendfile()来发送文件。<br /><br />在大多数平台上,使用sendfile可以通过免除分离的读和写<a class="UBBWordLink" href="#" target="_blank">操作</a>来提升性能。然而在某些情况下,使用sendfile会危害到httpd的稳定性<br /><br />&nbsp; *<br /><br />&nbsp; &nbsp; 一些平台可能会有Apache编译<a class="UBBWordLink" href="#" target="_blank">系统</a>检测不到的有缺陷的sendfile支持,特别是将在其他平台上使用交叉编译得到的二进制文件运行于当前对sendfile支持有缺陷的平台时。<br />&nbsp; *<br /><br />&nbsp; &nbsp; 对于一个挂载了NFS文件<a class="UBBWordLink" href="#" target="_blank">系统</a>的内核,它可能无法可靠的通过自己的cache服务于<a class="UBBWordLink" href="#" target="_blank">网络</a>文件。<br /><br />如果出现以上情况,你应当使用&quot;EnableSendfile off&quot;来禁用sendfile 。注意,这个指令可以被针对目录的<a class="UBBWordLink" href="#" target="_blank">设置</a>覆盖。<br />进程的建立<br /><br />在Apache1.3以前,MinSpareServers, MaxSpareServers, StartServers的<a class="UBBWordLink" href="#" target="_blank">设置</a>对性能都有很大的影响。尤其是为了应对负载而建立足够的子进程时,Apache需要有一个&quot;渐进&quot;的过程。在最初建立StartServers数量的子进程后,为了满足MinSpareServers设置的需要,每一秒钟只能建立一个子进程。所以,对一个需要同时处理100个客户端的<a class="UBBWordLink" href="#" target="_blank">服务</a>器,如果StartServers使用默认的<a class="UBBWordLink" href="#" target="_blank">设置</a>5,则为了应对负载而建立足够多的子进程需要95秒。在实际应用中,如果不频繁重新启动<a class="UBBWordLink" href="#" target="_blank">服务</a>器,这样还可以,但是如果仅仅为了提供10分钟的<a class="UBBWordLink" href="#" target="_blank">服务</a>,这样就很糟糕了。<br /><br />&quot; 一秒钟一个&quot;的规定是为了避免在创建子进程过程中<a class="UBBWordLink" href="#" target="_blank">服务</a>器对请求的响应停顿,但是它对<a class="UBBWordLink" href="#" target="_blank">服务</a>器性能的影响太大了,必须予以改变。在Apache1.3中,这个 &quot;一秒钟一个&quot;的规定变得宽松了,创建一个进程,等待一秒钟,继续创建第二个,再等待一秒钟,继而创建四个,如此按指数级增加创建的进程数,最多达到每秒 32个,直到满足MinSpareServers设置的值为止。<br /><br />从多数反映看来,似乎没有必要调整MinSpareServers, MaxSpareServers, StartServers 。如果每秒钟创建的进程数超过4个,则会在ErrorLog中产生一条消息,如果产生大量此消息,则可以考虑修改这些<a class="UBBWordLink" href="#" target="_blank">设置</a>。可以使用mod_status的输出作为参考。<br /><br />与进程创建相关的是由MaxRequestsPerChild引发的进程的销毁。其默认值是&quot;0&quot;,意味着每个进程所处理的请求数是不受限制的。如果此值<a class="UBBWordLink" href="#" target="_blank">设置</a>得很小,比如30,则可能需要大幅增加。在SunOS或者Solaris的早期版本上,其最大值为10000以免内存泄漏。<br /><br />如果启用了持久链接,子进程将保持忙碌状态以等待被打开连接上的新请求。为了最小化其负面影响,KeepAliveTimeout的默认值被<a class="UBBWordLink" href="#" target="_blank">设置</a>为5秒,以谋求<a class="UBBWordLink" href="#" target="_blank">网络</a>带宽和<a class="UBBWordLink" href="#" target="_blank">服务</a>器资源之间的平衡。在任何情况下此值都不应当大于60秒,参见most of the benefits are lost。<br />top<br />编译时的配置<br />选择一个MPM<br /><br />Apache 2.x 支持加入式并行处理模块,称为多路处理模块(MPM)。在编译Apache时你必须选择也只能选择一个MPM,这里有几个针对非UNIX系统的MPM:beos, mpm_netware, mpmt_os2, mpm_winnt。对类UNIX系统,有几个不同的MPM可供选择,他们都会影响到httpd的速度和可伸缩性:<br /><br />&nbsp; * workerMPM使用多个子进程,每个子进程中又有多个线程。每个线程处理一个请求。该MPM通常对高流量的<a class="UBBWordLink" href="#" target="_blank">服务</a>器是一个不错的选择。因为它比preforkMPM需要更少的内存且更具有伸缩性。<br />&nbsp; * preforkMPM使用多个子进程,但每个子进程并不包含多线程。每个进程只处理一个链接。在许多<a class="UBBWordLink" href="#" target="_blank">系统</a>上它的速度和workerMPM一样快,但是需要更多的内存。这种无线程的设计在某些情况下优于workerMPM:它可以应用于不具备线程安全的第三方模块(比如php3/4/5),且在不支持线程调试的平台上易于调试,而且还具有比workerMPM更高的稳定性。<br /><br />关于MPM的更多内容,请参考其文档。<br />模块<br /><br />既然内存用量是影响性能的重要因素,你就应当尽量去除你不需要的模块。如果你将模块编译成DSO ,取消不必要的模块就是一件非常简单的事情:注释掉LoadModule指令中不需要的模块。<br /><br />如果你已经将模块静态链接进Apache二进制核心,你就必须重新编译Apache并去掉你不想要的模块。<br /><br />增减模块牵涉到的一个问题是:究竟需要哪些模块、不需要哪些模块?这取决于<a class="UBBWordLink" href="#" target="_blank">服务</a>器的具体情况。一般说来,至少要包含下列模块:mod_mime, mod_dir, mod_log_config 。你也可以不要mod_log_config ,但是一般不推荐这样做。<br />原子<a class="UBBWordLink" href="#" target="_blank">操作</a><br /><br />一些模块,比如mod_cache和worker使用APR(Apache可移植运行时)的原子API。这些API提供了能够用于轻量级线程同步的原子<a class="UBBWordLink" href="#" target="_blank">操作</a>。<br /><br />默认情况下,APR在每个目标OS/CPU上使用其最有效的特性执行这些<a class="UBBWordLink" href="#" target="_blank">操作</a>。比如许多现代CPU的指令集中有一个原子的比较交换(compare-and -swap, CAS)<a class="UBBWordLink" href="#" target="_blank">操作</a>指令。在一些老式平台上,APR默认使用一种缓慢的、基于互斥执行的原子API以保持对没有CAS指令的老式CPU的兼容。如果你只打算在新式的CPU上运行Apache,你可以在编译时使用 --enable-nonportable-atomics 选项:<br /><br />./buildconf<br />./configure --with-mpm=worker --enable-nonportable-atomics=yes<br /><br />--enable-nonportable-atomics 选项只和下列平台相关:<br /><br />&nbsp; * SPARC上的Solaris<br />&nbsp; &nbsp; 默认情况下,APR使用基于互斥执行的原子<a class="UBBWordLink" href="#" target="_blank">操作</a>。如果你使用 --enable-nonportable-atomics 选项,APR将使用SPARC v8plus操作码来加快基于硬件的CAS操作。注意,这仅对UltraSPARC CPU有效。<br />&nbsp; * x86上的Linux<br />&nbsp; &nbsp; 默认情况下,APR在Linux上使用基于互斥执行的原子<a class="UBBWordLink" href="#" target="_blank">操作</a>。如果你使用 --enable-nonportable-atomics 选项,APR将使用486操作码来加快基于硬件的CAS操作。注意,这仅对486以上的CPU有效。<br /><br />mod_status 和 &quot;ExtendedStatus On&quot;<br /><br />如果Apache在编译时包含了mod_status ,而且在运行时<a class="UBBWordLink" href="#" target="_blank">设置</a>了&quot;ExtendedStatus On&quot;,那么Apache会对每个请求调用两次gettimeofday()(或者根据<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>的不同,调用times())以及(1.3版之前)几个额外的time()调用,使状态记录带有时间标志。为了得到最佳性能,可以<a class="UBBWordLink" href="#" target="_blank">设置</a>&quot;ExtendedStatus off&quot;(这也是默认值)。<br />多socket情况下的串行accept<br />警告<br /><br />这部分内容尚未完全根据Apache2.0中的变化进行更新 。一些信息依然有效,使用中请注意。<br /><br />这里要说的是 Unix socket API 的一个缺点。假设web服务器使用了多个Listen语句****多个端口或者多个地址,Apache会使用select()以检测每个socket是否就绪。select()会表明一个socket有零或至少一个连接正等候处理。由于Apache的模型是多子进程的,所有空闲进程会同时检测新的连接。一个很天真的实现<a class="UBBWordLink" href="#" target="_blank">方法</a>是这样的(这些例子并不是源代码,只是为了说明问题而已):<br /><br />for (;;) {<br />for (;;) {<br />fd_set accept_fds;<br /><br />FD_ZERO (&amp;accept_fds);<br />for (i = first_socket; i &lt;= last_socket; ++i) {<br />FD_SET (i, &amp;accept_fds);<br />}<br />rc = select (last_socket+1, &amp;accept_fds, NULL, NULL, NULL);<br />if (rc &lt; 1) continue;<br />new_connection = -1;<br />for (i = first_socket; i &lt;= last_socket; ++i) {<br />if (FD_ISSET (i, &amp;accept_fds)) {<br />new_connection = accept (i, NULL, NULL);<br />if (new_connection != -1) break;<br />}<br />}<br />if (new_connection != -1) break;<br />}<br />process the new_connection;<br />}<br /><br />这种天真的实现<a class="UBBWordLink" href="#" target="_blank">方法</a>有一个严重的&quot;饥饿&quot;问题。如果多个子进程同时执行这个循环,则在多个请求之间,进程会被阻塞在select ,随即进入循环并试图accept此连接,但是只有一个进程可以成功执行(假设还有一个连接就绪),而其余的则会被阻塞在accept 。这样,只有那一个socket可以处理请求,而其他都被锁住了,直到有足够多的请求将它们唤醒。此&quot;饥饿&quot;问题在PR#467中有专门的讲述。目前至少有两种<a class="UBBWordLink" href="#" target="_blank">解决</a>方案。<br /><br />一种方案是使用非阻塞型socket ,不阻塞子进程并允许它们立即继续执行。但是这样会浪费CPU时间。设想一下,select有10个子进程,当一个请求到达的时候,其中9个被唤醒,并试图accept此连接,继而进入select循环,无所事事,并且其间没有一个子进程能够响应出现在其他socket上的请求,直到退出select循环。总之,这个方案效率并不怎么高,除非你有很多的CPU,而且开了很多子进程。<br /><br />另一种也是Apache所使用的方案是,使内层循环的入口串行化,形如(不同之处以高亮显示):<br /><br />for (;;) {<br />accept_mutex_on ();<br />for (;;) {<br />fd_set accept_fds;<br /><br />FD_ZERO (&amp;accept_fds);<br />for (i = first_socket; i &lt;= last_socket; ++i) {<br />FD_SET (i, &amp;accept_fds);<br />}<br />rc = select (last_socket+1, &amp;accept_fds, NULL, NULL, NULL);<br />if (rc &lt; 1) continue;<br />new_connection = -1;<br />for (i = first_socket; i &lt;= last_socket; ++i) {<br />if (FD_ISSET (i, &amp;accept_fds)) {<br />new_connection = accept (i, NULL, NULL);<br />if (new_connection != -1) break;<br />}<br />}<br />if (new_connection != -1) break;<br />}<br />accept_mutex_off ();<br />process the new_connection;<br />}<br /><br />函数accept_mutex_on和accept_mutex_off实现了一个互斥信号灯,在任何时刻只被为一个子进程所拥有。实现互斥的<a class="UBBWordLink" href="#" target="_blank">方法</a>有多种,其定义位于src/conf.h(1.3以前的版本)或src/include/ap_config.h(1.3或以后的版本)中。在一些根本没有锁定机制的体系中,使用多个Listen指令就是不安全的。<br /><br />AcceptMutex指令被用来改变在运行时使用的互斥方案。<br /><br />AcceptMutex flock<br /><br />&nbsp; 这种<a class="UBBWordLink" href="#" target="_blank">方法</a>调用<a class="UBBWordLink" href="#" target="_blank">系统</a>函数flock()来锁定一个加锁文件(其位置取决于LockFile指令)。<br />AcceptMutex fcntl<br /><br />&nbsp; 这种<a class="UBBWordLink" href="#" target="_blank">方法</a>调用<a class="UBBWordLink" href="#" target="_blank">系统</a>函数fcntl()来锁定一个加锁文件(其位置取决于LockFile指令)。<br />AcceptMutex sysvsem<br /><br />&nbsp; (1.3及更新版本)这种方案使用SysV风格的信号灯以实现互斥。不幸的是,SysV风格的信号灯有一些副作用,其一是,Apache有可能不能在结束以前释放这种信号灯(见ipcs()的man page),另外,这种信号灯API给与<a class="UBBWordLink" href="#" target="_blank">网络</a><a class="UBBWordLink" href="#" target="_blank">服务</a>器有相同uid的CGI提供了拒绝<a class="UBBWordLink" href="#" target="_blank">服务</a>攻击的机会(所有CGI,除非用了类似suexec或cgiwrapper)。鉴于此,在多数体系中都不用这种<a class="UBBWordLink" href="#" target="_blank">方法</a>,除了IRIX(因为前两种<a class="UBBWordLink" href="#" target="_blank">方法</a>在IRIX中代价太高)。<br />AcceptMutex pthread<br /><br />&nbsp; (1.3 及更新版本)这种<a class="UBBWordLink" href="#" target="_blank">方法</a>使用了POSIX互斥,按理应该可以用于所有完整实现了POSIX线程规范的体系中,但是似乎只能用在Solaris2.5及更新版本中,甚至只能在某种配置下才正常运作。如果遇到这种情况,则应该提防<a class="UBBWordLink" href="#" target="_blank">服务</a>器的挂起和失去响应。只提供静态内容的<a class="UBBWordLink" href="#" target="_blank">服务</a>器可能不受影响。<br />AcceptMutex posixsem<br /><br />&nbsp; (2.0及更新版本)这种<a class="UBBWordLink" href="#" target="_blank">方法</a>使用了POSIX信号灯。如果一个运行中的线程占有了互斥segfault ,则信号灯的所有者将不会被恢复,从而导致<a class="UBBWordLink" href="#" target="_blank">服务</a>器的挂起和失去响应。<br /><br />如果你的<a class="UBBWordLink" href="#" target="_blank">系统</a>提供了上述<a class="UBBWordLink" href="#" target="_blank">方法</a>以外的串行机制,那就可能需要为APR增加代码(或者提交一个补丁给Apache)。<br /><br />还有一种曾经考虑过但从未予以实施的方案是使循环部分地串行化,即只允许一定数量的进程进入循环。这种<a class="UBBWordLink" href="#" target="_blank">方法</a>仅在多个进程可以同时进行的多处理器的<a class="UBBWordLink" href="#" target="_blank">系统</a>中才是有价值的,而且这样的串行<a class="UBBWordLink" href="#" target="_blank">方法</a>并没有占用整个带宽。它也许是将来研究的一个领域,但是由于高度并行的<a class="UBBWordLink" href="#" target="_blank">网络</a><a class="UBBWordLink" href="#" target="_blank">服务</a>器并不符合规范,所以其被优先考虑的程度会比较低。<br /><br />当然,为了得到最佳性能,最后就根本不使用多个Listen语句。但是上述内容还是值得读一读。<br />单socket情况下的串行accept<br /><br />上述对多socket的<a class="UBBWordLink" href="#" target="_blank">服务</a>器进行了一流的讲述,那么对单socket的<a class="UBBWordLink" href="#" target="_blank">服务</a>器又怎样呢?理论上似乎应该没有什么问题,因为所有进程在连接到来的时候可以由accept()阻塞,而不会产生进程&quot;饥饿&quot;的问题,但是在实际应用中,它掩盖了与上述非阻塞方案几乎相同的问题。按大多数TCP栈的实现<a class="UBBWordLink" href="#" target="_blank">方法</a>,在单个连接到来时,内核实际上唤醒了所有阻塞在accept的进程,但只有一个能得到此连接并返回到用户空间,而其余的由于得不到连接而在内核中处于休眠状态。这种休眠状态为代码所掩盖,但的确存在,并产生与多socket中采用非阻塞方案相同的负载尖峰的浪费。<br /><br />同时,我们发现在许多体系结构中,即使在单socket的情况下,实施串行化的效果也不错,因此在几乎所有的情况下,事实上就都这样处理了。在Linux (2.0.30,双Pentium pro 166/128M RAM)下的测试显示,对单socket,串行化比不串行化每秒钟可以处理的请求少了不到3%,但是,不串行化对每一个请求多了额外的100ms的延迟,此延迟可能是因为长距离的<a class="UBBWordLink" href="#" target="_blank">网络</a>线路所致,并且仅发生在LAN中。如果需要改变对单socket的串行化,可以定义SINGLE_LISTEN_UNSERIALIZED_ACCEPT ,使单socket的<a class="UBBWordLink" href="#" target="_blank">服务</a>器彻底放弃串行化。<br />延迟的关闭<br /><br />正如draft-ietf-http-connection-00.txt section 8所述,HTTP服务器为了可靠地实现此协议,需要单独地在每个方向上关闭通讯(重申一下,一个TCP连接是双向的,两个方向之间是单独的)。在这一点上,其他<a class="UBBWordLink" href="#" target="_blank">服务</a>器经常敷衍了事,但从1.2版本开始被Apache正确实现了。<br /><br />但是增加了此功能以后,由于一些Unix版本的短见,随之也出现了许多问题。TCP规范并没有规定FIN_WAIT_2必须有一个超时,但也没有明确禁止。在没有超时的<a class="UBBWordLink" href="#" target="_blank">系统</a>中,Apache1.2经常会陷于FIN_WAIT_2状态中。多数情况下,这个问题可以用供应商提供的TCP/IP补丁予以<a class="UBBWordLink" href="#" target="_blank">解决</a>。而如果供应商不提供补丁(指SunOS4 -- 尽管用户们持有允许自己修补代码的许可证),那么只能关闭此功能。<br /><br />实现的<a class="UBBWordLink" href="#" target="_blank">方法</a>有两种,其一是socket选项SO_LINGER ,但是似乎命中注定,大多数TCP/IP栈都从未予以正确实现。即使在正确实现的栈中(指Linux2.0.31),此<a class="UBBWordLink" href="#" target="_blank">方法</a>也被证明其代价比下一种<a class="UBBWordLink" href="#" target="_blank">方法</a>高昂。<br /><br />Apache对此的实现代码大多位于函数lingering_close(位于http_main.c)中。此函数大致形如:<br /><br />void lingering_close (int s)<br />{<br />char junk_buffer;<br /><br />/* shutdown the sending side */<br />shutdown (s, 1);<br /><br />signal (SIGALRM, lingering_death);<br />alarm (30);<br /><br />for (;;) {<br />select (s for reading, 2 second timeout);<br />if (error) break;<br />if (s is ready for reading) {<br />if (read (s, junk_buffer, sizeof (junk_buffer)) &lt;= 0) {<br />break;<br />}<br />/* just toss away whatever is here */<br />}<br />}<br /><br />close (s);<br />}<br /><br />此代码在连接结束时多了一些开销,但这是可靠实现所必须的。由于HTTP/1.1越来越流行,而且所有连接都是稳定的,此开销将由更多的请求共同分担。如果你要玩火去关闭这个功能,可以定义NO_LINGCLOSE ,但绝不推荐这样做。尤其是,随着HTTP/1.1中管道化稳定连接的启用,lingering_close已经成为绝对必须。而且,管道化连接速度更快,应该考虑予以支持。<br />Scoreboard 文件<br /><br />Apache父进程和子进程通过scoreboard进行通讯。通过共享内存来实现当然是最理想的。在我们曾经实践过或者提供了完整移植的<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>中,都使用共享内存,其余的则使用磁盘文件。磁盘文件不仅速度慢,而且不可靠(功能也少)。仔细阅读你的体系所对应的src/main/conf.h文件,并查找USE_MMAP_SCOREBOARD或USE_SHMGET_SCOREBOARD 。定义其中之一,可以使共享内容的相关代码生效。如果你的<a class="UBBWordLink" href="#" target="_blank">系统</a>提供其他类型的共享内容,则需要修改src/main/http_main.c文件,并把必需的挂钩添加到<a class="UBBWordLink" href="#" target="_blank">服务</a>器中。(也请发送一个补丁给我们)<br />注意:在对Linux的Apache1.2移植版本之前,没有使用内存共享,此失误使Apache的早期版本在Linux中表现很差。<br />DYNAMIC_MODULE_LIMIT<br /><br />如果你不想使用动态加载模块(或者是因为看见了这段话,或者是为了获得最后一点点性能上的提高),可以在编译<a class="UBBWordLink" href="#" target="_blank">服务</a>器时定义 -DDYNAMIC_MODULE_LIMIT=0 ,这样可以节省为支持动态加载模块而分配的内存。<br />top<br />附录:踪迹的详细分析<br /><br />在Solaris8的MPM中,Apache2.0.38使用一个<a class="UBBWordLink" href="#" target="_blank">系统</a>调用以收集踪迹:<br /><br />truss -l -p httpd_child_pid.<br /><br />-l 参数使truss记录每个执行<a class="UBBWordLink" href="#" target="_blank">系统</a>调用的LWP(lightweight process--Solaris核心级线程)的ID。<br /><br />其他<a class="UBBWordLink" href="#" target="_blank">系统</a>可能使用不同的<a class="UBBWordLink" href="#" target="_blank">系统</a>调用追踪工具,诸如strace, ktrace, par ,其输出都是相似的。<br /><br />下例中,一个客户端向httpd请求了一个10KB的静态文件。对非静态或内容协商请求的记录会有很大不同(有时也很难看明白)。<br /><br />/67: &nbsp; accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)<br />/67: &nbsp; accept(3, 0x00200BEC, 0x00200C0C, 1) &nbsp; &nbsp; &nbsp; &nbsp; = 9<br /><br />下例中,****线程是 LWP #67 。<br />注意对accept()串行化支持的匮乏。与这个特殊平台对应的MPM在默认情况下使用非串行的accept ,除了在****多个端口的时候。<br /><br />/65: &nbsp; lwp_park(0x00000000, 0) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/67: &nbsp; lwp_unpark(65, 1) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br /><br />接受了一个连接后,****线程唤醒一个工作线程以处理此请求。下例中,处理请求的那个工作线程是 LWP #65 。<br /><br />/65: &nbsp; getsockname(9, 0x00200BA4, 0x00200BC4, 1) &nbsp; &nbsp; = 0<br /><br />为了实现虚拟主机,Apache需要知道接受连接的本地socket地址。在许多情况下,有可能无须执行此调用(比如没有虚拟主机,或者Listen指令中没有使用通配地址),但是目前并没有对此作优化处理。<br /><br />/65: &nbsp; brk(0x002170E8) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; brk(0x002190E8) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br /><br />此brk()调用是从堆中分配内存的,它在<a class="UBBWordLink" href="#" target="_blank">系统</a>调用记录中并不多见,因为httpd在多数请求处理中使用了自己的内存分配器(apr_pool和apr_bucket_alloc)。下例中,httpd刚刚启动,所以它必须调用malloc()以分配原始内存块用于自己的内存分配器。<br /><br />/65: &nbsp; fcntl(9, F_GETFL, 0x00000000) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 2<br />/65: &nbsp; fstat64(9, 0xFAF7B818) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0<br />/65: &nbsp; fstat64(9, 0xFAF7B818) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0<br />/65: &nbsp; setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0<br />/65: &nbsp; fcntl(9, F_SETFL, 0x00000082) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br /><br />接着,工作线程使客户端连接处于非阻塞模式。setsockopt()和getsockopt()调用是Solaris的libc对socket执行fcntl()所必须的。<br /><br />/65: &nbsp; read(9, &quot; G E T &nbsp; / 1 0 k . h t m&quot;.., 8000) &nbsp; = 97<br /><br />工作线程从客户端读取请求。<br /><br />/65: &nbsp; stat(&quot;/var/httpd/apache/httpd-8999/htdocs/10k.html&quot;, 0xFAF7B978) = 0<br />/65: &nbsp; open(&quot;/var/httpd/apache/httpd-8999/htdocs/10k.html&quot;, O_RDONLY) = 10<br /><br />这里,httpd被配置为&quot;Options FollowSymLinks&quot;和&quot;AllowOverride None&quot;。所以,无须对每个被请求文件路径中的目录执行lstat(),也不需要检查.htaccess文件,它简单地调用stat()以检查此文件是否存在,以及是一个普通的文件还是一个目录。<br /><br />/65: &nbsp; sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C) &nbsp; &nbsp; = 10269<br /><br />此例中,httpd可以通过单个<a class="UBBWordLink" href="#" target="_blank">系统</a>调用sendfilev()发送HTTP响应头和被请求的文件。Sendfile因<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>会有所不同,有些<a class="UBBWordLink" href="#" target="_blank">系统</a>中,在调用sendfile()以前,需要调用write()或writev()以发送响应头。<br /><br />/65: &nbsp; write(4, &quot; 1 2 7 . 0 . 0 . 1 &nbsp; - &quot;.., 78) &nbsp; &nbsp; = 78<br /><br />此write()调用在访问日志中对请求作了记录。注意,其中没有对time()的调用的记录。与Apache1.3不同,Apache2.0使用gettimeofday()以查询时间。在有些<a class="UBBWordLink" href="#" target="_blank">操作</a><a class="UBBWordLink" href="#" target="_blank">系统</a>中,比如Linux和Solaris,gettimeofday有一个优化的版本,其开销比一个普通的<a class="UBBWordLink" href="#" target="_blank">系统</a>调用要小一点。<br /><br />/65: &nbsp; shutdown(9, 1, 1) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; poll(0xFAF7B980, 1, 2000) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 1<br />/65: &nbsp; read(9, 0xFAF7BC20, 512) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; close(9) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br /><br />工作线程对连接作延迟的关闭。<br /><br />/65: &nbsp; close(10) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; = 0<br />/65: &nbsp; lwp_park(0x00000000, 0) &nbsp; &nbsp; &nbsp; (sleeping...)<br /><br />最后,工作线程关闭发送完的文件和块,直到****进程把它指派给另一个连接。<br /><br />/67: &nbsp; accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)<br /><br />其间,****进程可以在把一个连接指派给一个工作进程后立即接受另一个连接(但是如果所有工作进程都处于忙碌状态,则会受MPM中的一些溢出控制逻辑的制约)。虽然在此例中并不明显,在工作线程刚接受了一个连接之后,下一个accept()会(在高负荷的情况下更会)立即并行产生。</span></p>
頁: [1]
查看完整版本: APACHE性能方面的提示