Osheep

时光不回头,当下最重要。

Apache--Mpms && Nginx事件驱动

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 进程被 kill
  • MaxRequestWorkers 同一时刻可以处理的请求数,即并发量
  • 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  03 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 的稳定性也是可以保证的。

《Apache--Mpms && Nginx事件驱动》

web站点架构
点赞