nginx进程架构

1.概述

nginx默认采用的是多进程的架构方式。和haproxy有很大的不同,haproxy作者是推荐使用但进程的方式的,因为作者认为单进程的性能能应付大部分的case。而且多进程会带来很复杂的管理面问题,所以也不得宠。
但nginx采用单master+多worker进程的架构方式,天然就是为了多进程服务。
很多人看nginx代码,都迫不及待的看什么样的io模型,怎么快速做的http解析和收发等数据平面的东西,但当面临使用的时候,管理面遇到的问题远远比数据面严重的多,比如我们要是在云环境中使用nginx为用户做负载均衡或者cdn/waf之类的,必然要考虑如下的问题

  • 如何做到横向扩展,比如一台机器启动多少进程?多加机器能解决性能问题么?
  • m台机器,一台机器n个nginx进程,如何管理这些m*n个进程?比如加载新配置,比如重启死循环或者hang住的进程
  • 当升级重启的时候,如何做到真正的0宕机?
  • m*n的nginx集群,他们的统计怎么搞?比如访问的top 10 域名是什么?

master进程可以认为是管理平面的东西:

  • 加载/更新配置文件
  • 管理所有worker进程的创建,重启

然后我们看一下nginx是如何管理worker进程和配置文件的更新的

2.关于worker进程的管理

worker进程是从master进程fork出来的进程,nginx提供了几种不同的fork方式:

  • NGX_PROCESS_NORESPAWN
  • NGX_PROCESS_JUST_SPAWN
  • NGX_PROCESS_RESPAWN
  • NGX_PROCESS_JUST_RESPAWN
  • NGX_PROCESS_DETACHED

我们一个一个理一下

2.1.NGX_PROCESS_RESPAWN

这个是最常规的操作,fork worker进程的时候设置这个标志,当worker进程因为意外退出的时候,master进程会执行再生(respawn)操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ngx_uint_t
ngx_reap_children(ngx_cycle_t *cycle)
{
//......
if (ngx_processes[i].exited) {
//......
if (ngx_processes[i].respawn
&& !ngx_processes[i].exiting
&& !ngx_terminate
&& !ngx_quit)
{
if (ngx_spawn_process(cycle, ngx_processes[i].proc,
//.....
}
}
}

所以可以认为初次启动master的时候(比如刚启动,比如更新二进制了)都用以这个参数启动worker

2.2.NGX_PROCESS_JUST_RESPAWN

just是刚刚的意思,刚刚spawn出来的,用于更新配置的时候,因为更新配置执行如下的步骤
1.master加载新配置文件
2.fork新的worker进程
3.给使用旧配置文件的worker进程发QUIT信号

第二步fork进程的时候腰加上NGX_PROCESS_JUST_RESPAWN这个标志,用于给第三步区分哪些是旧进程,哪些是新欢。

2.3.NGX_PROCESS_JUST_SPAWN

这个和上一个差不多,用于cache manager,我不喜欢

这里注意一下,上面提到的3个类型,其实是转化成2个标志的,即respawn和just。
just:刚刚搞出来的,别动我,只动就的,用于区分新旧
respawn:本进程被master管理,死的时候可以自动拉起
spwawn由于前面没有re,只是fork出来就拉倒,所以JUST_SPAWN只有just是有含义的

2.4.NGX_PROCESS_DETACHED

这是说fork出来的进程和父进程没有管理的关系,比如nginx的master升级(老版本有bug),新的master从旧的mastr fork出来,就需要这样的标志,fork出来后和父进程没啥关系

2.5.NGX_PROCESS_NORESPAWN

cache loader会用到,当第一次启动的时候,使用NGX_PROCESS_NORESPAWN,就是启动一个进程执行ngx_cache_manager_process_cycle.但需要注意和上面的DETACHED的区别,因为在nginx里,一般父子进程都有很多管道通讯,只有DETACHED的模式下没有pipe通讯,这个NORESPAWN是保留了和父进程的管道通讯的

但是当重新加载配置的时候,还是继续使用NGX_PROCESS_JUST_SPAWN来区分新欢旧爱的

3.关于配置文件的加载过程

修改完配置文件后,通过如下的步骤让配置文件生效

  • 给master进程发送HUP信号

master收到信号后会设置

1
ngx_reconfigure = 1;

然后下个周期检查ngx_reconfigure,调用ngx_init_cycle重新解析配置文件,生成一个cycle,注意一个cycle可以理解对应一个配置文件的周期。
在ngx_init_cycle里会做一些listner的bind和unbind操作,即旧的listener和新的listener的merge,当然还有其他配置的merge。

  • fork worker进程

worker进程里当然会能访问前面的cycle对象

  • 给所有旧的worker发送NGX_SHUTDOWN_SIGNAL信号

旧的worker进程收到后,会关闭listen socket,然后等所有连接断开后,进程退出。

4.关于二进制的升级

写代码难免有bug,有bug就得改,改了后想生效就得升级。

给master发送一个USR2信号,ngx_change_binary会设置为1.
然后在那个ngx_init_cycle里,master进程会fork进程执行新的二进制(ngx_execute_proc)
ngx_new_binary会赋值为新master的进程id。
master起来后就是全新的master,会自动拉起新的worker进程,注意老master和新master都监听相同的listen socket,因为是fork出来执行execv的所以一样,nginx的listen socket的merger是它的killer feature。

这时候2套master和worker进程都在了,然后给旧的master发送WINCH信号,master会给worker发送graceful shutdown通知
这样就剩下旧的master+新的master+新的worker了,
为啥要留着旧的master呢?因为怕新的二进制有问题,如果有问题的话,

  • 发送HUP给旧的master,旧的worker就起来了
  • 发送TERM给新的master,刚来起来的worker就被干掉了
<div class="ds-thread" data-thread-key="nginx_1" data-title="nginx进程架构" data-url=""></div>