• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • Apache性能优化

    Apache 2.x是通用Web服务器,旨在在灵活性,可移植性和性能之间取得平衡。尽管不是专门为设置基准记录而设计的,但Apache 2.x能够在许多实际情况下实现高性能。

    与Apache 1.3相比,版本2.x包含许多其他优化功能,以提高吞吐量和可伸缩性。默认情况下,其中大多数改进都是启用的。但是,有一些编译时和运行时配置选择会严重影响性能。本文档介绍了服务器管理员可以配置的选项,以调整Apache 2.x安装的性能。这些配置选项中的一些使httpd可以更好地利用硬件和OS的功能,而其他一些则允许管理员以速度为代价来交换功能。

    硬件和操作系统问题

    影响Web服务器性能的最大硬件问题是RAM。Web服务器永远都不必交换,因为交换会增加每个请求的延迟,使之超过用户认为“足够快”的程度。这会导致用户点击停止并重新加载,从而进一步增加了负载。您可以并且应该控制该MaxRequestWorkers设置,以使服务器不会产生太多子代以至于无法开始交换。这样做的过程很简单:通过使用诸如之类的工具查看进程列表,确定平均Apache进程的大小top,然后将其划分为总可用内存,为其他进程留出一些空间。

    除此之外,其他一切都很普通:获得足够快的CPU,足够快的网卡和足够快的磁盘,其中“足够快”是需要通过实验确定的东西。

    操作系统的选择很大程度上取决于本地情况。但是,一些已被证明通常有用的准则是:

    • 运行所选操作系统的最新稳定版本和补丁程序级别。近年来,许多操作系统供应商都对其TCP堆栈和线程库进行了重大的性能改进。
    • 如果您的操作系统支持sendfile(2)系统调用,请确保安装了启用它所需的版本和/或补丁程序。(例如,对于Linux,这意味着使用Linux 2.4或更高版本。对于Solaris 8的早期版本,您可能需要应用补丁。)在可用的系统上,sendfile使Apache 2能够以较低的CPU更快地交付静态内容。利用率。

    运行时配置问题

    相关模块相关指令
    • mod_dir
    • mpm_common
    • mod_status
    • AllowOverride
    • DirectoryIndex
    • HostnameLookups
    • EnableMMAP
    • EnableSendfile
    • KeepAliveTimeout
    • MaxSpareServers
    • MinSpareServers
    • Options
    • StartServers

    主机名查找和其他DNS注意事项

    在Apache 1.3之前,HostnameLookups默认为On。这会增加每个请求的延迟,因为它需要在请求完成之前完成DNS查找。在Apache 1.3中,此设置默认为Off。如果您需要将日志文件中的地址解析为主机名,请使用logresolve Apache随附的程序或可用的众多日志报告软件包之一。

    建议您在生产Web服务器计算机以外的其他计算机上对日志文件进行这种后处理,以免此活动对服务器性能产生不利影响。

    如果您使用任何Allow from domainDeny from domain指令(即,使用主机名或域名,而不是IP地址),则需要支付两次DNS查找费用(反向查询,然后进行正向查询,以确保不进行反向查询)欺骗)。因此,为了获得最佳性能,请尽可能在使用这些指令时使用IP地址而不是名称。

    请注意,可以对指令进行范围调整,例如在一个<Location "/server-status">节内。在这种情况下,仅对符合条件的请求执行DNS查找。这是一个禁用查找的示例,除了.html.cgi文件之外:

    HostnameLookups off
    <Files ~ "\.(html|cgi)$">
      HostnameLookups on
    </Files>
    

    但即使如此,如果您仅在某些CGI中需要DNS名称,则可以考虑在gethostbyname需要它的特定CGI中进行调用。

    关注SymLinks和SymLinksIfOwnerMatch

    无论您在URL空间中的哪个位置没有Options FollowSymLinksOptions SymLinksIfOwnerMatch,Apache都需要发出额外的系统调用来检查符号链接。(每个文件名组件一个额外的调用。)例如,如果您有:

    DocumentRoot "/www/htdocs"
    <Directory "/">
      Options SymLinksIfOwnerMatch
    </Directory>
    

    和请求针对URI制成/index.html,然后Apache将执行lstat(2)/www/www/htdocs/www/htdocs/index.html。这些结果lstats永远不会被缓存,因此它们将在每个单个请求中出现。如果您确实需要符号链接安全检查,则可以执行以下操作:

    DocumentRoot "/www/htdocs"
    <Directory "/">
      Options FollowSymLinks
    </Directory>
    
    <Directory "/www/htdocs">
      Options -FollowSymLinks +SymLinksIfOwnerMatch
    </Directory>
    

    这至少避免了对DocumentRoot路径的额外检查。请注意,您需要添加类似的部分,如果您有任何AliasRewriteRule路径,您的文档根目录之外。为了获得最高性能,并且没有符号链接保护,请在FollowSymLinks各处设置,并且永远不要设置SymLinksIfOwnerMatch

    AllowOverride

    无论您在URL空间中的何处允许覆盖(通常是.htaccess文件),Apache都会尝试.htaccess为每个文件名组件打开。例如,

    DocumentRoot "/www/htdocs"
    <Directory "/">
      AllowOverride all
    </Directory>
    

    并请求URI /index.html。然后Apache会试图打开/.htaccess/www/.htaccess/www/htdocs/.htaccess。解决方案与的先前情况类似Options FollowSymLinks。为了获得最佳性能AllowOverride None,请在文件系统中的任何地方使用。

    谈判

    如果您确实对最后一刻的性能感兴趣,请尽可能避免进行内容协商。在实践中,谈判的好处胜于性能损失。在一种情况下,您可以加快服务器速度。而不是使用通配符,例如:

    DirectoryIndex index
    

    使用完整的选项列表:

    DirectoryIndex index.cgi index.pl index.shtml index.html
    

    首先列出最常见的选择。

    还要注意,显式创建type-map文件比使用来提供更好的性能MultiViews,因为可以通过读取单个文件来确定必要的信息,而不必扫描目录中的文件。

    如果您的站点需要进行内容协商,请考虑使用type-map文件而不是使用Options MultiViews指令来完成协商。有关协商方法的完整讨论和创建type-map文件的说明,请参阅内容协商文档。

    内存映射

    在Apache 2.x需要查看所传送文件的内容的情况下(例如,在进行服务器端包含处理时),如果操作系统支持某种形式的,通常它会对该文件进行内存映射mmap(2)

    在某些平台上,此内存映射可提高性能。但是,在某些情况下,内存映射可能会损害httpd的性能甚至稳定性:

    • 在某些操作系统上,当CPU数量增加时,mmap扩展性不佳read(2)。例如,在多处理器Solaris服务器上,Apache 2.x有时在mmap禁用时可以更快地交付服务器解析的文件。
    • 如果您对位于NFS挂载的文件系统上的文件进行内存映射,而另一台NFS客户端计算机上的进程删除或截断了该文件,则下次尝试访问映射的文件内容时,您的进程可能会遇到总线错误。

    对于其中任何一个因素都适用的安装,应使用EnableMMAP off禁用已交付文件的内存映射。(注意:可以基于每个目录覆盖此伪指令。)

    发送文件

    在Apache 2.x可以忽略要传递的文件内容的情况下(例如,在提供静态文件内容时),如果OS支持该sendfile(2)操作,则通常使用内核sendfile支持文件。

    在大多数平台上,使用sendfile通过消除单独的读取和发送机制来提高性能。但是,在某些情况下,使用sendfile可能会损害httpd的稳定性:

    • 某些平台可能破坏了构建系统无法检测到的sendfile支持,特别是如果二进制文件是在另一个盒子上构建的,并且转移到了具有sendfile支持被破坏的机器上的话。
    • 使用安装了NFS的文件系统,内核可能无法通过其自己的缓存可靠地为网络文件提供服务。

    对于这些因素之一适用的安装,应使用EnableSendfile off禁用文件内容的sendfile传递。(注意:可以基于每个目录覆盖此伪指令。)

    流程创建

    对于Apache 1.3 MinSpareServersMaxSpareServersStartServers设置都对测试结果巨大的影响。特别是,Apache需要一个“准备阶段”,以便达到足以满足所施加负载的子级数目。最初生成StartServers子代后,每秒只能创建一个子代来满足MinSpareServers设置。因此,由100个并发客户端访问的服务器(使用默认值StartServers5将花费95秒左右的时间来产生足够的子代来处理负载。实际上,这在现实生活中的服务器上效果很好,因为它们不经常重启。但是,在只能运行十分钟的基准测试上,它的性能确实很差。

    实施每秒一秒的规则是为了避免由于新的子项启动而淹没计算机。如果机器忙于产生子代,则无法处理请求。但是它对Apache的感知性能产生了极大的影响,因此必须将其替换。从Apache 1.3开始,该代码将放宽每秒规则。它会先生成一个,然后等待一秒钟,然后再生成两个,再等待一秒钟,然后再生成四个,然后它将以指数方式继续运行,直到每秒每秒生成32个孩子。只要满足MinSpareServers设置,它就会停止。

    这似乎足够灵敏,几乎不需要旋转MinSpareServersMaxSpareServersStartServers旋钮。每秒产生4个以上的子代时,将向发出一条消息ErrorLog。如果您看到很多此类错误,请考虑调整这些设置。使用mod_status输出作为指导。

    与流程创建相关的是由MaxConnectionsPerChild设置引起的流程死亡。默认情况下为0,这意味着每个孩子处理的连接数没有限制。如果您的配置当前将此值设置为一个很小的数字,例如30,您可能希望将其设置得较大。如果您正在运行SunOS或Solaris的旧版本,则10000由于内存泄漏而将其限制为大约。

    使用保持活动状态时,孩子将一直忙于不做任何事情,等待已打开的连接上的更多请求。默认KeepAliveTimeout5秒数尝试最小化此影响。这里的权衡是在网络带宽和服务器资源之间。在任何情况下,您都不应将其提高到60几秒钟以上,因为大多数收益都会丢失。

    编译时配置问题

    选择一个MPM

    Apache 2.x支持可插拔的并发模型,称为多处理模块(MPM)。构建Apache时,必须选择要使用的MPM。对于某些平台mpm_netware,有针对特定平台的MPM :mpmt_os2、和mpm_winnt。对于一般的Unix类型的系统,有几种MPM可供选择。MPM的选择会影响httpd的速度和可伸缩性:

    • workerMPM使用多个子进程,每个许多线程。每个线程一次处理一个连接。对于高流量服务器,工作者通常是一个不错的选择,因为它的内存占用空间比前叉MPM小。
    • eventMPM螺纹状的工人MPM,而是被设计成允许通过传递了一些处理工作的支撑线,以便释放主线程在新的工作要求同时服务更多的请求。
    • preforkMPM使用多个子进程,每个一个线程。每个进程一次处理一个连接。在许多系统上,prefork的速度可与worker媲美,但它使用更多的内存。在某些情况下,Prefork的无线程设计具有优于worker的优点:它可以与非线程安全的第三方模块一起使用,并且在具有较差的线程调试支持的平台上调试起来更容易。

    有关这些和其他MPM的更多信息,请参阅MPM 文档。

    模组

    由于内存使用是性能中的重要考虑因素,因此您应该尝试消除实际上不使用的模块。如果您已将模块构建为DSO,则消除模块是注释掉LoadModule该模块的关联指令的简单问题。这使您可以尝试删除模块,并查看您的站点在缺少模块的情况下是否仍然可以运行。

    另一方面,如果您有静态链接到Apache二进制文件中的模块,则需要重新编译Apache才能删除不需要的模块。

    当然,这里出现的一个相关问题是,您需要哪些模块,不需要哪些模块。当然,这里的答案会因一个网站而异。然而,最小的模块,你可以用得到的名单往往包括mod_mimemod_dir,和mod_log_configmod_log_config当然是可选的,因为您可以运行没有日志文件的网站。但是,不建议这样做。

    原子操作

    一些模块(例如mod_cache工作程序MPM的最新开发版本)使用APR的原子API。该API提供了可用于轻量级线程同步的原子操作。

    默认情况下,APR使用每个目标OS / CPU平台上可用的最有效机制来实现这些操作。例如,许多现代CPU都具有在硬件中执行原子比较和交换(CAS)操作的指令。但是,在某些平台上,APR默认使用原子API的基于互斥体的较慢实现,以确保与缺少此类指令的较旧CPU模型兼容。如果要为这些平台之一构建Apache,并且计划仅在较新的CPU上运行,则可以通过使用以下--enable-nonportable-atomics选项配置Apache,从而在构建时选择更快的原子实现:

    ./buildconf
    ./configure --with-mpm=worker --enable-nonportable-atomics=yes
    

    --enable-nonportable-atomics选项与以下平台有关:

    • SPARC
      上的Solaris默认情况下,APR在Solaris / SPARC上使用基于互斥锁的原子。--enable-nonportable-atomics但是,如果使用进行配置,则APR会生成将SPARC v8plus操作码用于快速硬件比较和交换的代码。如果使用此选项配置Apache,则原子操作将更高效(允许更低的CPU利用率和更高的并发性),但是生成的可执行文件将仅在UltraSPARC芯片上运行。
    • 在x86
      上的Linux默认情况下,APR在Linux上使用基于互斥锁的原子。--enable-nonportable-atomics但是,如果使用进行配置,则APR会生成将486操作码用于快速硬件比较和交换的代码。这将导致更有效的原子操作,但是生成的可执行文件将仅在486和更高版本的芯片上运行(而不是386)。

    mod_status和ExtendedStatus On

    如果您包括在内,mod_status并且还在ExtendedStatus On构建和运行Apache时进行了设置,则Apache将在每个请求上执行两次调用gettimeofday(2)(或times(2)根据您的操作系统),以及对1.3进行多次额外的调用time(2)。这样做是为了使状态报告包含时序指示。为了获得最佳性能,请设置ExtendedStatus off(这是默认设置)。

    接受序列化-多个套接字

    警告:

    考虑到在Apache HTTP Server 2.x版本中所做的更改,本部分尚未完全更新。某些信息可能仍然有用,但请谨慎使用。

    这讨论了Unix套接字API中的一个缺点。假设您的Web服务器使用多个Listen语句来侦听多个端口或多个地址。为了测试每个套接字以查看连接是否准备就绪,Apache使用了select(2)select(2)表示套接字上有零个至少一个连接正在等待。Apache的模型包含多个子级,所有空闲的子级同时测试新连接。一个简单的实现看起来像这样(这些示例与代码不匹配,它们被设计用于教学目的):

    for (;;) {
              for (;;) {
                fd_set accept_fds;
    
                FD_ZERO (&accept_fds);
                for (i = first_socket; i <= last_socket; ++i) {
                  FD_SET (i, &accept_fds);
                }
                rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
                if (rc < 1) continue;
                new_connection = -1;
                for (i = first_socket; i <= last_socket; ++i) {
                  if (FD_ISSET (i, &accept_fds)) {
                    new_connection = accept (i, NULL, NULL);
                    if (new_connection != -1) break;
                  }
                }
                if (new_connection != -1) break;
              }
              process_the(new_connection);
            }
    

    但是这种幼稚的实现存在严重的饥饿问题。回想一下,多个子进程同时执行此循环,因此,多个子进程在两次select请求之间时将被阻塞。select当任何请求出现在任何套接字上时,所有那些被阻止的孩子将唤醒并返回。(唤醒的子代数取决于操作系统和时序问题。)然后,它们全都陷入循环并尝试accept建立连接。但是只有一个能够成功(假设仍然只有一个连接可用)。其余部分将被阻止accept。这有效地将那些子级锁定为能够从该一个套接字(而不是其他套接字)提供请求,并且它们将被卡在那里,直到该套接字上出现足够的新请求以将其唤醒为止。此饥饿问题最早在PR#467中记录。至少有两种解决方案。

    一种解决方案是使套接字无阻塞。在这种情况下,accept不会阻止孩子,他们将被允许立即继续。但这浪费了CPU时间。假设您在中有十个闲置孩子select,并且一个连接到达。然后,其中9个孩子将醒来,尝试accept建立连接,失败,然后循环回去select,什么也做不了。同时,这些子级中的任何一个都不会为其他套接字上发生的请求提供服务,直到他们重新回到另一个套接字为止select。总体而言,除非您拥有(在多处理器设备中)空闲的CPU数量与孩子的空闲数量(不太可能的情况)一样,否则该解决方案似乎效果不佳。

    Apache使用的另一种解决方案是将条目序列化到内部循环中。循环如下所示(差异突出显示):

    for (;;) {accept_mutex_on ();for (;;) {
                fd_set accept_fds;
                
                FD_ZERO (&accept_fds);
                for (i = first_socket; i <= last_socket; ++i) {
                  FD_SET (i, &accept_fds);
                }
                rc = select (last_socket+1, &accept_fds, NULL, NULL, NULL);
                if (rc < 1) continue;
                new_connection = -1;
                for (i = first_socket; i <= last_socket; ++i) {
                  if (FD_ISSET (i, &accept_fds)) {
                    new_connection = accept (i, NULL, NULL);
                    if (new_connection != -1) break;
                  }
                }
                if (new_connection != -1) break;
              }accept_mutex_off ();process the new_connection;
            }
    

    这些功能accept_mutex_onaccept_mutex_off实现了互斥信号灯。任何时候只有一个孩子可以使用该互斥锁。实现这些互斥有多种选择。该选项在src/conf.h(1.3之前)或src/include/ap_config.h(1.3或更高版本)中定义。一些体系结构没有做出任何锁定选择,在这些体系结构上使用多个Listen指令是不安全的。

    Mutex指令可用于mpm-accept在运行时更改互斥量的互斥量实现。该指令记录了不同互斥量实现的特殊注意事项。

    已经考虑但从未实现的另一种解决方案是部分序列化循环-即让一定数量的进程进入。仅在多处理器盒可能同时运行多个子代的情况下,这才有意义,而序列化实际上并没有利用全部带宽。这是未来研究的可能领域,但是由于高度并行的Web服务器不是标准,因此优先级仍然很低。

    理想情况下,Listen如果要获得最高性能,则应运行不带多条语句的服务器。但是请继续阅读。

    接受序列化-单插槽

    上面对于多个套接字服务器来说是不错的选择,但是对于单个套接字服务器呢?从理论上讲,他们不应该遇到任何这些相同的问题,因为所有孩子都可以阻塞accept(2)直到连接建立,并且没有饥饿。实际上,这隐藏了上面在非阻塞解决方案中讨论的几乎相同的“旋转”行为。在大多数TCP堆栈的实现方式中,内核实际上唤醒了所有阻塞的进程accept当单个连接到达时。这些进程之一获取连接并返回到用户空间。其余的在内核中旋转并在他们发现没有连接时回到睡眠状态。用户自定义代码隐藏了这种旋转,但是仍然存在。这可能会导致与多套接字案例的非阻塞解决方案相同的负载高峰浪费行为。

    由于这个原因,我们发现,即使序列化单个套接字的情况,许多体系结构的表现也“更好”。因此,几乎在所有情况下,这实际上都是默认设置。在Linux上进行的粗略实验(双奔腾pro 166 w / 128Mb RAM上的2.0.30)显示,与未序列化的单插槽相比,单插槽的序列化导致每秒请求减少不到3%。但是未序列化的单路套接字在每个请求上显示了额外的100毫秒延迟。这种延迟可能是长途运输线路上的麻烦,而只是LAN上的一个问题。如果要覆盖单套接字序列化,则可以定义SINGLE_LISTEN_UNSERIALIZED_ACCEPT,然后单套接字服务器将根本不会序列化。

    徘徊关闭

    如在 draft-ietf-http-connection-00.txt部分8中所讨论的,为了使HTTP服务器可靠地实现该协议,它需要独立关闭通信的每个方向。(回想一下,TCP连接是双向的。每一半都彼此独立。)

    当此功能添加到Apache时,由于目光短浅,在各种版本的Unix上引起了一系列问题。TCP规范没有声明该FIN_WAIT_2状态有超时,但是没有禁止它。在没有超时的系统上,Apache 1.2会导致许多套接字永远停留在该FIN_WAIT_2状态。在许多情况下,可以通过简单地升级到供应商提供的最新TCP / IP修补程序来避免这种情况。在供应商从来没有发布补丁的情况下(,SunOS4 -虽然有源码许可人可以修补它自己),我们决定禁用此功能。

    有两种方法可以完成此操作。一种是套接字选项SO_LINGER。但是,正如命运所愿,这在大多数TCP / IP堆栈中从未得到正确实现。即使在具有适当实现的堆栈( Linux 2.0.31)上,该方法(cputime)也比下一个解决方案更昂贵。

    在大多数情况下,Apache通过一个名为lingering_close(in http_main.c)的函数来实现这一点。该函数大致如下所示:

    void lingering_close (int s)
            {
              char junk_buffer[2048];
              
              /* shutdown the sending side */
              shutdown (s, 1);
    
              signal (SIGALRM, lingering_death);
              alarm (30);
    
              for (;;) {
                select (s for reading, 2 second timeout);
                if (error) break;
                if (s is ready for reading) {
                  if (read (s, junk_buffer, sizeof (junk_buffer)) <= 0) {
                    break;
                  }
                  /* just toss away whatever is here */
                }
              }
              
              close (s);
            }
    

    这自然会在连接结束时增加一些费用,但对于可靠的实现是必需的。随着HTTP / 1.1越来越流行,并且所有连接都是持久性的,这笔费用将在更多请求上摊销。如果您想玩火并禁用此功能,则可以定义NO_LINGCLOSE,但是完全不建议这样做。特别是,随着HTTP / 1.1流水线持久连接的使用,这lingering_close是绝对必要的(流水线连接速度更快,因此您需要对其进行支持)。

    记分板文件

    Apache的父母和孩子通过称为记分板的方式相互交流。理想情况下,这应该在共享内存中实现。对于那些我们可以访问或已为其提供详细端口的操作系统,通常使用共享内存来实现。其余默认使用磁盘文件。磁盘文件不仅速度慢,而且不可靠(功能较少)。src/main/conf.h为您的体系结构仔细阅读文件,然后查找USE_MMAP_SCOREBOARDUSE_SHMGET_SCOREBOARD。定义这两者之一(以及它们各自的同伴HAVE_MMAPHAVE_SHMGET),将启用提供的共享内存代码。如果您的系统还有其他类型的共享内存,请编辑文件src/main/http_main.c并添加在Apache中使用它所必需的钩子。(也请向我们发送补丁。)

    历史记录:Apache的Linux端口直到Apache 1.2版才开始使用共享内存。这种疏忽导致Linux上的Apache早期版本的行为确实很差且不可靠。

    DYNAMIC_MODULE_LIMIT

    如果您不打算使用动态加载的模块(如果您正在阅读本文并针对每一个性能指标调整服务器,则可能不打算这样做),则应-DDYNAMIC_MODULE_LIMIT=0在构建服务器时添加。这将节省仅用于支持动态加载模块的RAM。

    附录:跟踪的详细分析

    这是Solaris 8上具有工作程序MPM的Apache 2.0.38的系统调用跟踪。此跟踪是使用以下收集的:

    truss -l -p httpd_child_pid.
    

    -l选项告诉truss记录调用每个系统调用的LWP(轻量级进程-Solaris形式的内核级线程)的ID。

    其他系统可能有不同的系统调用跟踪实用程序,如stracektracepar。它们都产生相似的输出。

    在此跟踪中,客户端从httpd请求了10KB静态文件。非静态请求或具有内容协商的请求的痕迹看起来截然不同(在某些情况下非常难看)。

    /67:    accept(3, 0x00200BEC, 0x00200C0C, 1) (sleeping...)
    /67:    accept(3, 0x00200BEC, 0x00200C0C, 1)            = 9
    

    在此跟踪中,侦听器线程在LWP#67中运行。

    请注意缺少accept(2)序列化。在此特定平台上,工作程序MPM缺省情况下使用非序列化接受,除非它正在多个端口上侦听。
    /65:    lwp_park(0x00000000, 0)                         = 0
    /67:    lwp_unpark(65, 1)                               = 0
    

    接受连接后,侦听器线程将唤醒工作线程以执行请求处理。在此跟踪中,将处理请求的工作线程映射到LWP#65。

    /65:    getsockname(9, 0x00200BA4, 0x00200BC4, 1)       = 0
    

    为了实现虚拟主机,Apache需要知道用于接受连接的本地套接字地址。在许多情况下(例如,当没有虚拟主机或Listen使用没有通配符地址的指令时),可以消除此调用。但是,尚未做出任何优化。

    /65:    brk(0x002170E8)                                 = 0
    /65:    brk(0x002190E8)                                 = 0
    

    brk(2)呼叫从堆中分配内存。很少会在系统调用跟踪中看到这些内容,因为httpd 在大多数请求处理中都使用了自定义内存分配器(apr_poolapr_bucket_alloc)。在此跟踪中,httpd刚刚启动,因此必须调用malloc(3)以获取用于创建自定义内存分配器的原始内存块。

    /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
    

    接下来,工作线程以非阻塞模式将与客户端的连接(文件描述符9)。在setsockopt(2)getsockopt(2)调用是Solaris的libc中如何把手的副作用fcntl(2)的插座。

    /65:    read(9, " G E T   / 1 0 k . h t m".., 8000)     = 97
    

    工作线程从客户端读取请求。

    /65:    stat("/var/httpd/apache/httpd-8999/htdocs/10k", 0xFAF7B978) = 0
    /65:    open("/var/httpd/apache/httpd-8999/htdocs/10k", O_RDONLY) = 10
    

    此httpd已使用Options FollowSymLinks和配置AllowOverride None。因此,它不需要到达lstat(2)所需文件的路径中的每个目录,也不需要检查.htaccess文件。它只是调用stat(2)以验证文件:1)存在,并且2)是常规文件,而不是目录。

    /65:    sendfilev(0, 9, 0x00200F90, 2, 0xFAF7B53C)      = 10269
    

    在此示例中,httpd能够通过单个sendfilev(2)系统调用发送HTTP响应标头和请求的文件。Sendfile语义在操作系统之间有所不同。在其他一些系统上,有必要在调用之前执行write(2)writev(2)调用以发送标头sendfile(2)

    /65:    write(4, " 1 2 7 . 0 . 0 . 1   -  ".., 78)      = 78
    

    write(2)调用将请求记录在访问日志中。请注意,此跟踪缺少的一件事是time(2)调用。与Apache 1.3不同,Apache 2.x用于gettimeofday(3)查找时间。在某些操作系统(例如Linux或Solaris)上,gettimeofday它具有优化的实现,不需要像典型的系统调用那样大的开销。

    /65:    shutdown(9, 1, 1)                               = 0
    /65:    poll(0xFAF7B980, 1, 2000)                       = 1
    /65:    read(9, 0xFAF7BC20, 512)                        = 0
    /65:    close(9)                                        = 0
    

    辅助线程会持续关闭连接。

    /65:    close(10)                                       = 0
    /65:    lwp_park(0x00000000, 0)         (sleeping...)
    

    最后,工作线程关闭它刚刚传递的文件并阻塞,直到侦听器为它分配另一个连接。

    /67:    accept(3, 0x001FEB74, 0x001FEB94, 1) (sleeping...)
    

    同时,侦听器线程将连接分配给工作线程后,便能够接受另一个连接(受工作线程MPM中某些流控制逻辑的约束,如果所有可用工作线程都处于繁忙状态,则会限制侦听器)。尽管从此跟踪中看不出来,但下一个accept(2)罐(通常在高负载条件下也可以)与工作线程对刚刚接受的连接的处理并行发生。