保护私人版权,尊重他人版权。转载请注明出处并附带页面链接
背景
对于一个高并发的网络服务来说,平滑重启功能是必不可少的。试想,如果我们直接kill然后再start一个服务进程,则会导致:
1. 正在处理的连接直接中断,客户端返回错误,即便通过客户端重试,仍然会对业务造成影响;
2. kill后start前,中间短时间内新的请求无法建立连接;
3. 对于设置了factcgi_bind或者proxy_bind的nginx,在kill后会有socket处于FIN_WAIT或者TIME_WAIT阶段,立即启动后相关接口会返回500。
nginx平滑重启原理
1. nginx的主要架构
nginx是一种master-worker的架构。程序启动后会解析配置文件、打开监听端口、初始化共享内存,然后根据worker_processes的配置,fork出几个woker进程,worker进程处理所有的网络请求,master进程会一直阻塞,只处理各种信号。启动流程如下:
注:
1)上图的启动流程省略了很多细节,只保留跟平滑重启有关的一些函数;
2)如果在配置文件中将master_process置为0(默认为1),nginx会只有一个master进程,进程解析配置、打开端口后会进行网络处理。
2. fork后两个进程的关系
fork是glibc提供的函数,它内部调用clone或者fork系统调用,clone或者fork系统调用会复制出一个新的进程(其实根据clone的参数,也可能复制出来的是一个线程,即对于一般用户来说的进程或者线程,都是使用同一个系统调用生成的,对于操作系统来说,进程或者线程都对应一个task_struck),新的woker进程跟master有同样的代码,并且继承了所有的文件描述符。因为在master中打开了一个socket并且bind、listen,那么在几个worker中都继承了这个listening socket,都可以进行accept。如果来了一个请求,哪个woker会accept成功呢?其实操作系统会随机选择一个进程,accept成功,其他不会,类似操作系统在listen的端口上进行了负载均衡。
3. nginx收到reload信号后的处理逻辑
使用nginx -s reload进行平滑重启。nginx启动时会通过参数-s 发现目前要进行信号处理而不是启动nginx服务,然后他会查看nginx的pid文件,pid文件中保存有master的进程号,然后向master进行发送相应的信号,reload对应的是HUP信号,所以nginx –s reload 跟kill -1 masterpid 一样。Master收到HUP信号后的处理流程如下:
1)master解析新的配置文件。
2)master fork出新的worker进程,此时新的worker会和旧的worker共存。
3)master向旧的worker发送QUIT命令。
4)旧的worker会关闭监听端口,不再接受新的网络请求,并等待所有正在处理的请求完成后,退出。
5)此时只有新的worker存在,nginx完成了重启。
假设nginx监听80端口,nginx启动后,master会进行socket(),bind(),listen()三个函数,此时只有master监听80端口,然后master fork出来n个worker,此时有n+1个进程监听80端口,n个woker都会调用accept处理新来的连接。然后用户修改了nginx配置文件,并进行重启,此时master会首先fork出n个新的worker,此时有2*n+1个worker监听80端口;然后旧的n个worker会关闭listening socket,此时有n+1个监听80端口,新的请求都会落在新的worker上。从头至尾,80端口一直都处于打开状态,客户端一直都能连接。
在master启动新的worker后,会sleep一小段时间,然后向旧的worker发送quit消息,那sleep的功能是什么?假设,master启动新的worker后,直接向旧的worker发送quit,因为系统调度的不确定性,可能旧的worker已经关闭了listening socket,而新的worker还没有运行,这样一个很小的时间间隔中,将没有进程对listening socket 进行accept,客户端的连接请求将放在listening socket的半连接队列中,当半连接个数大于backlog时,客户端将无法连接(如果间隔t*流量qps > listen的backlog时),关于listen的backlog可以查阅相关资料。
4. 总结:nginx实现的平滑重启主要逻辑
- master解析配置文件,打开监听端口;worker继承监听端口,处理网络请求,master等待信号。
- master 处理reload信号,重新解析配置,建立新的listening socket,fork新的worker,并向旧的worker发送quit消息。
- 旧的worker关闭listening socket,处理完所有连接后,退出。