说明:文章主要是整理和总结,下面是网上找一些比较好的资料
资料1: nginx默认以多进程的方式工作,一个master进程和多个worker进程,master进程主要用来管理worker进程.多个worker进程同等竞争来自客户端的请求,一个worker进程可以处理多个请求,但不能处理其它worker进程的请求.每个worker进程里面只有一个主线程,在epoll支持下,采用异步非阻塞的方式来处理请求,从而实现高并发.epoll支持监听多个事件(socket轮询),当事件没准备好时,放到epoll里面,事件准备好了,就去读写.与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级.并发数再多也不会导致无谓的资源浪费(上下文切换),更多的并发数,只是会占用更多的内存而已.而httpd常用的工作方式是每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了,这对操作系统来说,是个不小的挑战.线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大,httpd的性能自然就上不去了.Tengine团队之前有对连接数进行过测试,在24G内存的机器上,Nginx处理的并发请求数达到过200万.(平均1G内存可以处理8万多请求)Nginx支持将某一个进程绑定在某一个核上(CPU亲缘性绑定),这样就不会因为进程的切换带来cache的失效,所以推荐设置cpu有几个核就设置几个worker进程.但注意,过多的worker进程,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换,所以worker进程不是越多越好.
资料2: 进程数与并发数不存在很直接的关系。这取决取server采用的工作方式。 如果一个server采用一个进程负责一个request的方式,那么进程数就是并发数。那么显而易见的,就是会有很多进程在等待中。等什么?最多的应该是等待网络传输。其缺点题主应该也感觉到了,此处不述。 而nginx 的异步非阻塞工作方式正是利用了这点等待的时间。在需要等待的时候,这些进程就空闲出来待命了。因此表现为少数几个进程就解决了大量的并发问题。 nginx是如何利用的呢,简单来说:同样的4个进程,如果采用一个进程负责一个request的方式,那么,同时进来4个request之后,每个进程就负责其中一个,直至会话关闭。期间,如果有第5个request进来了。就无法及时反应了,因为4个进程都没干完活呢,因此,一般有个调度进程,每当新进来了一个request,就新开个进程来处理。 nginx不这样,每进来一个request,会有一个worker进程去处理。但不是全程的处理,处理到什么程度呢?处理到可能发生阻塞的地方,比如向上游(后端)服务器转发request,并等待请求返回。那么,这个处理的worker不会这么傻等着,他会在发送完请求后,注册一个事件:“如果upstream返回了,告诉我一声,我再接着干”。于是他就休息去了。此时,如果再有request 进来,他就可以很快再按这种方式处理。而一旦上游服务器返回了,就会触发这个事件,worker才会来接手,这个request才会接着往下走。 由于web server的工作性质决定了每个request的大部份生命都是在网络传输中,实际上花费在server机器上的时间片不多。这是几个进程就解决高并发的秘密所在。即@skoo所说的 webserver刚好属于网络io密集型应用,不算是计算密集型。 异步,非阻塞,使用epoll,和大量细节处的优化。也正是nginx之所以然的技术基石。
资料3: 最开始接触编程时,使用的是Apache服务器,后来随着网站用户访问量的增加,考虑高并发是必不可少的环节,越来越多的公司使用nginx服务器。我们公司最近也打算更换nginx服务器。那么nginx和Apache有哪些异同点呢,nginx为什么比Apache支持高并发呢? 首先,先看一下各自使用的IO模型。 Apache使用的是select模型,该模型特点如下: 1、每个连接对应一个描述。select模型受限于 FD_SETSIZE(即进程最大打开的描述符数),linux2.6.35为1024,实际上linux每个进程所能打开描数字的个数仅受限于内存大小,然而在设计select的系统调用时,却是参考FD_SETSIZE的值。可通过重新编译内核更改此值,但不能根治此问题,对于百万级的用户连接请求即便增加相应进程数,仍显得杯水车薪。 2、select每次都会扫描一个文件描述符的集合,这个集合的大小是作为select第一个参数传入的值。但是每个进程所能打开文件描述符若是增加了,扫描的效率也将减小。 3、内核到用户空间,采用内存复制方式传递信息。 nginx使用的是epoll模型,该模型特点如下: 1、无文件描述字大小限制仅与内存大小相关。 2、epoll返回时已经明确的知道哪个socket fd发生了什么事件,不用像select那样再一个个比对。 3、内核到用户空间,采用共享内存方式传递消息。 其次,看一下各自的优势。 Apache的优点: 1、rewrite ,比nginx 的rewrite 强大; 2、模块超多,基本想到的都可以找到; 3、bug少 ,nginx的bug相对Apache较多; 4、超稳定,最核心的区别在于apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程。 nginx的优点: 1、轻量级,同样起web 服务,比apache 占用更少的内存及资源; 2、抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能; 3、高度模块化的设计,编写模块相对简单; 4、社区活跃,各种高性能模块发布迅速。 最后,epoll相对select的优点总结如下: 1、select的句柄数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024 表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目。 2、epoll的最大好处是不会随着FD的数目增长而降低效率,在selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,而epoll是维护一个队列,直接看队列是不是空就可以了。epoll只会对”活跃”的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有”活跃”的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个”伪”AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。 3、使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。
资料4 select和epool比较: (1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。 (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
总结: Apache:采用多进程的方式,当有新的连接产生时,Apache会新建一个进程来处理,也就是一个进程处理一个连接请求(最多可同时维护1024个进程,这个数值可调,因为进程太多,CPU扛不住),当进程中的连接需要读取数据时,也就是I/O,进程会阻塞(当该进程阻塞后,CPU释放,其它的进程可以占用CPU,这里涉及到进程间的切换,也会消耗内存和时间,算是慢的原因之一,但不是主要的)。那么这些阻塞的进程啥时被唤醒呢?这里就涉及到事件驱动模型select,它会将所有的连接句柄Copy到内核(这里很慢,还需要消耗内核资源,是导致Apache比Nginx慢的原因之一),然后在内核中遍历所有的连接(注意,是依次遍历所有的连接,从中查找I/O完毕的连接,它是导致Apache比Nginx慢的主要原因),如果发现某些连接I/O完毕,就将进程唤醒,执行后续的操作(例如读取该连接中的数据)。 Nginx:采用多进程单线程的方式,这里的多进程和当前连接数没有关系,多进程的个数在nginx初始化时,已经设定好了,和Apache不一样的是,Nginx的每个进程可以处理多个连接(可以达到上万的数量级),其原理是每个进程会接受大量连接请求(这里其实也是一个一个处理,只是速度非常快,所以每秒处理的数量级非常大)。进程接受到连接请求时,给该连接请求加一个回调函数(后面会用到),然后一起扔给epoll,当该连接需要I/O时,自己就不管了(后面会处理),直接返回,然后处理下一个连接请求(即通过异步非阻塞的方式实现高并发,可以简单理解,该进程把硬骨头都扔出去了,专门吃肉,肯定就快了)。当I/O完毕后,通过上述的回调函数将该连接加入到epool提供的双向链表(该链表记录的都是需要处理的事件),所以系统不需要遍历所有的连接,仅处理epool中的双向链表中的连接事件即可(select 是轮询,epool直接获取即可,这里是高并发的主要原因)