Nginx的进程结构
Nginx有两种进程结构:单进程结构和多进程结构
默认使用多进程结构。单进程结构只适合开发环境,实际应用中要用多进程结构。
为什么nginx要使用多进程而不是多线程结构呢?
因为nginx要保持高可用性和可靠性。如果使用多线程,由于线程之间是使用同一个地址空间的,所以当某个第三方模块引起一个地址空间的段错误时会导致所有线程全部挂掉。多进程就不会出现这个问题。
具体结构如下:
Nginx会产生一个master父进程,该父进程会产生很多子进程。
这些子进程又分为两类:cache进程和worker进程
Nginx进程结构
第三方模块是不会在master进程中加入自己的功能代码的。所以不会因为第三方模块的错误导致master进程崩溃。master进程设计出来的目的是用来管理worker子进程,而worker子进程才是真正用来处理请求的。master的工作是监控每一个worker进程是否在正常工作,是否需要重新载入配置文件。
缓存要在多个worker进程间共享,缓存不仅要被worker进程使用还要被cache进程(cache manager进程和cache loader进程)使用。cache进程是为nginx做反向代理时终端返回的响应做缓存(像之前做反向代理的时候,终止终端服务器依旧可以在反向代理服务器得到终端的页面就是因为将终端页面缓存了下来)。
而这么多个worker和cache进程都是通过共享内存来通信的。
电脑有几核,nginx就有几个worker进程,而且每个worker进程会和独立的CPU核绑定在一起,这样可以更好的使用CPU核的缓存。
我们执行 nginx -s reload 进行重新读取配置文件的过程其实是master父进程将cache和所有worker子进程关闭,并且读取最新配置文件再重新生成新的cache和worker子进程的过程。
所以执行 nginx -s reload 时,master进程不会关闭,worker进程会关闭。
查看一下nginx的进程:
ps -ef|grep nginx
root 22651 1 0 17:26 ? 00:00:00 nginx: master process nginx
nobody 24756 22651 0 17:36 ? 00:00:00 nginx: worker process
由于我的电脑是单核的所以,看到只有一个worker进程。第二列是进程号,第三列是该进程的父进程号,第三列1表示是root开启的进程,而worker的父进程号22651看得出其父进程是master进程。
如果将24756这个worker进程杀死,他会发送一个信号给master进程,master会再生成一个新的worker进程,来保持一个CPU有一个worker进程的进程结构。
Nginx中的信号管理
nginx是多进程结构,通过发送信号来做进程间的通信的。
怎么发送信号:
通过kill命令发送信号。
nginx中比较关键的几个信号有CHLD/TERM/QUIT/HUP/USR1/USR2这几个信号。能够发送和处理信号的有master进程,worker进程和nginx命令行。
下面是对CHLD/TERM/QUIT/HUP/USR1/USR2这几个信号一一介绍:
Linux中子进程挂掉,子进程会向父进程发送一个CHLD信号。
在nginx中,如果因为某个第三方模块的bug导致某个worker进程挂掉,该worker进程会发送CHLD信号给master。master就会重新生成一个新的worker顶替。
master还会接收一些信号来管理worker进程。
假如master进程的进程id(即pid)为55555
kill -TERM 55555
发送term信号给master,master就会马上结束掉master父进程(结束掉自己,当然master结束了,下面的worker也会全部结束)
kill -QUIT 55555
发送quit信号给master,master会优雅的结束自己这个进程,优雅的结束就是不会马上终止用户的请求,而是先拒绝新的请求,将连接的请求处理完后结束进程。
kill -HUP 55555
发送hup信号给master,master会重载配置文件,即关闭所有worker子进程再重新生成worker子进程
kill -USR1 5555
发送usr1信号给master,master会重写日志文件,也会关闭所有worker子进程再重新生成worker子进程
master接收到这4个信号会再去向其worker进程发送这些信号达到关闭和管理worker进程的目标。
上面这4个信号可以通过nginx命令发送。
分别对应
nginx -s stop
nginx -s quit
nginx -s reload
nginx -s reopen
而其实执行这几个nginx命令其实本质上还是通过kill发送上面那几个信号来控制nginx进程的,其过程是读取/usr/local/nginx/logs/nginx.pid这个pid文件里记录的master进程id,然后再执行kill命令发送信号。
worker进程也可以直接接受kill发送的TERM/QUIT/HUP/USR1信号,但是是作用于worker进程自己。
例如我知道某一个worker进程为55566
kill -TERM 55566
那么就只有这一个worker进程挂掉而已,当然master会再生成一个新的worker进程。
除了这4个信号,还有USR2和WINCH这两个信号,是用于做热部署的。这两个信号只能通过kill命令发送,无法通过nginx命令发送。
nginx -s reload 的流程
1.reload命令会向master进程发送HUP信号
2.master进程校验配置文件语句是否正确
3.master进程打开新的监听端口
4.master进程用新配置启动新的worker子进程
5.master进程向老的worker子进程发送quit信号
6.老的worker进程关闭监听句柄,处理完所有连接在旧的worker进程的请求才会结束。
也就是说,这是一个平滑重启worker子进程的过程。已连接到旧的worker进程的请求要被处理完之后才会结束旧的worker子进程。当然在这个新旧交替的过程中,如果有新的请求会被分配到新的worker子进程,而旧的worker子进程只需处理完之前的请求,不会处理新的请求。而且这个交替过程是很快的。
但是如果旧的请求要处理很久,旧的worker进程也会很久都不会结束,不过就像刚刚说的,新的请求会交给新的worker进程处理。
热部署(升级nginx版本或添加模块)的流程
1.先将旧版本的nginx二进制命令文件换成新版本的nginx二进制命令
2.使用 “kill -USR2 master的pid” 向master进程发送USR2信号
3.master进程会去修改pid文件名,加后缀.oldbin
4.master进程用新的nginx命令启动新的master进程,此时用ps命令发现会有两个master进程,一个新的一个旧的,新的master会生成新的worker进程。
5.使用 “kill -WINCH 旧的master的pid” 向老的master进程发送WINCH信号,来关闭老的worker进程。
此时老的worker进程都没了,但老的master进程还保留,方便我们之后回滚
6.当我们想回滚的时候,只需向新的master发送quit信号,再向老的master发送HUP信号,那么新master会消失,老master会生成worker进程。
此时我们又使用回老版本的nginx了
通过上面的方式,我们实现了不停止服务的情况下升级了nginx。在新老交替的过程,是新老nginx一起处理请求的,当然这个交替的时间很短很短。
nginx -s quit 优雅停止的过程
这里的停止是针对worker进程
1.设置定时器
worker_shutdown_timeout
2.关闭监听句柄,即让worker进程停止接受新的请求连接。
3.关闭空闲连接
4.在循环中等待全部连接关闭(worker进程每处理完一个请求就会关闭该请求对应的连接,所以这里也就是等待所有旧的请求处理完)
5.worker进程退出
如果第四步花了很长的时间,这个时间超过了worker_shutdown_timeout设置的时间,就会直接强制退出,即使有些请求没有处理完。