Apache 性能调优

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

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

硬件和 os 问题

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

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

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

运行时配置问题

Related Modules Related Directives
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。如果您需要将日志文件中的地址解析为主机名,请使用 Apache 随附的logresolve程序或可用的众多日志报告程序包之一。

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

如果您使用任何来自域的Allow或来自域指令的Deny(例如,使用主机名或域名,而不是 IP 地址),那么您将需要支付两次 DNS 查找费用(反向查找,然后进行正向查找以确保反之亦然)。因此,为了获得最佳性能,请尽可能在使用这些指令时使用 IP 地址而不是名称。

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

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

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

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

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

并且请求 URI /index.html,然后 Apache 将对/www/www/htdocs/www/htdocs/index.html执行lstat(2)。这些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

Negotiation

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

DirectoryIndex index

使用完整的选项列表:

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

首先列出最常见的选择。

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

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

Memory-mapping

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

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

对于其中两个因素均适用的安装,应使用EnableMMAP off禁用已交付文件的内存 Map。 (注意:可以根据每个目录覆盖此指令.)

Sendfile

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

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

对于其中两个因素均适用的安装,应使用EnableSendfile off禁用文件内容的 sendfile 传递。 (注意:可以根据每个目录覆盖此指令.)

Process Creation

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

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

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

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

使用保持活动状态时,孩子将一直忙于不做任何事情,await 已打开的连接上的更多请求。默认的KeepAliveTimeout 5秒尝试最小化此影响。这里的权衡是在网络带宽和服务器资源之间。在任何情况下,您都不应将其提高到60秒以上,例如大部分好处都丧失了

编译时配置问题

选择一个 MPM

Apache 2.x 支持称为Multi-Processing Modules(MPM)的可插入并发模型。构建 Apache 时,必须选择要使用的 MPM。对于某些平台,有特定于平台的 MPM:mpm_netwarempmt_os2mpm_winnt。对于一般的 Unix 类型的系统,有几种 MPM 可供选择。 MPM 的选择会影响 httpd 的速度和可伸缩性:

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

Modules

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

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

当然,这里出现的一个相关问题是,您需要哪些模块,不需要哪些模块。当然,这里的答案会因一个网站而异。但是,您可以使用的* minimal *模块列表通常包含mod_mimemod_dirmod_log_configmod_log_config当然是可选的,因为您可以运行没有日志文件的网站。但是,不建议这样做。

Atomic Operations

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

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

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

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

mod_status 和 ExtendedStatus On

如果在构建和运行 Apache 时包括mod_status,并且还设置了ExtendedStatus On,则 Apache 将在每次请求时对gettimeofday(2)(或times(2)取决于您的 os)执行两次调用,并在(1.3 之前)对time(2)进行几次额外调用。这样做是为了使状态报告包含时序指示。为了获得最佳性能,请设置ExtendedStatus off(默认设置)。

接受序列化-多个套接字

Warning:

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

这讨论了 Unix 套接字 API 中的一个缺点。假设您的 Web 服务器使用多个Listen语句在多个端口或多个地址上侦听。为了测试每个套接字以查看连接是否就绪,Apache 使用了select(2)select(2)表示套接字具有至少一个连接正在 await。 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返回。 (唤醒的子代数取决于 os 和时序问题.)然后,它们全都陷入循环并尝试accept连接。但是只有一个能够成功(假设仍然只有一个连接可用)。其余的将在accept中被“阻止”。这样可以有效地将那些子级锁定为能够从那个套接字(而不是其他套接字)提供请求,并且它们将被卡在那里,直到该套接字上出现足够多的新请求以将其唤醒为止。此饥饿问题最早在PR#467中记录了。至少有两种解决方案。

一种解决方案是使套接字无阻塞。在这种情况下,accept不会阻止孩子,他们将被允许立即 continue。但这浪费了 CPU 时间。假设select中有十个空闲子级,并且一个连接到达。然后,其中 9 个孩子将醒来,尝试accept连接,失败,然后循环回到select,什么也没做。同时,这些子级中的任何一个都没有为其他套接字上发生的请求提供服务,直到他们再次回到select为止。总体而言,除非您拥有(在 multiprocessing 器设备中)空闲的 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;
        }

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

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

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

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

接受序列化-单插槽

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

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

Lingering Close

如第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(在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是绝对必要的(和流水线连接速度更快,因此您要支持它们)。

Scoreboard File

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

Note

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

DYNAMIC_MODULE_LIMIT

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

附录:跟踪的详细分析

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

truss -l -p httpd_child_pid.

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

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

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

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

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

Note

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

/65:    lwp_park(0x00000000, 0)                         = 0
/67:    lwp_unpark(65, 1)                               = 0

接受连接后,侦听器线程将唤醒工作线程以执行请求处理。在此跟踪中,将处理请求的工作线程 Map 到 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

接下来,工作线程以非阻塞模式将到 Client 端的连接(文件 Descriptors 9)。 setsockopt(2)getsockopt(2)调用是 Solaris libc 如何处理套接字上fcntl(2)的副作用。

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

工作线程从 Client 端读取请求。

/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

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

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

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

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

write(2)调用将请求记录在访问日志中。请注意,此跟踪缺少的一件事是time(2)调用。与 Apache 1.3 不同,Apache 2.x 使用gettimeofday(3)查找时间。在某些 os(如 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)可以(通常在高负载条件下也可以)与工作线程对刚刚接受的连接的处理并行发生。

首页