nginx怎么实现平滑重启

发布时间 - 2023-05-11 00:00:00    点击率:

一、背景

在服务器开发过程中,难免需要重启服务加载新的代码或配置,如果能够保证server重启的过程中服务不间断,那重启对于业务的影响可以降为0。

二、重启流程

  •  重启意味着新旧接替,在交接任务的过程中势必会存在新旧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;
}

函数的流程为

  1.  将旧的master进程监听的所有fd,拷贝至新master进程的env环境变量nginx_var。

  2. rename重命名pid文件

  3. ngx_execute函数fork子进程,execve执行命令行启动新的server。

  4. 在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  # 重启  # 如图  # 放在  # 过程中  # 第一条  # 第二条  # 重命名  # 都是  # 来了  # 设为 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: bootstrap日历插件datetimepicker使用方法  如何登录建站主机?访问步骤全解析  小米17系列还有一款新机?主打6.9英寸大直屏和旗舰级影像  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  Laravel怎么实现搜索高亮功能_Laravel结合Scout与Algolia全文检索【实战】  Laravel如何配置中间件Middleware_Laravel自定义中间件拦截请求与权限校验【步骤】  详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)  如何在阿里云虚拟主机上快速搭建个人网站?  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  Python自然语言搜索引擎项目教程_倒排索引查询优化案例  1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤  如何在HTML表单中获取用户输入并用JavaScript动态控制复利计算循环  html5怎么画眼睛_HT5用Canvas或SVG画眼球瞳孔加JS控制动态【绘制】  Laravel如何使用集合(Collections)进行数据处理_Laravel Collection常用方法与技巧  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  如何在建站之星网店版论坛获取技术支持?  如何在万网开始建站?分步指南解析  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  如何用PHP快速搭建CMS系统?  如何快速配置高效服务器建站软件?  如何快速查询网址的建站时间与历史轨迹?  Python企业级消息系统教程_KafkaRabbitMQ高并发应用  如何在万网主机上快速搭建网站?  阿里云高弹*务器配置方案|支持分布式架构与多节点部署  Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道  如何在企业微信快速生成手机电脑官网?  js实现点击每个li节点,都弹出其文本值及修改  微信推文制作网站有哪些,怎么做微信推文,急?  高防服务器租用首荐平台,企业级优惠套餐快速部署  Laravel如何使用Blade组件和插槽?(Component代码示例)  网站优化排名时,需要考虑哪些问题呢?  如何在宝塔面板中修改默认建站目录?  ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法  html5的keygen标签为什么废弃_替代方案说明【解答】  Laravel怎么解决跨域问题_Laravel配置CORS跨域访问  Laravel如何创建自定义中间件?(Middleware代码示例)  Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控  如何快速搭建高效可靠的建站解决方案?  深入理解Android中的xmlns:tools属性  如何在宝塔面板创建新站点?  如何快速建站并高效导出源代码?  如何获取免费开源的自助建站系统源码?  Laravel如何为API编写文档_Laravel API文档生成与维护方法  Laravel Facade的原理是什么_深入理解Laravel门面及其工作机制  香港服务器部署网站为何提示未备案?  Laravel用户认证怎么做_Laravel Breeze脚手架快速实现登录注册功能  中国移动官方网站首页入口 中国移动官网网页登录  矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?  如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】  深圳网站制作培训,深圳哪些招聘网站比较好?