📄 perf-tuning.html
字号:
<dd>
<p>(1.3及更新版本)这种方案使用SysV风格的信号灯以实现互斥。不幸的是,SysV风格的信号灯有一些副作用,其一是,Apache有可能不能在结束以前释放这种信号灯(见<code>ipcs()</code>的man page),另外,这种信号灯API给与网络服务器有相同uid的CGI提供了拒绝服务攻击的机会(所有CGI,除非用了类似<code class="program"><a href="../programs/suexec.html">suexec</a></code>或<code>cgiwrapper</code>)。鉴于此,在多数体系中都不用这种方法,除了IRIX(因为前两种方法在IRIX中代价太高)。</p>
</dd>
<dt><code>AcceptMutex pthread</code></dt>
<dd>
<p>(1.3及更新版本)这种方法使用了POSIX互斥,按理应该可以用于所有完整实现了POSIX线程规范的体系中,但是似乎只能用在Solaris2.5及更新版本中,甚至只能在某种配置下才正常运作。如果遇到这种情况,则应该提防服务器的挂起和失去响应。只提供静态内容的服务器可能不受影响。</p>
</dd>
<dt><code>AcceptMutex posixsem</code></dt>
<dd>
<p>(2.0及更新版本)这种方法使用了POSIX信号灯。如果一个运行中的线程占有了互斥segfault ,则信号灯的所有者将不会被恢复,从而导致服务器的挂起和失去响应。</p>
</dd>
</dl>
<p>如果你的系统提供了上述方法以外的串行机制,那就可能需要为APR增加代码(或者提交一个补丁给Apache)。</p>
<p>还有一种曾经考虑过但从未予以实施的方案是使循环部分地串行化,即只允许一定数量的进程进入循环。这种方法仅在多个进程可以同时进行的多处理器的系统中才是有价值的,而且这样的串行方法并没有占用整个带宽。它也许是将来研究的一个领域,但是由于高度并行的网络服务器并不符合规范,所以其被优先考虑的程度会比较低。</p>
<p>当然,为了得到最佳性能,最后就根本不使用多个<code class="directive"><a href="../mod/mpm_common.html#listen">Listen</a></code>语句。但是上述内容还是值得读一读。</p>
<h3>单socket情况下的串行accept</h3>
<p>上述对多socket的服务器进行了一流的讲述,那么对单socket的服务器又怎样呢?理论上似乎应该没有什么问题,因为所有进程在连接到来的时候可以由<code>accept()</code>阻塞,而不会产生进程"饥饿"的问题,但是在实际应用中,它掩盖了与上述非阻塞方案几乎相同的问题。按大多数TCP栈的实现方法,在单个连接到来时,内核实际上唤醒了所有阻塞在<code>accept</code>的进程,但只有一个能得到此连接并返回到用户空间,而其余的由于得不到连接而在内核中处于休眠状态。这种休眠状态为代码所掩盖,但的确存在,并产生与多socket中采用非阻塞方案相同的负载尖峰的浪费。</p>
<p>同时,我们发现在许多体系结构中,即使在单socket的情况下,实施串行化的效果也不错,因此在几乎所有的情况下,事实上就都这样处理了。在Linux(2.0.30,双Pentium pro 166/128M RAM)下的测试显示,对单socket,串行化比不串行化每秒钟可以处理的请求少了不到3%,但是,不串行化对每一个请求多了额外的100ms的延迟,此延迟可能是因为长距离的网络线路所致,并且仅发生在LAN中。如果需要改变对单socket的串行化,可以定义<code>SINGLE_LISTEN_UNSERIALIZED_ACCEPT</code> ,使单socket的服务器彻底放弃串行化。</p>
<h3>延迟的关闭</h3>
<p>正如<a href="http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-connection-00.txt">draft-ietf-http-connection-00.txt</a> section 8所述,HTTP服务器为了<strong>可靠</strong>地实现此协议,需要单独地在每个方向上关闭通讯(重申一下,一个TCP连接是双向的,两个方向之间是独立的)。在这一点上,其他服务器经常敷衍了事,但从1.2版本开始被Apache正确实现了。</p>
<p>但是增加了此功能以后,由于一些Unix版本的短见,随之也出现了许多问题。TCP规范并没有规定<code>FIN_WAIT_2</code>必须有一个超时,但也没有明确禁止。在没有超时的系统中,Apache1.2经常会陷于<code>FIN_WAIT_2</code>状态中。多数情况下,这个问题可以用供应商提供的TCP/IP补丁予以解决。而如果供应商不提供补丁(指SunOS4 -- 尽管用户们持有允许自己修补代码的许可证),那么只能关闭此功能。</p>
<p>实现的方法有两种,其一是socket选项<code>SO_LINGER</code> ,但是似乎命中注定,大多数TCP/IP栈都从未予以正确实现。即使在正确实现的栈中(指Linux2.0.31),此方法也被证明其代价比下一种方法高昂。</p>
<p>Apache对此的实现代码大多位于函数<code>lingering_close</code>(位于<code>http_main.c</code>)中。此函数大致形如:</p>
<div class="example"><p><code>
void lingering_close (int s)<br />
{<br />
<span class="indent">
char junk_buffer[2048];<br />
<br />
/* shutdown the sending side */<br />
shutdown (s, 1);<br />
<br />
signal (SIGALRM, lingering_death);<br />
alarm (30);<br />
<br />
for (;;) {<br />
<span class="indent">
select (s for reading, 2 second timeout);<br />
if (error) break;<br />
if (s is ready for reading) {<br />
<span class="indent">
if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {<br />
<span class="indent">
break;<br />
</span>
}<br />
/* just toss away whatever is here */<br />
</span>
}<br />
</span>
}<br />
<br />
close (s);<br />
</span>
}
</code></p></div>
<p>此代码在连接结束时多了一些开销,但这是可靠实现所必须的。由于HTTP/1.1越来越流行,而且所有连接都是稳定的,此开销将由更多的请求共同分担。如果你要玩火去关闭这个功能,可以定义<code>NO_LINGCLOSE</code> ,但绝不推荐这样做。尤其是,随着HTTP/1.1中管道化稳定连接的启用,<code>lingering_close</code>已经成为绝对必须。而且,<a href="http://www.w3.org/Protocols/HTTP/Performance/Pipeline.html">管道化连接速度更快</a>,应该考虑予以支持。</p>
<h3>Scoreboard 文件</h3>
<p>Apache父进程和子进程通过scoreboard进行通讯。通过共享内存来实现当然是最理想的。在我们曾经实践过或者提供了完整移植的操作系统中,都使用共享内存,其余的则使用磁盘文件。磁盘文件不仅速度慢,而且不可靠(功能也少)。仔细阅读你的体系所对应的<code>src/main/conf.h</code>文件,并查找<code>USE_MMAP_SCOREBOARD</code>或<code>USE_SHMGET_SCOREBOARD</code> 。定义其中之一(或者分别类似HAVE_MMAP和HAVE_SHMGET),可以使共享内容的相关代码生效。如果你的系统提供其他类型的共享内容,则需要修改<code>src/main/http_main.c</code>文件,并把必需的挂钩添加到服务器中。(也请发送一个补丁给我们)</p>
<div class="note">注意:在对Linux的Apache1.2移植版本之前,没有使用内存共享,此失误使Apache的早期版本在Linux中表现很差。</div>
<h3>DYNAMIC_MODULE_LIMIT</h3>
<p>如果你不想使用动态加载模块(或者是因为看见了这段话,或者是为了获得最后一点点性能上的提高),可以在编译服务器时定义 <code>-DDYNAMIC_MODULE_LIMIT=0</code> ,这样可以节省为支持动态加载模块而分配的内存。</p>
</div><div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
<div class="section">
<h2><a name="trace" id="trace">附录:踪迹的详细分析</a></h2>
<p>在Solaris8的MPM中,Apache2.0.38使用一个系统调用以收集踪迹:</p>
<div class="example"><p><code>
truss -l -p <var>httpd_child_pid</var>.
</code></p></div>
<p><code>-l</code> 参数使truss记录每个执行系统调用的LWP(lightweight process--Solaris核心级线程)的ID。</p>
<p>其他系统可能使用不同的系统调用追踪工具,诸如<code>strace</code>, <code>ktrace</code>, <code>par</code> ,其输出都是相似的。</p>
<p>下例中,一个客户端向httpd请求了一个10KB的静态文件。对非静态或内容协商请求的记录会有很大不同(有时也很难看明白)。</p>
<div class="example"><pre>/67: accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
/67: accept(3, 0x00200BEC, 0x00200C0C, 1) = 9</pre></div>
<p>下例中,监听线程是 LWP #67 。</p>
<div class="note">注意对<code>accept()</code>串行化支持的匮乏。与这个特殊平台对应的MPM在默认情况下使用非串行的accept ,除了在监听多个端口的时候。</div>
<div class="example"><pre>/65: lwp_park(0x00000000, 0) = 0
/67: lwp_unpark(65, 1) = 0</pre></div>
<p>接受了一个连接后,监听线程唤醒一个工作线程以处理此请求。下例中,处理请求的那个工作线程是 LWP #65 。</p>
<div class="example"><pre>/65: getsockname(9, 0x00200BA4, 0x00200BC4, 1) = 0</pre></div>
<p>为了实现虚拟主机,Apache需要知道接受连接的本地socket地址。在许多情况下,有可能无须执行此调用(比如没有虚拟主机,或者<code class="directive"><a href="../mod/mpm_common.html#listen">Listen</a></code>指令中没有使用通配地址),但是目前并没有对此作优化处理。</p>
<div class="example"><pre>/65: brk(0x002170E8) = 0
/65: brk(0x002190E8) = 0</pre></div>
<p>此<code>brk()</code>调用是从堆中分配内存的,它在系统调用记录中并不多见,因为httpd在多数请求处理中使用了自己的内存分配器(<code>apr_pool</code>和<code>apr_bucket_alloc</code>)。下例中,httpd刚刚启动,所以它必须调用<code>malloc()</code>以分配原始内存块用于自己的内存分配器。</p>
<div class="example"><pre>/65: fcntl(9, F_GETFL, 0x00000000) = 2
/65: fstat64(9, 0xFAF7B818) = 0
/65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B910, 2190656) = 0
/65: fstat64(9, 0xFAF7B818) = 0
/65: getsockopt(9, 65535, 8192, 0xFAF7B918, 0xFAF7B914, 2190656) = 0
/65: setsockopt(9, 65535, 8192, 0xFAF7B918, 4, 2190656) = 0
/65: fcntl(9, F_SETFL, 0x00000082) = 0</pre></div>
<p>接着,工作线程使客户端连接处于非阻塞模式。<code>setsockopt()</code>和<code>getsockopt()</code>调用是Solaris的libc对socket执行<code>fcntl()</code>所必须的。</p>
<div class="example"><pre>/65: read(9, " G E T / 1 0 k . h t m".., 8000) = 97</pre></div>
<p>工作线程从客户端读取请求。</p>
<div class="example"><pre>/65: stat("/var/httpd/apache/httpd-8999/htdocs/10k.html", 0xFAF7B978) = 0
/65: open("/var/httpd/apache/httpd-8999/htdocs/10k.html", O_RDONLY) = 10</pre></div>
<p>这里,httpd被配置为"<code>Options FollowSymLinks</code>"和"<code>AllowOverride None</code>"。所以,无须对每个被请求文件路径中的目录执行<code>lstat()</code>,也不需要检查<code>.htaccess</code>文件,它简单地调用<code>stat()</code>以检查此文件是否存在,以及是一个普通的文件还是一个目录。</p>
<div class="example"><pre>/65: sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C) = 10269</pre></div>
<p>此例中,httpd可以通过单个系统调用<code>sendfilev()</code>发送HTTP响应头和被请求的文件。Sendfile因操作系统会有所不同,有些系统中,在调用<code>sendfile()</code>以前,需要调用<code>write()</code>或<code>writev()</code>以发送响应头。</p>
<div class="example"><pre>/65: write(4, " 1 2 7 . 0 . 0 . 1 - ".., 78) = 78</pre></div>
<p>此<code>write()</code>调用在访问日志中对请求作了记录。注意,其中没有对<code>time()</code>的调用的记录。与Apache1.3不同,Apache2.0使用<code>gettimeofday()</code>以查询时间。在有些操作系统中,比如Linux和Solaris,<code>gettimeofday</code>有一个优化的版本,其开销比一个普通的系统调用要小一点。</p>
<div class="example"><pre>/65: shutdown(9, 1, 1) = 0
/65: poll(0xFAF7B980, 1, 2000) = 1
/65: read(9, 0xFAF7BC20, 512) = 0
/65: close(9) = 0</pre></div>
<p>工作线程对连接作延迟的关闭。</p>
<div class="example"><pre>/65: close(10) = 0
/65: lwp_park(0x00000000, 0) (sleeping...)</pre></div>
<p>最后,工作线程关闭发送完的文件和块,直到监听进程把它指派给另一个连接。</p>
<div class="example"><pre>/67: accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)</pre></div>
<p>其间,监听进程可以在把一个连接指派给一个工作进程后立即接受另一个连接(但是如果所有工作进程都处于忙碌状态,则会受MPM中的一些溢出控制逻辑的制约)。虽然在此例中并不明显,在工作线程刚接受了一个连接之后,下一个<code>accept()</code>会(在高负荷的情况下更会)立即并行产生。</p>
</div></div>
<div id="footer">
<p class="apache">本文允许自由使用、分发、转载,但必须保留译者署名;详见:<a href="../translator_announcement.html#announcement">译者声明</a>。</p>
<p class="menu"><a href="../mod/index.html">模块索引</a> | <a href="../mod/directives.html">指令索引</a> | <a href="../faq/index.html">常见问题</a> | <a href="../glossary.html">词汇表</a> | <a href="../sitemap.html">站点导航</a></p></div>
</body></html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -