本篇内容主要讲解“nginx怎么实现平滑重启”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nginx怎么实现平滑重启”吧!
一、背景
在服务器开发过程中,难免需要重启服务加载新的代码或配置,如果能够保证server重启的过程中服务不间断,那重启对于业务的影响可以降为0。
二、重启流程
三、nginx实现
为了验证nginx平滑重启,笔者首先尝试nginx启动的情形下再次开启一个新的server实例,结果如图:

很明显,重新开启server实例是行不通的,原因在于新旧server使用了同一个端口80,在未开始socket reuseport选项复用端口时,bind系统调用会出错。nginx默认bind重试5次,失败后直接退出。而nginx需要监听ipv4地址0.0.0.0和ipv6地址[::],故图中打印出10条emerg日志。
接下来就开始尝试平滑重启命令了,一共两条命令:
kill -usr2 `cat /var/run/nginx.pid`
kill -quit `cat /var/run/nginx.pid.oldbin`
第一条命令是发送信号usr2给旧的master进程,进程的pid存放在/var/run/nginx.pid文件中,其中nginx.pid文件路径由nginx.conf配置。
第二条命令是发送信号quit给旧的master进程,进程的pid存放在/var/run/nginx.pid.oldbin文件中,随后旧的master进程退出。
那么问题来了,为什么旧的master进程的pid存在于两个pid文件之中?事实上,在发送信号usr2给旧的master进程之后,旧的master进程将pid重命名,原先的nginx.pid文件rename成nginx.pid.oldbin。这样新的master进行就可以使用nginx.pid这个文件名了。
先执行第一条命令,结果如图:

不错,新旧master和worker进程并存了。 再来第二条命令,结果如图:

如你所见,旧的master进程8527和其worker进程全部退出,只剩下新的master进程12740。
不由得产生困惑,为什么手动开启一个新的实例行不通,使用信号重启就可以达到。先看下nginx log文件:

除了之前的错误日志,还多了一条notice,意思就是继承了sockets,fd值为6,7。 随着日志翻看nginx源码,定位到nginx.c/ngx_exec_new_binary函数之中,
ngx_pid_t
ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv)
{
...
ctx.path = argv[0];
ctx.name = "new binary process";
ctx.argv = argv;
n = 2;
env = ngx_set_environment(cycle, &n);
...
var = ngx_alloc(sizeof(nginx_var)
+ cycle->listening.nelts * (ngx_int32_len + 1) + 2,
cycle->log);
...
p = ngx_cpymem(var, nginx_var "=", sizeof(nginx_var));
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
p = ngx_sprintf(p, "%ud;", ls[i].fd);
}
*p = '\0';
env[n++] = var;
...
env[n] = null;
...
ctx.envp = (char *const *) env;
ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
if (ngx_rename_file(ccf->pid.data, ccf->oldpid.data) == ngx_file_error) {
...
return ngx_invalid_pid;
}
pid = ngx_execute(cycle, &ctx);
if (pid == ngx_invalid_pid) {
if (ngx_rename_file(ccf->oldpid.data, ccf->pid.data)
== ngx_file_error)
{
...
}
}
...
return pid;
}
函数的流程为
将旧的master进程监听的所有fd,拷贝至新master进程的env环境变量nginx_var。
rename重命名pid文件
ngx_execute函数fork子进程,execve执行命令行启动新的server。
在server启动流程之中,涉及到环境变量nginx_var的解析,ngx_connection.c/ngx_add_inherited_sockets具体代码为:
static ngx_int_t
ngx_add_inherited_sockets(ngx_cycle_t *cycle)
{
...
inherited = (u_char *) getenv(nginx_var);
if (inherited == null) {
return ngx_ok;
}
if (ngx_array_init(&cycle->listening, cycle->pool, 10,
sizeof(ngx_listening_t))
!= ngx_ok)
{
return ngx_error;
}
for (p = inherited, v = p; *p; p++) {
if (*p == ':' || *p == ';') {
s = ngx_atoi(v, p - v);
...
v = p + 1;
ls = ngx_array_push(&cycle->listening);
if (ls == null) {
return ngx_error;
}
ngx_memzero(ls, sizeof(ngx_listening_t));
ls->fd = (ngx_socket_t) s;
}
}
...
ngx_inherited = 1;
return ngx_set_inherited_sockets(cycle);
}
函数流程为:
解析环境变量nginx_var的值,获取fd存入数组
fd对应的socket设为ngx_inherited,保存这些socket的信息。
也就是说,新的server压根就没重新bind端口listen,这些fd状态和值都是新的master进程fork时带过来的,新的master进程监听处理继承来的文件描述符即可,这里比较关键的一点在于listen socket文件描述符通过env传递。
到此,相信大家对“nginx怎么实现平滑重启”有了更深的了解,不妨来实际操作一番吧!这里是天达云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!