MPM全称是多道处理模块,我们都知道apache是以模块化方式设计的.那么MPM用来决定apache如何处理用户请求的.是通过一个进程处理一个请求,还是一个线程处理一个请求.当前MPM有三种可以选择的方式:
prefork
worker
event
虽然有以上三种方式,但是要注意在任何时间,必须有一个,而且只能有一个MPM被使用.那么下面就介绍一下这三种处理方式的区别.
prefork
在这种工作模型下,apache进程分为master进程跟worker进程.web服务启动就是启动master进程,随之master进程会启动若干个worker子进程.master进程的工作就是管理worker子进程.而worker子进程的工作就是处理用户请求.当用户发起一个请求,apache就会从空闲的子进程中选择一个处理这个用户请求.
这种处理方式有以下几点好处:
用户不用等到其他进程处理完毕.因为只要有空闲子进程在就可以处理新的请求
如果一个worker子进程崩溃了,不会影响其他worker进程处理请求.
但是worker子进程的个数限制于apache配置文件中如下几个条目的限制
MinSpareServers
最少空闲worker进程.MaxSpareServers
最多空闲worker进程,超过这个数,就会有一些空闲worker进程被killMaxRequestWorkers
同一时刻可以处理的请求数,即并发量MaxConnectionsPerChild
每一个worker子进程一生中可以处理的请求,超过这个数之后就会被master进程kill
同时, 一般master进程使用root用户启动,这样方便master进程监听80端口,以及管理进程.而余下的worker子进程则是以apache配置文件中User
指令指定的用户启动.这样子是为了减少worker子进程的权限.保证安全.
root@ff1221aa94a9:~# ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4448 676 ? Ss 09:33 0:00 /bin/sh -c supervisord -n
root 5 0.0 0.8 60556 17172 ? S 09:33 0:02 /usr/bin/python /usr/bin/supervisord -n
root 31 0.0 0.1 61384 3160 ? Ss 09:33 0:00 /usr/sbin/sshd
root 32 0.0 0.8 200164 16384 ? Ss 09:33 0:00 /usr/local/apache/bin/httpd -k start
daemon 33 0.0 0.4 200300 8392 ? S 09:33 0:00 /usr/local/apache/bin/httpd -k start
daemon 34 0.0 0.3 200300 7512 ? S 09:33 0:00 /usr/local/apache/bin/httpd -k start
daemon 35 0.0 0.3 200300 7512 ? S 09:33 0:00 /usr/local/apache/bin/httpd -k start
daemon 36 0.0 0.3 200300 7512 ? S 09:33 0:00 /usr/local/apache/bin/httpd -k start
daemon 37 0.0 0.3 200300 7512 ? S 09:33 0:00 /usr/local/apache/bin/httpd -k start
执行ps可以看出只有一个master进程以root用户方式启动
worker
prefork的缺点很明显,一个worker进程处理一个请求,并发不会高.而且进程占用的资源太多.做的事情却只是处理一个请求.worker针对prefork的问题进行了改进.
仍然有一个master父进程启动若干个子进程
每个子进程启动若干个线程
每个线程处理每个请求
这样子,worker模型的并发性高于prefork模型.并且由于线程的开销小于进程,所以worker模型占用的资源反而小于prefork.
但是worker相对于prefork存在一个问题:非线程安全.最典型的一个问题在于:如果你的apache使用了worker模型工作.但是php却使用非线程安全的版本,那么这两者就不能工作了.所以纵然worker有万般好,但是碰到使用非线程安全的历史代码,还是只能乖乖使用prefork模型.
worker模型使用多线程响应请求,这样子存在一个问题,即一个线程崩溃就会影响整个进程.所以worker使用的是多进程+多线程的混合模型.即可以提高并发性,也可以避免一个线程崩溃导致整个整个web站点崩溃.
同prefork一样,worker中子进程跟线程数量也收到apache配置文件的控制.有如下参数
MinSpareThreads
最少空闲线程ThreadsPerChild
每个子进程可以创建的线程数量MaxClients
同时可以处理的请求数.....
其web服务调优大抵就是根据服务器配置调节这些参数.具体参数细节可以参考Apache文档
Event
event模型是在apache2.2之后当做试验特性引入的,在apache2.4之后才正式支持.event模型是为了解决长连接(keep-alive)问题而生的.使用worker模型,一个线程对应一个请求,当一个请求为长连接的时候,线程就会保持当长连接状态,等待客户端的下一个请求.这样子当前线程就不能处理其他客户端请求了.
event模型跟worker模型很像,也是多个进程+多个线程的混合模式,但是event模型下每个进程会有一个单独的线程来管理这些keep-alive类型的线程.当新的请求过来的时候,管理线程会把请求交给其他的空闲线程处理.这样子就避免了每个线程都被keep-alive阻塞.
但是event模型并不是所有情况都通用,在https协议下会退化成worker模型.具体原因可以看官方文档.
Nginx
讲到Apache,不得不提起现在Nginx.相比于Apache.Nginx于2004年正式发布.而Apache在1995就已经出现了.当时的web环境还只是简单的展示静态页面,而且并发量远没有现在这么高.所以当时Apache的prefork模型也可以很好的承担web服务需求.加之其稳定性好,没有什么理由不用它.
当时后来互联网渐渐变大,网站的并发量变大,Apache就出现了一个C10K的问题.即一个物理服务器达到并发量1W的时候apache就会承受不了.后来2004年Nginx很好的解决了C10K问题.Nginx为何能优于apache解决C10K问题,我们还是得从其处理请求模型说起.
Nginx有三个著名的特性:
事件驱动编程
异步
非IO阻塞
正是这三种编程方式促使Nginx可以有如此高的并发量.下面来分析下Nginx到底是如何工作的.
同样,Nginx的进程也分为master进程跟worker子进程.(其实还有两个cache有关的进程, 这里略过).在启动nginx之后,master进程就会随即创建一定数量的worker子进程,并且之后worker子进程数量保持不变.并且这些worker子进程都是单线程的.当一个请求到来时,worker进程中某一个空闲进程就会去处理这个请求.乍一看到这里nginx的工作模式跟apache没有什么区别.关键就在于nginx如何处理用户请求.
worker子进程开始处理请求.这个请求可能是访问某个网站的静态页面.而html页面都是保存在硬盘上的.站在操作系统角度来看,nginx是没有办法直接读取硬盘上的文件,必须由nginx告诉操作系统需要读取哪个文件,然后又操作系统去读取这个文件,读取完毕操作系统再交给nginx.也就是说,在操作系统读取文件的时候,nginx是空闲的.如果是apache,那这个时候apache的worker进程/线程就阻塞在这里等待操作系统把文件读取好再交个自己,这种就称之为IO阻塞.
但是nginx不一样, nginx的worker进程在这个时候就会注册一个事件,相当于告诉操作系统:你文件读好了跟我说一下,我先去处理其他事情.然后这个worker就可以去处理新的用户请求了.这里nginx的worker进程并没有由于操作系统读取文件而阻塞等待,这种即称之为非IO阻塞
当操作系统读取好文件之后,就会通知ngixn:我文件帮你读取好了,你过来拿走."操作系统读取好文件"这个事件被触发了,于是Nginx就跑回去把文件拿走,然后返回响应.这种由于某个事件出现触发Nginx执行操作的方式就称为事件驱动编程.
我们回顾上面过程,一个用户请求读取文件,nginx把读取文件这个事情通知操作系统之后就去处理下一个用户请求,直到操作系统读取好文件之后再返回响应.这种一个请求还没有处理完毕就去处理下一个请求的编程方式即异步编程
正是由于nginx这种工作模型,使得nginx在保持一定量的worker进程下,也可以得到相当大的并发量.这点正是nginx优于apache的地方.同样,nginx的这种请求处理模型在处理长连接的时候也可以使用.
Use Both
那么是不是说Nginx一定就优于Apache.Apache就药丸了呢.也不是.一定要记住,一个后来者的出现, 没有在它的前辈所擅长的领域打败它,那么后来者是不可能完全取代前者.很有可能的情况是两者并存.nginx本身并不能处理php,python等脚本语言,只能把这些动态请求通过CGI转发给其他程序处理.所以现在通常的架构是前台Ngixn负责处理静态文件诸如js,css,image文件.而碰到请求php等动态内容.就在后端多个apache服务器中选择一个比较空闲的服务器,把这请求转发给这个服务器处理.等apache处理好之后把返回交给nginx.nginx再返回给用户.这是目前典型的一种设计方案.上面的流程中nginx负责两个功能:反向代理,负载均衡.这也是nginx所擅长的两个功能.而apache丰富的模块可以很好的满足一个站点的各种需求.并且经过了20+年的考验,Apache的稳定性也是可以保证的.