一、Nginx 的进程模型

 Nginx 模型有两种进程,Master进程和Worker进程,Master进程主要用来管理Worker进程,管理包含:

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

而基本的网络事件,则是放在Worker进程中来处理了,多个Worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。Worker进程的个数是可以设置的,一般我们会设置与机器CPU核数一致,这里面的原因与Nginx进程模型以及事件处理模型分不开。

(1)守护进程 

   一般启动nginx后,在unix系统的后端会以daemon(守护进程)的方式运行,守护进程是只运行的服务器端程序,通常在系统后 台运行,没有控制终端,不与前台交互,daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭时结束。

(2)Nginx启动方式 
    Nginx的启动方式有两种:

  • 单进程启动:此时系统中只有一个进程,这个进程既是master进程,也是worker进程。
  • 多进程启动:此时系统中有且仅有一个master进程,有多个worker进程,master进程主要是用来管理worker进程的。

(3)Nginx进程模型 

              

nginx多线程 nginx有几个进程_多线程

Nginx 服务器,正常运行过程中:

  • 多进程:一个 Master 进程、多个 Worker 进程
  • Master 进程:管理 Worker 进程

           (1)对外接口:接收外部的操作(信号)

           (2)对内转发:根据外部的操作的不同,通过信号管理 Worker

           (3)监控:监控 worker 进程的运行状态,worker 进程异常终止后,自动重启 worker 进程

  • Worker 进程:所有 Worker 进程都是平等的

          (1)实际处理:网络请求,由 Worker 进程处理;

          (2)Worker 进程数量:在 nginx.conf 中配置,一般设置为核心数,充分利用 CPU 资源,同时,避免进程数量过多,避免进程竞争 CPU 资源,增加上下文切换的损耗。

思考:

  1. 请求是连接到 Nginx,Master 进程负责处理和转发?
  2. 如何选定哪个 Worker 进程处理请求?请求的处理结果,是否还要经过 Master 进程?

(4)怎样操作Nginx? 
      如上所知,master进程主要是用来管理worker进程的,所以操作Nginx只需要操作master进程就好,我们通过发送信号的方式来操作master进程。master进程会接收来自外界发送来的消息,再根据信号做不同的事情,通过kill向master进程发送信号。

(5)怎样处理请求? 

       worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket之后,然后再fork出多个worker进程,这样每个worker进程都可以去accept这个socket(当然不是同一个socket,只是每个进程的这个socket会监控在同一个ip地址与端口,这个在网络协议里面是允许的)。一般来说,当一个连接进来后,所有在accept在这个socket上面的进程,都会收到通知,而只有一个进程可以accept这个连接,其它的则accept失败,这是所谓的惊群现象。当然,nginx也不会视而不见,所以nginx提供了一个accept_mutex这个东西,从名字上,我们可以看这是一个加在accept上的一把共享锁。有了这把锁之后,同一时刻,就只会有一个进程在accpet连接,这样就不会有惊群问题了。accept_mutex是一个可控选项,我们可以显示地关掉,默认是打开的。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。

(6)如何实现高并发? 

   nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的。

(7)为什么采用异步非阻塞的方式?

      虽然每个worker里面只有一个主线程,但nginx采用了异步非阻塞的方式来处理请求,具体到系统调用就是像 select/poll/epoll/kqueue这样的系统调用。 它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们上面的两个问题,拿epoll为例(在后面的例子中,我们多以epoll为例子,以代表这一类函数),当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。

与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。

 

二、Nginx框架处理流程

     nginx对请求处理分为11个阶段,在这11个阶段中有处理的先后顺序,如果第一个阶段的模块处理并返回后,后面的阶段将不会再处理,灰色阶段是nginx框架做的事,我们无法处理,如下:

nginx多线程 nginx有几个进程_Nginx_02

1.realip:可以拿到客户端真实IP
2.limit_req(限制每秒连接数)、limit_conn(限制每秒并发连接数)
3.access、auth_basic等是做验证的,可以让客户端输入用户和密码后才能请求
4.content阶段是对请求发往上游服务后并进行响应
5.log是记录请求日志

三、Nginx的事件处理模型

request:Nginx 中 http 请求。

