Apache和Nginx比较

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。使用事件驱动、异步非阻塞IO、多进程单线程、模块化,在高连接并发的情况下,Nginx是Apache服务器不错的替代品,具有优秀的工作模式和事件处理模型。

1、工作模式比较

Apache工作模式

1、prefork模式(预派生模式):prefork是Unix平台上的默认多处理模块(MPM),使用多个子进程,每个子进程只有一个线程。每个进程在某个确定的时间只能维持一个连接,效率高,但内存占用量比较大。用进程处理不同的请求,每个请求对应一个子进程,进程之前彼此独立。它会采用预派生子进程方式,当apache启动后会先启动StartServers个子进程,等待1秒后会再创建两个,再等待1秒后创建4个,再一秒后创建8个这样直到创建满MinSpareServers个子进程为止,那么此时MinSpareServers个子进程会待命,这种待命模式不必在新请求到来时重新创建, 一定程度上加快了进程的响应速度。
缺点:资源耗费较大、处理请求数有限(最大的问题在于使用了同步阻塞i/o方式,严重限制了并发请求数)
优点:不必考虑资源同步、线程安全等问题,设计简单且运行稳定

2、work模式:采用混合多线程方式,每个进程中包含多个线程,每个线程对应一个请求。
缺点:需要更多地考虑线程安全性问题,稳定性差相对较差,当请求过多时资源开销过大,假如一个线程崩溃,整个进程就会连同其任何线程一起”死掉”,所以要保证一个程式在运行时必须被系统识别为”每个线程都是安全的”。
优点:更大的请求并发量、更小的资源开销,内存占用量比较小,适合高流量的http服务器。

3、event模式:模式:event模式相对于work模式,它将http连接管理交给了父进程完成,而每个进程中的子线程只处理请求本身。之前尽管HTTP的keepalive方式能减少TCP连接数量和网络负载,但是 Keepalive需要和服务进程或者线程绑定,这就导致一个繁忙的服务器会耗光所有的线程。把服务进程从连接中分离出来,线程本身只处理请求不再负责管理http连接。

nginx工作模式

在工作方式上,nginx分为单工作进程和多工作进程两种模式。在单工作进程模式下,除主进程外,还有一个工作进程,工作进程是单线程的;在多工作进程模式下,每个工作进程包含多个线程。Nginx默认为单工作进程模式。采用多进程非阻塞方式运行,一个master进程,多个worker进程。nginx的进程模型,可以由下图来表示

 

master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。

多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。由于master来管理worker进程,所以我们只需要与master进程通信就行了。master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?

首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。

nginx进程模型的优势:

1、对于每个worker进程来说,独立的进程都不需要加锁,省掉了锁带来的开销;

2、采用独立的进程,可以让相互之间不会影响,一个进程退出后,其他进程还在工作,服务也不会中断,master进程则很快启动新的worker进程。 当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?

首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。

2、事件处理

Apache事件处理模型

多线程方式、一个请求对应一个线程、并发请求多情况下的线程资源开销(CPU/内存),线程上下文切换开销

Apache事件处理模型缺点:

apache服务器在处理请求时采用多线程方式,每个请求独占一个工作线程,当并发数上到几千时,就同时有几千个线程在处理请求了。大量线程的存在使得内存占用量相当大,同时线程的上下文切换也会带来不小的CPU开销。

Nginx事件处理模型

多进程方式,每个进程只有一个主线程、异步非阻塞方式(优化I/O)、一个请求对应一个进程、不需要创建更多的线程,事件切换更轻量
它提供了一种机制,让你可以监听到多个事件,调用它们是阻塞的,但是可以设置超时事件,在超时事件内,如果有事件准备好就返回。比如采用epoll事件处理模型,当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。
1、节约了创建大量线程的资源开销、内存开销
2、节省了线程上下文切换的开销
Apache 和 Nginx 最大的不同是, apache 使用了每个请求一个线程的阻塞 I/O 模型 ,nginx 使用了单线程的非阻塞 I/O 模型。 Apache 的工作池的确是缩减了一些启动内容并销毁了一些进程,但是在处理多请求的时候他仍然让 CPU 在不同的线程间跳来跳去。 Nginx 却把所有的请求都放在一个线程里面。当有一个请求想要发送网络请求的时候, nginx 向后端求求附加一个回调,然后再一个新的活跃客户端请求上工作。Nginx 和 Ternado 如此高效同时服务多个客户端请求的主要原因是:只有一个进程(因此也就剩内存)只有一个线程(因为也就减少了 CUP 在不同的上下文里面交换)。