先说答案,Nginx之所以支持高并发,是因为它是基于epoll的异步及非阻塞的事件驱动模型。在这个模型下,Nginx服务端可以同一时间接收成千上万个客户端请求而不阻塞。这是因为Nginx会把一个一个的客户端请求注册成事件给到系统内核,而这个事件管理器完全由系统内核管理,Nginx只负责注册和接收通知。下面再来详细聊聊具体原因。
Nginx架构Nginx启动时,先启动一个master进程,然后再根据配置文件里定义的参数来启动对应数量的worker进程。
所以,Nginx是多进程模式,多个进程之间不会相互影响,多个worker进程还可以配置成使用不同的CPU来工作,从而提高了Nginx处理请求速度。
Nginx的异步非阻塞机制首先理解同步和异步的概念,这两个概念是从客户端与服务端通信交互方式来说。
- 同步,指服务端接收到客户端请求后,必须处理完该请求后(发送处理结果给客户端),才会接收客户端发送来的下一个请求。
- 异步,指服务端还没有处理完客户端请求(没有发送处理结果给客户端),就已经接收下一个客户端发来的请求了。
再来理解阻塞和非阻塞的概念。这两个概念是从服务器内部处理请求的方式来说的。
- 阻塞,指服务器接收到请求后,如果遇到IO阻塞,当前线程会被挂起,直到IO完成后唤醒当前线程,当前线程期间不会去处理其他事情。
- 非阻塞,指服务器接收到请求后,如果遇到IO阻塞,当前线程不会挂起,而是会立即返回去执行下一个调用。
同步与异步,重点在于消息通知的方式。阻塞与非阻塞,重点在于等消息时候的行为。
事件驱动模型上面提到的异步非阻塞机制,Nginx是通过基于事件的驱动模型来实现的。这个模型下,客户端发起的所有请求在服务端都会被标记为一个事件,Nginx会把这些事件收集到“事件收集器”里,然后再把这些事件交给内核去处理。
对于Nginx服务器来说,客户端A的请求连接到服务端时,服务端进程(Nginx worker process)会处理该请求,此进程在没有返回给客户端A结果时,它又去处理了客户端B的请求。服务端把客户端A以及客户端B发来的请求作为事件交给了“事件收集器”,而“事件收集器”再把收集到的事件交由“事件发送器”发送给“事件处理器”进行处理。最后“事件处理器”处理完该事件后,通知服务端进程,服务端进程再把结果返回给客户端A、客户端B。在这个过程中,服务端进程做的事情属于用户级别的,而事件处理这部分工作属于内核级别的。也就是说这个事件驱动模型是需要操作系统内核来作为支撑的。
Nginx支持的事件驱动模型Nginx支持的事件驱动模型有:select, poll, epoll, rtsig, kqueue, /dev/poll, eventport等,最常用的是前三种。
- Select模型:首先会创建所关注事件的描述符集合。会分别创建读(Read)事件、写(Write)事件、异常发生(Exception)事件三类描述符集合来收集三类描述符。然后调用底层提供的select()函数,等待事件发生。最后轮询所有事件描述符集合中的每一个描述符,检查是否有相应的事件发生,然后处理事件。
- Poll模型:与select的基本实现方式相同,只不过创建关注的描述符集合时,不分成三个集合,而是一个集合包括所有描述符。
- Epoll模型:当描述符比较多时,遍历描述符集合、然后查找每个描述符是否有相应事件发生这一过程会效率较低。Epoll选择将描述符列表的管理交给内核复制,一旦有事件发生,内核会将有事件发生的描述符列表通知给进程,这样避免了应用程序轮询整个描述符列表。Epoll会通过相关调用,通知内核创建一个N个描述符的事件列表,然后给这些描述符设置所关注的事件,并把他添加到内核的事件列表中,在编码过程中也可以通过相关调用对事件列表中的描述符进行动态地删除和修改。