基本的 HTTP Web Server 工作模式:

  1. 接收请求:逐行读取请求行请求头,判断段有请求体后,读取请求体
  2. 处理请求
  3. 返回响应:根据处理结果,生成相应的 HTTP 请求(响应行响应头响应体

Nginx 也是这个套路,整体流程一致。

nginx多线程 nginx有几个进程_Nginx_03

四、模块化体系结构

nginx.conf中的配置信息,根据其逻辑上的意义对其进行分类,可以分成多个作用域或指令上下文,指令上下文层次关系如下:

            

nginx多线程 nginx有几个进程_nginx多线程_04

 

  1.    main:Nginx在运行时与具体业务功能无关的参数,比如工作进程数、运行身份等。
  2.    http:与提供http服务相关的参数,比如keepalive、gzip等。
  3.    server:http服务上支持若干虚拟机,每个虚拟机一个对应的server配置项,配置项里包含该虚拟机相关的配置。
  4.    location:http服务中,某些特定的URL对应的一系列配置项。
  5.    mail: 实现email相关的SMTP/IMAP/POP3代理时,共享的一些配置项。

二、模块体系

    Nginx的内部结构是由核心部分和一系列功能模块组成的,这样可以使得每个模块的功能相对简单,便于对系统进行功能扩展,各模块之间的关系如下图:

            

nginx多线程 nginx有几个进程_nginx多线程_05

  • nginx core实现了底层的通讯协议,为其他模块和Nginx进程构建了基本的运行时环境,并且构建了其他各模块的协作基础。
  • http模块mail模块位于nginx core和各功能模块的中间层,这2个模块在nginx core之上实现了另外一层抽象,分别处理与http协议和email相关协议(SMTP/IMAP/POP3)有关的事件,并且确保这些事件能被以正确的顺序调用其它的一些功能模块。

   nginx功能模块基本上分为如下几种类型:

    (1) event module:搭建了独立于操作系统的事件处理机制的框架,以及提供了各具体事件的处理,包括ngx_event_module、ngx_event_core_module和ngx_epoll_module等,Nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。

    (2) phase handler:此类型的模块也被直接称为handler模块,主要负责处理客户端请求并产生待响应内容,比如ngx_http_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。

     (3) output filter:也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改,比如可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。

     (4) upstream:实现反向代理功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端,upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。

     (5) load-balancer:负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。

      (6) extend module:根据特定业务需要编写的第三方模块。

五、常见问题解析

Nginx vs. Apache

  nginx vs. apache:



网络 IO 模型:

  1. nginx:IO 多路复用,epoll(freebsd 上是 kqueue )
  1. 高性能
  2. 高并发
  3. 占用系统资源少
  1. apache:阻塞 + 多进程/多线程
  1. 更稳定,bug 少
  2. 模块更丰富

参考:https://www.zhihu.com/question/19571087

场景:

处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程

  1. IO 多路服用一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个;
  2. 阻塞 IO + 多线程:每一个请求,新建一个服务线程

思考IO 多路复用 和 多线程 的适用场景?

  • IO 多路复用:单个连接的请求处理速度没有优势,适合 IO 密集型 场景,事件驱动
  • 大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求;
  • 消耗更少的系统资源(不需要线程调度开销
  • 适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度
  • 阻塞IO + 多线程:实现简单,可以不依赖系统调用,适合 CPU 密集型 场景
  • 每个线程,都需要时间和空间;
  • 线程数量增长时,线程调度开销指数增长

六、Nginx 最大连接数

基础背景:

  1. Nginx 是多进程模型,Worker 进程用于处理请求;
  2. 单个进程的连接数(文件描述符 fd),有上限(nofile):ulimit -n
  3. Nginx 上配置单个 worker 进程的最大连接数:worker_connections 上限为 nofile
  4. Nginx 上配置 worker 进程的数量:worker_processes

因此,Nginx 的最大连接数:

  1. Nginx 的最大连接数:Worker 进程数量 x 单个 Worker 进程的最大连接数
  2. 上面是 Nginx 作为通用服务器时,最大的连接数
  3. Nginx 作为反向代理服务器时,能够服务的最大连接数:(Worker 进程数量 x 单个 Worker 进程的最大连接数)/ 2。
  4. Nginx 反向代理时,会建立 Client 的连接后端 Web Server 的连接,占用 2 个连接

思考:

  1. 每打开一个 socket 占用一个 fd
  2. 为什么,一个进程能够打开的 fd 数量有限制?

附录

HTTP 请求和响应

  • HTTP 请求:
  1. 请求行:methodurihttp version
  2. 请求头
  3. 请求体
  • HTTP 响应:
  1. 响应行:http versionstatus code
  2. 响应头
  3. 响应体

七、IO 模型

场景:

处理多个请求时,可以采用:IO 多路复用 或者 阻塞 IO +多线程

  1. IO 多路服用一个 线程,跟踪多个 socket 状态,哪个就绪,就读写哪个;
  2. 阻塞 IO + 多线程:每一个请求,新建一个服务线程

思考:IO 多路复用 和 多线程 的适用场景?

  • IO 多路复用:单个连接的请求处理速度没有优势
  • 大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求;
  • 消耗更少的系统资源(不需要线程调度开销
  • 适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度
  • 阻塞IO + 多线程:实现简单,可以不依赖系统调用。
  • 每个线程,都需要时间和空间;
  • 线程数量增长时,线程调度开销指数增长

select/poll 和 epoll 比较

详细内容,参考:

select/poll 系统调用:


select


  • 查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回;
  • fd_set 是一个位集合,大小是在编译内核时的常量,默认大小为 1024
  • 特点:
  • 连接数限制,fd_set 可表示的 fd 数量太小了;
  • 线性扫描:判断 fd 是否就绪,需要遍历一边 fd_set;
  • 数据复制:用户空间和内核空间,复制连接就绪状态信息

poll

  • 解决了连接数限制
  • poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组
  • 解决 fd 数量过小的问题
  • 数据复制:用户空间和内核空间,复制连接就绪状态信息

epoll: event 事件驱动

  • 事件机制:避免线性扫描
  • 为每个 fd,注册一个监听事件
  • fd 变更为就绪时,将 fd 添加到就绪链表
  • fd 数量:无限制(OS 级别的限制,单个进程能打开多少个 fd)

select,poll,epoll:

  1. I/O多路复用的机制;
  2. I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
  1. 监视多个文件描述符
  1. 但select,poll,epoll本质上都是同步I/O
  1. 用户进程负责读写(从内核空间拷贝到用户空间),读写过程中,用户进程是阻塞的;
  2. 异步 IO,无需用户进程负责读写,异步IO,会负责从内核空间拷贝到用户空间

八、Nginx 的并发处理能力

关于 Nginx 的并发处理能力:

  • 并发连接数,一般优化后,峰值能保持在 1~3w 左右。(内存和 CPU 核心数不同,会有进一步优化空间)

更多细节,参考: