Apache MPM 事件

Description:worker MPM 的一种变体,其目标是仅消耗具有活动处理能力的连接的线程
Status:MPM
Module Identifier:mpm_event_module
Source File:event.c

Summary

eventmultiprocessing 模块(MPM)旨在通过将一些处理工作传递给侦听器线程,从而释放工作线程来处理新请求,从而允许同时处理更多请求。

要使用event MPM,请在构建httpd时将--with-mpm=event添加到configure脚本的参数中。

与工作人员 MPM 的关系

event基于worker MPM,它实现了混合多进程多线程服务器。单个控制进程(父进程)负责启动子进程。每个子进程都会创建ThreadsPerChild指令中指定的固定数量的服务器线程,以及侦听器线程,该线程监听连接并将连接到达时将其传递给工作线程进行处理。

运行时配置指令与worker提供的指令相同,只是增加了AsyncRequestWorkerFactor

工作原理

该 MPM 尝试修复 HTTP 中的“保持活动状态”。Client 端完成第一个请求后,它可以保持连接打开,使用同一套接字发送其他请求,并节省创建 TCP 连接的大量开销。但是,Apache HTTP Server 传统上会保留整个子进程/线程来 await 来自 Client 端的数据,这有其自身的缺点。为了解决此问题,该 MPM 为每个进程使用专用的侦听器线程来处理侦听套接字,处于保持活动状态的所有套接字,处理程序和协议过滤器已完成工作的套接字以及仅剩余的套接字要做的就是将数据发送到 Client 端。

这种新的架构利用了APR公开的无阻塞套接字和现代内核功能(例如 Linux 的 epoll),不再需要将mpm-accept Mutex配置为可避免雷声群发问题。

单个进程/线程块可以处理的连接总数由AsyncRequestWorkerFactor指令控制。

Async connections

异步连接将需要使用固定的专用工作线程与先前的 MPM,但不需要事件。 mod_status的状态页在“异步连接”部分下显示了新列:

  • Writing

    • 在将响应发送到 Client 端时,由于连接速度太慢,TCP 写缓冲区可能已满。通常,在这种情况下,套接字的write()返回EWOULDBLOCKEAGAIN以使其在空闲时间后再次可写。持有套接字的工作程序可能能够将 await 的任务卸载到侦听器线程,一旦为套接字引发事件(例如,“套接字已现在可写”)。请检查“限制”部分以获取更多信息。
  • Keep-alive

    • “保持活动”处理是工作人员 MPM 的最基本改进。一旦工作线程完成将响应刷新到 Client 端的操作,便可以将套接字处理工作分流到侦听器线程,从而依次 await 来自 OS 的任何事件,例如“套接字可读”。如果 Client 端有任何新请求,则侦听器会将其转发到可用的第一个工作线程。相反,如果出现KeepAliveTimeout,则套接字将被侦听器关闭。这样,辅助线程就不负责空闲套接字,并且可以将它们重新用于服务其他请求。
  • Closing

    • 有时,MPM 需要执行持久的关闭,即在仍将数据传输到 httpd 的同时,将早期错误发送回 Client 端。发送响应然后立即关闭连接不是正确的操作,因为 Client 端(仍在尝试发送其余请求)将重置连接并且无法读取 httpd 的响应。延迟关闭是有时间限制的,但是可能会花费相对较长的时间,因此将其卸载到工作线程(包括关闭钩子和实际套接字关闭)。从 2.4.28 开始,连接最终超时时也是如此(侦听器线程除了 await 和调度其事件外,从不处理连接)。

这些改进对 HTTP/HTTPS 连接均有效。

正常终止流程并使用记分板

