一、背景

在服务器开发过程中,难免需要重启服务加载新的代码或配置,如果能够保证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日志。

接下来就开始尝试平滑重启命令了,一共两条命令:

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传递。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

java jar包 平滑重启,nginx 平滑重启的实现方法相关推荐

  1. centos 开机启动java_Centos 7将java jar包自定义开机启动服务

    Centos 7将java jar包自定义开机启动服务 1. 先上 jar包的启动脚本 vim service.sh #!/bin/bash # 需要变更的参数 # 先查看java绝对路径:which ...

  2. 手动部署java jar包

    手动部署java jar包 出现的问题 使用说明 使用步骤 注意事项 脚本内容 出现的问题 项目开发或者部署过程中,开发人员总是因为启动命令的错误,导致启动参数不对,或者环境不对的情况,根据此种情况, ...

  3. Java Jar包的压缩、解压使用指南

    转载自 Java Jar包的压缩.解压使用指南 什么是jar包 JAR(Java Archive)是Java的归档文件,它是一种与平台无关的文件格式,它允许将许多文件组合成一个压缩文件. 如何打/解包 ...

  4. java jar包示例_Java包isCompatibleWith()方法与示例

    java jar包示例 包类的isCompatibleWith()方法 (Package Class isCompatibleWith() method) isCompatibleWith() met ...

  5. java jar包示例_Java包getSpecificationVendor()方法和示例

    java jar包示例 包类的getSpecificationVendor()方法 (Package Class getSpecificationVendor() method) getSpecifi ...

  6. java jar包示例_Java包getImplementationTitle()方法和示例

    java jar包示例 包类的getImplementationTitle()方法 (Package Class getImplementationTitle() method) getImpleme ...

  7. java jar包示例_Java包getImplementationVersion()方法和示例

    java jar包示例 包类的getImplementationVersion()方法 (Package Class getImplementationVersion() method) getImp ...

  8. java jar包示例_Java包getSpecificationVersion()方法和示例

    java jar包示例 包类的getSpecificationVersion()方法 (Package Class getSpecificationVersion() method) getSpeci ...

  9. Discuz! Ucenter API for JAVA jar包和测试代码

    为什么80%的码农都做不了架构师?>>> Discuz! Ucenter API for JAVA jar包和测试代码 Discuz! Ucenter API for JAVA-ja ...

  10. Stegsolve查看隐水印(暗水印)java jar包工具

    Stegsolve查看隐水印.隐写水印.暗水印的java jar包工具 (1)下载jar包 file/Stegsolve.jar at main · zhangphil/file · GitHubCo ...

最新文章

  1. 010-012列表:一个打了激素的数组
  2. 央视与遗传发育所合拍《锄禾者新说》科教片
  3. Shell:字符串操作
  4. 留着肯定有用,让你脱胎成技术大神的JAVA开发技巧
  5. centos7 python3.6升级到3.7_Centos7下把python 2.7升级到python 3.6(升级过程遇到的一些相关问题)...
  6. 下列不是c语言浮点常量的是,C语言二级题库.doc
  7. c++ primer 5th p289页练习题自己的解答程序
  8. 新手入门python的注意事项_【新手入门Python语言的方法】
  9. 在服务器端渲染完毕的 Angular Component,在客户端还会重新渲染一次吗?
  10. 欢乐纪中某A组赛【2019.7.5】
  11. ubuntu 18.04下 配置qt opencv的坑
  12. 数据集准备及数据预处理_数据理解和准备–数据集的基础工作
  13. 质因数分解(0)P2012_1
  14. Facebook 研发可穿戴脑机接口,读心术成真?
  15. MongoDB CookBook读书笔记之备份与恢复
  16. 单片机入门学习十五 STM32单片机学习十二 电容触摸按键
  17. 企业如何管理软件资产,软件许可证,软件license,license管理,许可管理
  18. 解决SpringBoot项目jar包启动慢
  19. Reliance Jio 4G网速最快 超过沃达丰及Idea
  20. 安卓开发——升级compileSdkVersion、targetSdkVersion、buildToolsVersion、support包到最新(28)问题解决

热门文章

  1. 广东职业教育信息化研究会2019年会暨区块链专题研讨会
  2. 推荐几个华为,字节跳动、蚂蚁金服等大佬的公众号
  3. 使用.Net Core CLI命令dotnet new创建自定义模板
  4. ASP.NET Core launchsettings.json文件(8)《从零开始学ASP.NET CORE MVC》:
  5. DDD领域驱动设计理论篇 - 学习笔记
  6. 用StyleCop规范团队代码
  7. 【送书活动】C# 程序员的自我修养
  8. Ocelot-基于.NET Core的开源网关实现
  9. ASP.NET Core 认证与授权[7]:动态授权
  10. 配置高性能ElasticSearch集群的9个小贴士