Nginx 工作模式和进程模型

工作模式

  1. Nginx启动后,会产生一个master主进程,主进程执行一系列的工作后会产生一个或者多个工作进程worker
  2. 在客户端请求动态站点的过程中,Nginx服务器还涉及和后端服务器的通信。Nginx将接收到的Web请求通过代理转发到后端服务器,由后端服务器进行数据处理和组织;
  3. Nginx为了提高对请求的响应效率,降低网络压力,采用了缓存机制,将历史应答数据缓存到本地。保障对缓存文件的快速访问

进程模型

nginx的进程模型,可以由下图来表示:

image

master进程

主要用来管理 worker 进程,master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。

具体包括以下主要功能:

  • 接收来自外界的信号
  • 向各worker进程发送信号
  • 监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程

重启说明(示例)

比如kill -HUP pid,则是告诉nginx,重启nginx,早期版本可以用这个信号来重启nginx,因为是从容地重启,因此服务是不中断的。(现在一般使用nginx -s reload

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

(master不需要处理网络事件,不负责业务的执行)

worker进程

主要任务是完成具体的任务逻辑。其主要关注点是与客户端或后端真实服务器(此时 worker 作为中间代理)之间的数据可读/可写等I/O交互事件。具体包括以下主要功能:

  • 接收客户端请求;
  • 将请求一次送入各个功能模块进行过滤处理;
  • 与后端服务器通信,接收后端服务器处理结果;
  • 数据缓存proxy_cache模块
  • 响应客户端请求

(一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。)

worker进程是如何处理请求的?

首先,worker进程之间是平等的,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。每个worker进程,处理请求的机会也是一样的。当一个连接请求过来,每个进程都有可能处理这个连接,怎么做的呢?

所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个worker进程注册listenfd读事件,在读事件里调用accept接受该连接。

当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后断开连接,这样就是一个完整的请求就是这样的了。

我们可以了解到一个请求,完全由worker进程来处理,且只在一个worker进程中处理。

Nginx采用的IO多路复用模型

IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程,目前支持I/O多路复用的系统调用有 select , poll , epoll ,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符(socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读 写操作。

select
基本原理

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

优点
  • 目前几乎在所有的平台上支持
缺点

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

  • select最大的缺陷就是单个进程所打开的FD是有一定限制的,它由FD_SETSIZE设置,默认值是1024。(一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看。32位机默认是1024个。64位机默认是2048)
  • 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。(当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的)
  • 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
poll
基本原理

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

优点
  • 它没有最大连接数的限制,原因是它是基于链表来存储的。
缺点
  • 大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
  • poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

注意:从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

基本原理

epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就绪态,并且只会通知一次。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

epoll对文件描述符的操作有两种模式

LT(level trigger)和ET(edge trigger)。LT模式是默认模式,两者区别如下:

  • LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
  • ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
优点
  • 没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。
  • 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。
      只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
  • 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
kqueue

kqueue与epoll非常相似,最初是2000年Jonathan Lemon在FreeBSD系统上开发的一个高性能的事件通知接口。注册一批socket描述符到 kqueue 以后,当其中的描述符状态发生变化时,kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了。只是适应平台不多。

参考:

这应该是Nginx系列最后一篇文章了,如果你有疑问,欢迎交流,水平有限,错误欢迎指正。

Nginx 相关文章:


技术文章也发布在自己的公众号【爱好历史的程序员】,欢迎扫码关注,谢谢!

爱好历史的程序员

本作品采用《CC 协议》,转载必须注明作者和本文链接
分享开发知识,欢迎交流。公众号:开源到
讨论数量: 3
  1. 查了下资料,cat /proc/sys/fs/file-max查看的是系统级别的能够打开的文件句柄的数量,文中这里应该是有误(我也是从参考文章copy过来,并没有向你一样去验证,惭愧,向你学习),自己摸索了下,你可以这样查看
    // 进入目录
    cd /usr/include/
    // 应该能索引到FD_SETSIZE定义的文件
    grep -Rn "FD_SETSIZE" ./*
    //此文件下整个文件里面有select模型FD_SETSIZE的定义
    ./sys/select.h 
  2. master进程只是回管理worker进程和发送重启停止等信号,真正处理请求都是有worker进行。请求处理的整个过程可以看下这篇文章,讲得比我的要清楚:https://blog.csdn.net/xululu123/article/de...
  3. 是的,IO 复用就是用在worker处理client的请求中,更细的说是在nginx内部的事件处理上,也正是这样,nginx才有这么好的性能

另外,看得很仔细哦,给你👍,我对底层的研究并没有达到源码级别,文中解释略有不足。底层的话可以看看:http://tengine.taobao.org/book/chapter_02....

4年前 评论

感谢分享,有几个问题请教一下

  1. 文中说 具体数目可以 cat /proc/sys/fs/file-max 查看。32 位机默认是 1024 个。64 位机默认是 2048
    这个我实际查看了一下好像与默认数据差好多,而且这个值好像是文件系统最大的操作数量?
  2. 每个 worker 进程...当一个连接请求过来,每个进程都有可能处理这个连接,怎么做的呢?
    woker进程怎么知道请求过来的?是因为master进程的通知吗?
  3. Nginx的IO复用具体是在那个环节上使用的?
    是在worker调用Client的时候吗?
4年前 评论
已下线 (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!