nginx 平滑重启的实现方法
一、背景
在服务器开发过程中,难免需要重启服务加载新的代码或配置,如果能够保证server重启的过程中服务不间断,那重启对于业务的影响可以降为0。最近调研了一下nginx平滑重启,觉得很有意思,记录下来供有兴趣的同学查阅。
二、重启流程
- 重启意味着新旧接替,在交接任务的过程中势必会存在新旧server并存的情形,因此,重启的流程大致为:
- 启动新的server
- 新旧server并存,两者共同处理请求,提供服务
- 旧的server处理完所有的请求之后优雅退出
- 这里,最主要的问题在于如何保证新旧server可以并存,如果重启前后的server端口一致,如何保证两者可以监听同一端口。
三、nginx实现
为了验证nginx平滑重启,笔者首先尝试nginx启动的情形下再次开启一个新的server实例,结果如图:

很明显,重新开启server实例是行不通的,原因在于新旧server使用了同一个端口80,在未开始socket reuseport选项复用端口时,bind系统调用会出错。nginx默认bind重试5次,失败后直接退出。而nginx需要监听IPV4地址0.0.0.0和IPV6地址[::],故图中打印出10条emerg日志。
接下来就开始尝试平滑重启命令了,一共两条命令:
|
1
2
|
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函数之中,
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
ngx_pid_tngx_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具体代码为:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
static ngx_int_tngx_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 平滑重启的实现方法的更多相关文章
- nginx平滑重启与平滑升级的方法
如何实现nginx平滑重启与平滑升级? 平滑重启 kill -HUP `cat /usr/local/www/nginx/logs/nginx.pid` 平滑升级nginx: cd /yujialin ...
- 【学习笔记】启动Nginx、查看nginx进程、查看nginx服务主进程的方式、Nginx服务可接受的信号、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级
1.启动nginx的方式: cd /usr/local/nginx ls ./nginx -c nginx.conf 2.查看nginx的进程方式: [root@localhost nginx] ...
- 启动Nginx、查看nginx进程、nginx帮助命令、Nginx平滑重启、Nginx服务器的升级
1.启动nginx的方式: cd /usr/local/nginx ls
- Apache和Nginx平滑重启
之前修改了服务器配置都是简单粗暴的用restart重启apache/nginx,据说这样不好.需要平滑重启服务器,避免重启时打断用户行为.然后就根据官方文档了解了一下平滑重启的命令.本文根据Apach ...
- Nginx平滑重启
kill -HUP cat /usr/local/nginx/logs/nginx.pid`
- nginx重启几种方法
http://blog.csdn.net/zqinghai/article/details/71125045 ps -ef|grep nginx 平滑重启命令: kill -HUP 住进称号或进程号文 ...
- Nginx常用的平滑重启
之前在做运维工作中,经常需要添加虚拟主机,或者添加修改配置文件,但是测试环境还好,随便玩,如果是生产环境的话,既要保证配置不出问题,有不能中断服务.如果是这样的话,就需要对配置文件进行语法检测以及平滑 ...
- Nginx 的启动、停止、平滑重启、信号控制和平滑升级
Nginx 的启动 假设 nginx 安装在 /usr/local/nginx 目录中,那么启动 nginx 的命令就是: [root@localhost ~]# /usr/local ...
- Nginx 启动、停止、平滑重启和平滑升级 graceful shutdown of worker processes
Controlling nginx http://nginx.org/en/docs/control.html nginx can be controlled with signals. The pr ...
随机推荐
- jQuery实现图片上传
$('input[type="file"]').change(function(event) { var currentTarget = event.currentTarget; ...
- Java 之 NOSQL
一.什么是 NOSQL NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库. 随着互联网web2.0网站的兴起,传统的关系数据 ...
- robotframework Run keyword if ELSE 用法
1.Run keyword if 后面必须跟 关键字 ,可以是你自己的, 也可以是 框架自带的 2.ELSE 必须是大写,
- 【Docker】docker安装Jenkins
一.下载镜像 docker pull jenkinsci/jenkins 二.运行Jenkins容器 docker run --name myjenkins -d -p 8580:8080 -p 50 ...
- uuid简述
什么是UUID? UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符,参考RFC规范-RFC4122. UU ...
- USB之设备插入波形变化2
============= 本系列参考 ============= <圈圈教你玩USB>.<Linux那些事儿之我是USB> 协议文档:https://www.usb.or ...
- Linux命令——gdisk、fdisk、partprobe
gdisk.fdisk MBR分区表请使用fdisk分区,GPT分区表请使用gdisk分区 MBR与GPT区别参考:Linux磁盘管理——MBR 与 GPT gdisk.gdisk这两个命令参数不需要 ...
- vue父组件触发子组件方法
比如应用场景是弹窗中的组件,想要点弹窗时更新该组件展示对应记录的的值 methods: { edit (record) { this.mdl = Object.assign({}, record) t ...
- 在Windows下/Linux下安装jdk版本
到官网https://www.oracle.com/technetwork/java/javase/downloads/index.html选择适合自己的版本, 目前我做测试和开发主要用的是jdk 8 ...
- web程序防止攻击的一些资料——整理
地址:https://docs.microsoft.com/en-us/previous-versions/aspnet/a2a4yykt(v=vs.100)?redirectedfrom=MSDN ...