该 MPM 过去显示了一些可伸缩性瓶颈,导致出现以下错误:“ 记分板已满,而不是 MaxRequestWorkers ”。 MaxRequestWorkers限制了在任何给定时间将服务的并发请求数,以及允许的进程数(MaxRequestWorkers/ThreadsPerChild);同时,记分板表示所有正在运行的进程及其工作线程的状态。如果记分板已满(因此所有线程的状态都为非空闲),但服务的活动请求数不是MaxRequestWorkers,则意味着其中一些正在阻止可服务但正在排队的新请求(向上到ListenBacklog施加的极限)。在大多数情况下,线程都处于“优美”状态,即它们正在 await 通过 TCP 连接完成其工作,以安全地终止并释放记分板插槽(例如,处理长时间运行的请求,慢速 Client 端或与保持活动状态)。两种情况很常见:

  • graceful restart期间,父进程会发 signal 通知其所有子进程完成其工作并终止,同时它会重新加载配置并派生新进程。如果大一点的孩子在停下来之前 continueRunning 一段时间,记分板将被部分占用,直到他们的插槽被释放。

  • 服务器负载的下降会导致 httpd 停止某些进程(例如,由于MaxSpareThreads)。这特别有问题,因为当负载再次增加时,httpd 将尝试启动新进程。如果这种模式重复出现,则进程的数量可能会增加很多,最终导致尝试停止的旧进程和尝试执行某些工作的新进程混合在一起。

从 2.4.24 开始,mpm-event 变得更智能,并且能够以更好的方式处理正常终止。一些改进是:

  • 允许使用最多ServerLimit的所有计分板插槽。 MaxRequestWorkersThreadsPerChild用于限制活动进程的数量;同时,ServerLimit还考虑了进行平滑关闭的操作,以在需要时允许额外的插槽。这个想法是使用ServerLimit来指示 httpd 在影响系统资源之前允许多少个总体过程。

  • 强制优雅地完成进程以使其处于保持活动状态以关闭其连接。

  • 在正常关闭期间,如果给定进程的运行中的工作线程多于打开的连接,请终止这些线程以更快地释放资源(新进程可能需要)。

  • 如果记分板已满,请防止由于负载减少而导致更多进程正常完成,直到旧进程终止为止(否则,一旦负载再次增加,情况将变得更糟)。

最后一点中描述的行为可以通过连接摘要表中的mod_status通过两个新列“ Slot”和“ Stopping”来完全观察到。前者指示 PID,后者指示过程是否停止。多余的状态“是(旧生成)”表示正常重启后进程仍在运行。

Limitations

改进的连接处理可能不适用于某些已声明自己与事件不兼容的连接过滤器。在这种情况下,该 MPM 将退回到worker MPM 的行为,并为每个连接保留一个工作线程。服务器随附的所有模块均与事件 MPM 兼容。

当前,对于涉及需要读取和/或修改整个响应主体的输出过滤器的请求,存在类似的限制。如果在过滤器处理数据时阻塞了与 Client 端的连接,并且过滤器产生的数据量太大而无法在内存中进行缓冲,则在 httpd await 挂起的数据被释放之前,不会释放用于请求的线程。发送给 Client。
为了说明这一点,我们可以考虑以下两种情况:提供静态资产(如 CSS 文件)与提供从 FCGI/CGI 或代理服务器检索的内容的服务。前者是可以预见的,即事件 MPM 在内容末尾具有完全可见性,并且可以使用事件:提供响应内容的工作线程可以刷新第一个字节,直到返回EWOULDBLOCKEAGAIN,然后将其余的字节委派给侦听器。这依次 await 套接字上的事件,并委派工作以将其余内容刷新到第一个空闲工作线程。同时,在后一个示例(FCGI/CGI /代理内容)中,MPM 无法预测响应的结束,并且工作线程必须先完成其工作,然后才能将控件返回给侦听器。唯一的选择是将响应缓冲在内存中,但是出于服务器稳定性和内存占用的考虑,这不是最安全的选择。

Background material

通过在支持的 os 中引入新的 API,使事件模型成为可能:

  • epoll (Linux)

  • kqueue (BSD)

  • 事件端口(Solaris)

在提供这些新 API 之前,必须使用传统的selectpoll API。如果用于处理许多连接或连接变化率很高,则这些 API 会变慢。新的 API 允许监视更多的连接,并且在一组监视频繁变化的连接时,它们的性能更好。因此,这些 API 使得编写事件 MPM 成为可能,该事件可通过许多空闲连接的典型 HTTP 模式进行更好地扩展。

MPM 假定基础apr_pollset实现是合理的线程安全的。这使 MPM 可以避免过多的高级锁定,也不必唤醒侦听器线程才能向其发送保持活动套接字。当前仅与 KQueue 和 EPoll 兼容。

Requirements

此 MPM 依赖APR的原子比较和交换操作来进行线程同步。如果要针对 x86 目标进行编译,并且不需要支持 386,或者您要针对 SPARC 进行编译,并且不需要在 UltraSPARC 之前的芯片上运行,请在configure脚本的参数中添加--enable-nonportable-atomics=yes。这将导致 APR 使用旧式 CPU 中不可用的有效操作码来实现原子操作。

该 MPM 在缺少良好线程的较旧平台上的性能不佳,但是对于 EPoll 或 KQueue 的要求却引起了争议。

  • 要在 FreeBSD 上使用此 MPM,建议使用 FreeBSD 5.3 或更高版本。但是,如果您使用libkse(请参阅man libmap.conf),则可以在 FreeBSD 5.2.1 上运行此 MPM。

  • 对于 NetBSD,建议至少使用 2.0 版。

  • 对于 Linux,建议使用 2.6 内核。还必须确保您的glibc版本已编译为支持 EPoll。

AsyncRequestWorkerFactor Directive

Description:限制每个进程的并发连接
Syntax:AsyncRequestWorkerFactor factor
Default:2
Context:server config
Status:MPM
Module:event
Compatibility:在 2.3.13 版和更高版本中可用

事件 MPM 以异步方式处理某些连接,其中请求工作线程仅根据需要分配很短的时间,而其他连接每个连接保留一个请求工作线程。这可能导致所有工作人员都被束缚,并且没有工作线程可用于处理已构建的异步连接上的新工作的情况。

为了缓解此问题,事件 MPM 做两件事:

  • 它限制了每个进程接受的连接数,具体取决于空闲请求工作程序的数量。

  • 如果所有工作人员都忙,即使保持活动超时没有到期,它也会以保持活动状态关闭连接。这允许各个 Client 端重新连接到可能仍具有工作线程可用的其他进程。

该指令可用于微调每个进程的连接限制。如果当前连接数(不计算处于“关闭”状态的连接数)低于以下数量,则“进程”将仅接受新连接:

*ThreadsPerChild(AsyncRequestWorkerFactor 空闲 Worker 数)

给定空闲工作线程的平均值,可以估算所有进程之间的最大并发连接数,其估算公式如下:

(ThreadsPerChild(AsyncRequestWorkerFactor 空闲 Worker 数)) ServerLimit

Example

ThreadsPerChild = 10
ServerLimit = 4
AsyncRequestWorkerFactor = 2
MaxRequestWorkers = 40

idle_workers = 4 (average for all the processes to keep it simple)

max_connections = (ThreadsPerChild + (AsyncRequestWorkerFactor * idle_workers)) * ServerLimit
= (10 + (2 * 4)) * 4 = 72

当所有工作线程都处于空闲状态时,可以用一种更简单的方式计算并发连接的绝对最大数量:

( AsyncRequestWorkerFactor + 1) * MaxRequestWorkers

Example

ThreadsPerChild = 10
ServerLimit = 4
MaxRequestWorkers = 40
AsyncRequestWorkerFactor = 2

如果所有进程的所有线程都空闲,则:

idle_workers = 10

我们可以通过两种方式计算并发连接的绝对最大数量:

max_connections = (ThreadsPerChild + (AsyncRequestWorkerFactor * idle_workers)) * ServerLimit
= (10 + (2 * 10)) * 4 = 120

max_connections = (AsyncRequestWorkerFactor + 1) * MaxRequestWorkers
= (2 + 1) * 40 = 120

调整AsyncRequestWorkerFactor需要了解每个特定用例中 httpd 处理的流量,因此更改默认值需要对mod_status进行广泛的测试和数据收集。

在版本 2.3.13 之前,MaxRequestWorkers被称为MaxClients。上面的值表明,旧名称不能准确描述事件 MPM 的含义。

AsyncRequestWorkerFactor可以接受非整数参数,例如“ 1.5”。