本文章将采用由表及里的方式进行讲解!首先给出Nginx和Apache的优点和缺点,继而再从架构和模式来分析其原因
优缺点分析
一.Nginx
优点:
1.Nginx有很好的并发性,Nginx再Linux中以epoll作为事件驱动模型,处理请求的方式是异步非阻塞的,负载能力要比Apache高跟多,而Apache则是阻塞型的,在高并发的情况下,Nginx能很简单的保持低资源消耗和高性能,而Apache在PHP处理慢或者前端压力很大的情况下,很容易出现进程数飙升,从而拒绝服务的现象
2.nginx处理静态文件的性能相对于Apache更好
3.nginx涉及高度模块化,编写模块相对比较简单
4.nginx配置简洁,配置完成后能使用-t测试配置有没有问题
5.nginx可以作为负载均衡器,支持7层的负载
6.nginx本身就是一个反向代理服务器,而且可以作为非常优秀的邮件代理服务器
7.nginx可以实现热重启
缺点:
1.nginx处理动态文件即和后端PHP服务器交互的能力一般,而Apache对处理动态请求的效率非常的高
关于nginx的详细降解,可以参阅笔者的另一篇文章一文初步认识Nginx
二.Apache
优点:
1.Apache的rewrite要比Nginx强大,在rewrite频繁的情况下,使用Apache
2.因为Apache出现的时间要比Nginx时间长,所以他可能出现的bug也会更少,更加的稳定
3.Apache的PHP的支持比较简单,因为PHP之前就算是Apache架构中的一个模块,而Nginx则需要配合其他后端使用(比如FastCGI)
4.Apache处理动态请求的能力比较强,nginx在这方面做的不好
缺点:
1.Apache相对nginx来说更加笨重,其应对高并发的能力要弱于nginx
三.总结
下面对Nginx和Apache的优缺点用表格的形式表现出来,供大家比对
比较对象 | nginx | apache |
---|---|---|
应对高并发的能力 | 强 | 一般 |
处理静态请求的能力 | 强 | 一般 |
处理动态请求的能力 | 一般 | 强 |
稳定性 | 一般 | 强 |
扩展功能 | 少 | 多 |
资源利用率(性能) | 更好 | 一般 |
多核CPU的支持 | 效果好 | 效果一般 |
静态网页和动态网页的区别请阅读这篇文章静态网页与动态网页的区别
架构分析
我们主要分析Nginx和Apache在高并发和性能上的架构!!!
Nginx与Apache的区别主要是因为Nginx的工作模式是异步非阻塞的,而Apache的工作模式是同步阻塞的
与并发量有关的实现
一.工作模式(处理Web请求的方式,同步or异步)
1.Nginx-----异步
nginx的工作模式的实现是首先ngxin启动创建一个master进程,再由master进程创建worker进程,master进程的作用是创建和停止子进程,并将用户的请求转交给worker进程,即实际处理用户请求的是worker进程,因为其通信模式是异步性的架构,所以一个worker进程可以处理多个用户的请求
2.Apache-----同步
Aapache有好几种工作模式,但其网络通信方式都是同步式的通信,即每个进程或线程只能处理一个用户的请求,与nginx类似,Apache也是由一个主进程控制产生许多工作进程(工作进程下还可以有许多的工作线程),主进程负载创建子进程和调度用户的请求,真正负责处理用户请求的是工作进程(或工作线程)
下面给出Apache的三种工作模式,分别是prefork模式,worker模式和event模式的示意图,其本质都是同步的通信模式,即每个进程或线程只能同时处理一个用户的请求!,但每种模式有其对应使用的场景,这里就不再赘述
经过对Web请求方式的对比,我们大体就可以得出,为什么Nginx的并发性要好于Apache了,下面我给大家总结一下我的看法
因为Nginx的每个worker进程可以处理多个用户的请求,所以Nginx进程切换的次数就会很少,由此上下文的切换就少,这样系统消耗的资源就比较少。而Apache需要频繁的切换进程所以进程切换消耗的资源就阻碍了性能的提升。其次当面对高并发时Apache需要创建多个进程或者线程,创建进程和线程也是需要消耗系统资源的,所以当面对高并发的情况,nginx可以轻松面对,而Apache面对高并发则比较困难
与性能有关的实现
二.事件驱动模型(调用本地进程处理I/O的模式,阻塞or非阻塞)
承接上边介绍的处理Web请求的方式,当服务器收到Web请求之后,由主进程进行调度将请求分配给对应的worker进程。此时worker进程开始处理web请求。假如用户请求的是某个文件中的内容,worker进程就要进行I/O操作。
提到I/O操作,我们这里先简单提一下,阻塞和非阻塞。首先我们这里讲的阻塞是针对I/O操作来说
阻塞:当一个进程调用了某个I/O操作之后,他需要等到I/O操作完成返回的结果,如果在等待期间他什么事情都 不做,只等待I/O操作的完成,那么就说这是一个阻塞过程
非阻塞:当一个进程调用了I/O操作之后,他不需要等待I/O操作完成的返回就可以立即去处理其他事情,这就是一个非阻塞的过程
与之对应的,Nginx的I/O是非阻塞的,Apache的I/O是阻塞的
这里简单讲一下我的看法
非阻塞总是和异步搭配(Nginx),阻塞总是和同步搭配(Apache)。
因为Nginx是异步通信的,所以他非阻塞的I/O是有意义的(因为异步可以同时处理多个请求,当某个请求的I/O操作执行的时候,worker进程可以去处理别的请求)。
因为Apache是同步通信的,所以他非阻塞的I/O是没有意义的(因为同步下,一个进程只能处理一个Web请求,当请求的I/O操作执行的时候,进程也没有别的请求可以处理)
好了,上面讲完了阻塞和非阻塞的概念,接下来我们进入我们的正题事件驱动模型的问题
当我们请求I/O操作之后,我们该如何获知I/O请求已经完成,我们去获取I/O的返回结果呢?有两种思路!
第一种,worker进程对I/O操作进行标记,然后定时的去轮询这些标记,如果轮询到的I/O操作已经完成,那么我们的进程就去处理这个I/O,否则不处理
第二种,worker进程不再去主动询问,而是当I/O操作完成之后,通过某种方式,由内核告知进程I/O操作已经完成,然后worker进程再去处理I/O操作
很明显,第二种方法的效率会更高一些,因为遍历所有的I/O操作,无疑会浪费系统的资源,尤其是当并发量很大,I/O操作很多的时候,需要遍历的范围就很大,那么对用户请求的应答就会变慢,即性能的降低
回到我们的Nginx和Apache,Apache使用的就是第一种模型,典型代表叫做select模型,而Nginx使用的是第二种模型,在Linnux上的实现是epoll模型
下面,我们来简单介绍一些select和epoll这两个模型
select库
使用select库的步骤一般是:
首先,创建所关注事件(I/O操作的具体文件)的描述符集合。对于一个描述符,可以关注其上面的读(read)事件,写(write)事件以及异常发生(Exception)事件,所以要创建三类事件描述符集合,分别用来收集读事件的描述符,写事件的描述符,和异常事件的描述符
其次调用底层的select()函数,等待事件发生。然后轮询所有事件描述符集合种的每一个事件描述符,检查是否有相应的事件发生,如果有就进行处理
为每个I/O请求创建描述符集合,包含三种类型,然后定时遍历这些集合,一旦有I/O的完成就将调用结果返回给work-process进程
epoll库
epoll库是Nginx服务器支持的高性能事件驱动库之一,epoll库的效率非常的高
select库,polo库的处理方式都是创建一个待处理事件列表,然后把这个列表发给内核,返回的时候,再去轮询检查这个列表,以判断事件是否发生。这样当描述符较多的时候效率就显的非常低下了。
一种比较好的做法是,把描述符列表的管理交给内核负责,一旦有某种事情发生,内核把发生事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表,epoll库就是这样的一种模型
首先epoll库通过相关调用通知内核创建一个具有N个描述符的事件列表,然后,给这些描述符设置所关注的事件,并把它添加到内核的事件列表中去,在具体的编码过程中,也可以通过相关调用对事件列表中的描述符进行修改和删除
完成设置之后,epoll库就开始等待内核通知事件发生了,某一事件发生后,内核将发生事件的描述符列表上报给epoll库。得到事件列表的epoll库,就可以进行事件处理了
epoll库在Linux平台上是高效的,它支持一个进程打开大数目的事件描述符,上限是系统可以打开文件的最大数目,同时epoll库的I/O效率不随描述符数目增加而线性下降,因为它只会对内核上报的“活跃”的描述符进行操作
通过对事件处理模型的分析,我们就可以得出,为何Nginx的性能要比Apache的性能好了
因为Nginx所使用的epoll模型处理请求时更加的高效,尤其是在高并发环境下其优势更加突出,因为Apache的select模型需要遍历文件描述符,当并发很大的时候,遍历一遍的时间及消耗都是比较大的,会造成性能的损耗,而epoll模型不同,高并发情况下,也还是由内核将I/O完成的这个消息通告给worker进程,并不会去遍历,只是有完成的就告知worker进程,所以高并发下的nginx的性能不会有太大影响,而Apache在高并发下性能就会有很大的降低