MPMs
Multi-Processing Modules(MPMs)为多路处理模块,对于类UNIX系统,有三个不同的MPM可选择,不同的模式会影响apache的速度和可靠性。
Perfork MPM
该MPM实现了一个非线程,pre-forking的web server,每个进程均可处理请求,并且有一个父进程管理server poll。适用于没有线程安全库,需要避免线程兼容性问题的系统。每个请求之间都是相互独立,这对于那些需要隔离请求的系统来说是最好的MPM。
这个MPM具有很强的自我调节能力,一般情况很少去调整配置。最重要的配置是MaxRequestWorkers,应该让该值大于你所预估的同时并发请求数,同时也应考虑是否有足够的内存。
Perfork 配置
extra/httpd-mpm.conf
1 | <IfModule mpm_prefork_module> |
Worker MPM
该MPM实现了一个混合多进程多线程server,使用线程来处理请求,在处理大量的请求情况下,比Perfork模式消耗更少的资源。同时,它通过保持多个进程,每个进程含有多个线程来保证服务器的稳定性。
一个单独控制进程(父进程)负责子进程的建立,每个子进程建立固定ThreadsPerChild数量的服务线程和一个监听线程,该监听线程接入请求并传递给服务线程处理和应答。
最重要的配置是ThreadsPerChild和MaxRequestWorkers,ThreadsPerChild控制每个进程的线程数量,MaxRequestWorkers控制了最大的总线程数量,其决定了能处理的最大并发请求数量。最大的子进程数量由MaxRequestWorkers/ThreadsPerChild值决定。
两个指令可以对子进程数量和子进程的线程数量进行强限制,但需要完全停止server后再启动才能生效
- ServerLimit限制最大的进程数量,但需要>=MaxRequestWorkers/ThreadsPerChild
- ThreadLimit限制子进程的最大的线程数量,但需要>= ThreadsPerChild
除了处于激活状态的进程,还有一些进程处于等待销毁的状态,但其仍在处理一个连接请求,这时候总的线程数量可能达到了MaxRequestWorkers的数量,但实际处理请求的线程数量并没有达到该数量,可以通过以下设置来避免该情况:
- 设置MaxConnectionsPerChild为0,
- 设置MaxSpareThreads等于MaxRequestWorkers
当存在the thundering herd problem(generally, when there are multiple listening sockets)情况时,该MPM采用了mpm-accept mutex来序列化请求连接,可以通过Mutex指令来修改。详细可以看performance_hints
Worker 配置
extra/httpd-mpm.conf
1 | <IfModule mpm_worker_module> |
Event MPM
官方文档
该MPM可以承受更高的并发,通过传递一些处理工作给监听线程,从而释放工作线程来服务新的请求。
与Worker MPM的关系
Event是基于Worker MPM,也是一个混合多进程多线程server。一个单独控制进程(父进程)负责子进程的建立,每个子进程建立固定ThreadsPerChild数量的服务线程和一个监听线程,该监听线程接入请求并传递给服务线程处理和应答。
相关配置指令与Worker MPM的指令保持一致,只增加了AsyncRequestWorkerFactor。
工作模式
该MPM为了解决HTTP上面的keep alive问题:当一个client完成了第一个请求,会继续保持该连接打开,并通过同一个socket发送更多的请求,减少在建立TCP连接时的开销。然而Apache HTTP Server会保持整个子进程/子线程以等待client端的数据,这样会带来一些缺点。为了解决这个问题,该MPM为每个进程使用专用的监听线程处理监听套接字,所有的处于keepalive状态的sockets,还有通过了handler和protocol filters在将数据发送到client的sockets。
单个进程/线程块可以处理连接的总数量由AsyncRequestWorkerFactor指令来调节
异步连接
除了event外的前面的MPMs,异步连接均需要一个专门固定的工作线程。在mod_status的状态页面显示以下部分的异步连接
- Writing
发送response到client,可能因为连接太慢而TCP write buffer填满后出现。通常向该socket中进行write(),会返回EWOULDBLOCK或者EAGAIN,过一段空闲时间后,才会变成可写。持有该socket的工作线程可能会将该等待的任务返回给监听线程,一旦有事件发生(”the socket is now writable“),监听线程会重新分配该任务到第一个可用的空闲线程 - Keep-alive
来自于worker MPM最基本的改进,一旦工作线程完成一个回复后,会将该socket返回给监听线程,将等待后续的请求。当新的请求来的时候,监听器会直接把请求分配到第一个可用的工作线程。反过来,当KeepAliveTimeout时,socket将会被关闭。通过该方式,工作线程无需负责空闲的sockets,他们可以重用来处理其他的请求。 - Closing
有时候MPM需要完成一些lingering close,即先返回一个早期的错误给client,同时它仍将数据传输给httpd。发送response,然后立即关闭连接是不正确的,因为client(仍在试图发送请求的其余部分)会得到一个connection reset和无法读取httpd的应答。在这种情况下,httpd会读取余下的请求并允许client获取应答。Lingering close是有时间限制,但仍会占用相对较长的时间,因此工作线程会把该任务返回给监听器
这些改进均对HTTP/HTTPS连接有效。
限制
这个连接的改进对某些声明不兼容event的connection filters并不起作用、在这种情况下,MPM会使用worker MPM并为每个连接保持一个工作线程。而server的内置模块均与event MPM兼容。
(详细说明请查看官方文档)
背景资料
event modle可以引进一些操作系统的新的APIs:
- epoll(linux)
- kqueue(BSD)
- event ports(solaris)
在这些新的API之前,采用的是select和poll。当处理大量连接或者连接速度很高的情况下,这些APIs会变慢,而新的APIs则不会。
MPM认为底层的apr_pollset实现是合理的线程安全。这使得MPM避免过多的高级锁,或者为了发送一个keep-alive socket而唤醒监听线程。这些近来只兼容KQueue和EPoll。
要求
该MPM的线程同步基于APR的原子比较和交换操作,如果你在一个X86平台上并且不需要支持386,或者你在一个SPARC平台并且不需要在pre-UItraSPARC芯片上运行,在编译configure时添加参数–enable-nonportable-atomics=yes,这会让APR使用更有效的操作码来实现原子操作,这在旧的CPU上无法实现。
该MPM无法很好的支持缺乏好的线程管理的旧平台,但使用EPoll或KQueue则是可行的
- FreeBSD 5.3以上
- NetBSD 2.0 以上
- Linux 推荐2.6 kernel,同样需保证glibc支持EPoll
AsyncRequestWorkerFactor指令
该指令只在2.3.13以上版本有效(AsyncRequestWorkerFactor 默认值为2)。
event 以异步的方式来处理连接,让工作线程只占用其需要的更短时间,和为只有一个请求的工作线程连接保留其连接。这样会导致一个情况:所有的工作线程被占用,没有工作线程去建立新的异步连接。
为了减轻该情况,event MPM做了两件事:
- 基于空闲的工作线程来限制每个进程的连接数
- 如果所有的工作线程均处于忙状态,会关闭keep-alive状态的连接(继续keep-alive还未超时),这样允许各个clients去重新连接一个不同的进程,该进程可能有可用的工作线程。
这个指令可以用来微调每个进程的连接数限制,一个进程只有但以下条件满足的情况下才会新建连接:
1 | 当前连接数(不包括“closing”状态) < ThreadsPerChild + (AsyncRequestWorkerFactor * number of idle workers) |
所有进程最大的并发连接数可以用以下的公式进行计算:
1 | (ThreadsPerChild + (AsyncRequestWorkerFactor * number of idle workers)) * ServerLimit |
1 | Example: |
当所有工作线程均为空闲的情况下,可以用更简单的方式计算最大并发连接数:
1 | (AsyncRequestWorkerFactor + 1) * MaxRequestWorkers |
1 | Example: |
Tuning AsyncRequestWorkerFactor requires knowledge about the traffic handled by httpd in each specific use case, so changing the default value requires extensive testing and data gathering from mod_status.
Event 配置
extra/httpd-mpm.conf
1 | <IfModule mpm_event_module> |
默认的MPM
编译httpd的时候,若没有指定MPM模式,将会根据当前环境自动选择对应的MPM模式
在类Unix系统中,有以下两个条件来决定默认的MPM模式
- 系统是否支持多线程
- 系统是否支持安全的线程池(如kqueue/epoll)
如果上述问题均为yes,则默认的MPM为event
如果第一个问题为yes,第二个问题为no,则默认的MPM为worker
如果两个问题均为no,则默认的MPM为prefork
当前的MPM模式
运行以下命令,查看MPM输出
1 | /usr/local/apache/bin/apachectl -V |
设置MPM
以静态模块编译MPM
在编译安装configure的时候指定特定的MPM模式即可:–with-mpm=NAME
以动态模块编译MPM
以动态模式编译MPM,并通过LoadModule加载,而不必要对httpd进行重新编译
1 | LoadModule mpm_prefork_module modules/mod_mpm_prefork.so |
但需要在httpd编辑阶段指定了编译共享模块:–enable-mods-shared=all 或者 –enable-mpms-shared=all