首先要明白,Nginx 采用的是多进程(单线程) & 多路IO复用模型。使用了 I/O 多路复用技术的 Nginx,就成了”并发事件驱动“的服务器。nginx
Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。编程
master 接收来自外界的信号,向各 worker 进程发送信号,每一个进程都有可能来处理这个链接。服务器
注意 worker 进程数,通常会设置成机器 cpu 核数。由于更多的 worker 数,只会致使进程相互竞争 cpu ,从而带来没必要要的上下文切换。网络
使用多进程模式,不只能提升并发率,并且进程之间相互独立,一个 worker 进程挂了不会影响到其余 worker 进程。并发
使用多进程模式,不只能提升并发率,并且进程之间相互独立,一个 worker 进程挂了不会影响到其余 worker 进程。socket
主进程(master 进程)首先经过 socket() 来建立一个 sock 文件描述符用来监听,而后fork生成子进程(workers 进程),子进程将继承父进程的 sockfd(socket 文件描述符),以后子进程 accept() 后将建立已链接描述符(connected descriptor),而后经过已链接描述符来与客户端通讯。高并发
那么,因为全部子进程都继承了父进程的 sockfd,那么当链接进来时,全部子进程都将收到通知并“争着”与它创建链接,这就叫“惊群现象”。大量的进程被激活又挂起,只有一个进程能够accept() 到这个链接,这固然会消耗系统资源。命令行
Nginx 提供了一个 accept_mutex 这个东西,这是一个加在accept上的一把共享锁。即每一个 worker 进程在执行 accept 以前都须要先获取锁,获取不到就放弃执行 accept()。有了这把锁以后,同一时刻,就只会有一个进程去 accpet(),这样就不会有惊群问题了。accept_mutex 是一个可控选项,咱们能够显示地关掉,默认是打开的。线程
Nginx 在启动后,会有一个 master 进程和多个 worker 进程。日志
主要用来管理 worker 进程,包含接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当 worker 进程退出后(异常状况下),会自动从新启动新的 worker 进程。
master 进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不须要处理网络事件,不负责业务的执行,只会经过管理 worker 进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。
咱们要控制 nginx,只须要经过 kill 向 master 进程发送信号就好了。好比 kill -HUP pid 是告诉 nginx 从容地重启 nginx。咱们通常用这个信号来重启 nginx,或从新加载配置,由于是从容地重启,所以服务是不中断的。master 进程在接收到 HUP 信号后是怎么作的呢?
首先 master 进程在接到信号后,会先从新加载配置文件,而后再启动新的 worker 进程,并向全部老的 worker 进程发送信号,告诉他们能够光荣退休了。新的 worker 在启动后,就开始接收新的请求,老的 worker 在收到来自 master 的信号后,就再也不接收新的请求,而且在当前进程中的全部未处理完的请求处理完成后,再退出。
固然,直接给 master 进程发送信号,这是比较老的操做方式,nginx 在 0.8 版本以后,引入了一系列命令行参数,来方便咱们管理。好比 ./nginx -s reload 就是来重启 nginx,./nginx -s stop 就是来中止 nginx 的运行。如何作到的呢?咱们仍是拿 reload 来讲,咱们看到,执行命令时,咱们是启动一个新的 nginx 进程,而新的 nginx 进程在解析到 reload 参数后,就知道咱们的目的是控制 nginx 来从新加载配置文件了,它会向 master 进程发送信号,而后接下来的动做,就和咱们直接向 master 进程发送信号同样了。
而基本的网络事件,则是放在 worker 进程中来处理了。多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求只可能在一个 worker 进程中处理,一个 worker 进程不可能处理其它进程的请求。worker 进程的个数是能够设置的,通常咱们会设置与机器 cpu 核数一致,这里面的缘由与 nginx 的进程模型以及事件处理模型是分不开的。
worker 进程之间是平等的,每一个进程处理请求的机会也是同样的。当咱们提供 80 端口的 http 服务时,一个链接请求过来,每一个进程都有可能处理这个链接,怎么作到的呢?首先,每一个 worker 进程都是从 master 进程 fork 过来,在 master 进程里面,先创建好须要 listen的socket(listenfd)以后,而后再 fork 出多个 worker 进程。全部 worker 进程的 listenfd 会在新链接到来时变得可读,为保证只有一个进程处理该链接,全部 worker 进程在注册 listenfd 读事件前抢 accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,在读事件里调用 accept 接受该链接。当一个 worker 进程在 accept 这个链接以后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开链接,这样一个完整的请求就是这样的了。咱们能够看到,一个请求,彻底由 worker 进程来处理,并且只在一个 worker 进程中处理。
当一个 worker 进程在 accept() 这个链接以后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开链接,一个完整的请求。一个请求彻底由 worker 进程来处理,并且只能在一个 worker 进程中处理。
这样作带来的好处:
节省锁带来的开销。每一个 worker 进程都是独立的进程,不共享资源,不须要加锁。同时在编程以及问题查上时,也会方便不少。
独立进程,减小风险。采用独立的进程,可让互相之间不会影响,一个进程退出后,其它进程还在工做,服务不会中断,master 进程则很快从新启动新的 worker 进程。固然,worker 进程的也能发生意外退出。
多进程模型每一个进程/线程只能处理一路 IO,那么 Nginx 是如何处理多路 IO 呢?
若是不使用 IO 多路复用,那么在一个进程中,同时只能处理一个请求,好比执行 accept(),若是没有链接过来,那么程序会阻塞在这里,直到有一个链接过来,才能继续向下执行。而多路复用,容许咱们只在事件发生时才将控制返回给程序,而其余时候内核都挂起进程,随时待命。
Nginx 会注册一个事件:“若是来自一个新客户端的链接请求到来了,再通知我”,此后只有链接请求到来,服务器才会执行 accept() 来接收请求。又好比向上游服务器(好比 PHP-FPM)转发请求,并等待请求返回时,这个处理的 worker 不会在这阻塞,它会在发送完请求后,注册一个事件:“若是缓冲区接收到数据了,告诉我一声,我再将它读进来”,因而进程就空闲下来等待事件发生。