我眼中的 Nginx(四):是什么让你的 Nginx 服务退出这么慢?
张超:又拍云系统开发高级工程师,负责又拍云 CDN 平台相关组件的更新及维护。Github ID: tokers,活跃于 OpenResty 社区和 Nginx 邮件列表等开源社区,专注于服务端技术的研究;曾为 ngx_lua 贡献源码,在 Nginx、ngx_lua、CDN 性能优化、日志优化方面有较为深入的研究。
笔者曾今在更新 Nginx 服务的过程中发现旧的 Nginx worker 进程退出非常缓慢(旧的 worker 进程始终处在 "is shutting down" 的状态),对此非常好奇,并对此展开了一些研究,本文将介绍 Nginx worker 进程退出时的准备步骤,延缓退出的原因,并介绍对应的解决办法。
准备退出
当 worker 进程接收到 master 进程要求它退出的指令后(详见笔者另一篇文章:谈谈 Nginx 信号集),它便会开始为退出做准备。
首先 worker 进程会将正在监听的套接字从事件分发器(epoll,kqueue 等)中删除,并将它们关闭,之后它将不再处理连接事件。
接着关闭所有的空闲连接,所谓的空闲连接,指的是当前没有请求正在使用的连接,例如 Nginx 和后端服务器维持的长连接,或者 ngx_lua Cosocket 对象底层的长连接。
接着 worker 进程会等待所有定时器过期(ngx_lua 提供给用户使用的定时器比较特殊,在退出阶段,它会提前过期,其他的 Nginx 内部的定时器不会提前过期),并同时处理尚未完成的事件。等事件处理完毕后, worker 进程会调用所有模块注册的 exit_process 钩子,最后退出。
退出被延缓
了解了 worker 进程退出时的准备过程后,我们可以深入分析为什么有的时候退出如此缓慢。
根据笔者目前的分析,目前有以下两种情况会延缓 worker 进程的退出:
- ngx_lua:在提前过期的定时器中使用 Cosocket
- Nginx http/2 实现上的一个 bug
第一种情况曾有人在 ngx_lua 的 issue 页面提出过( Cosocket :setkeepalive() in a a premature timer handler blocks Nginx worker from exiting · Issue #1279 · openresty/lua-Nginx-module)[1]。
比如 issue 中的示例代码:
ngx.timer.at(100, function ()
-- This blocks Nginx worker from exiting
local timer_sock = ngx.socket.tcp()
timer_sock:connect("127.0.0.1", 8080)
timer_sock:setkeepalive()
end)
当然,这段代码省略了一些错误处理,但是用以解释问题已经足够。这段代码注册了一个定时器,只要这个定时器运行,就会创建一个 Cosocket 对象,然后去连接本机的 8080 端口,然后马上将这个对象底层的连接置为 keep alive 状态。
先说 connect 函数,如果和对端的连接不能一次性完成,ngx_lua 会为这次连接操作添加一个定时器,用以判断连接超时,当然这里是连接本机的端口,因此几乎不会出现连接超时(对端异常除外)。
假如这里所要连接的对端处在公网,而且网络状况不理想的话,连接超时就有可能发生了,ngx_lua 默认的 Cosocket 连接超时是 60s(lua_socket_connect_timeout),这意味着这个 worker 进程会等待至少 60s,然后再退出。
同样地,setkeepalive 也会为这条连接设置一个超时时间,默认也是 60s( lua_socket_keepalive_timeout) ,因此 worker 进程也不得不等到这个定时器过期,或者某个时刻对端主动关闭/异常关闭这条连接后,它才能够退出。
读者可能会有疑惑,之前讲到 worker 进程退出时会主动关闭这些空闲的长连接,那为什么这个示例还回造成 worker 进程退出那么慢呢?即使是本机连接,也有可能出现无法一次完成连接( EAGAIN) 的情况,此时当前定时器的 Lua 协程就会被挂起,因此当 worker 进程在关闭所有空闲连接的时候,这个示例里 setkeepalive 是还没被执行到的(甚至可能连接也没有建立完成),所以这条连接在当时不是空闲的。直到后来某个时刻连接建立完成或者超时,当时的 Lua 协程重新得到运行机会,才会为这条连接添加定时器,置为空闲状态。
另外一个阻碍 worker 进程退出的原因来自于一个 Nginx HTTP/2 模块实现上的缺陷(见 Stale workers not exiting after reload (with HTTP/2 long poll requests))[2]。这个问题在 Nginx/1.11.6 发布之后就修复了(见 Nginx: 5e95b9fb33b7)[3],1.11.6 之前的版本,如果一个 HTTP/2 协议的客户端一直在打开新的流,会导致这条连接上一直有事件在处理(当然会伴随着创建定时器),这会导致 worker 进程会一直无法退出,直到这条连接断开。
Nginx 支持透明代理 websocket 连接。在 Nginx/1.13.7 版本以前,如果 worker 进程存在一些 websocket 连接,而且连接上经常有数据传送,使得连接一直在正常工作的话,即使 worker 进程收到来自 master 的退出指令,它也无法立刻退出,它需要等到这些连接出现异常、超时或者是某一端主动断开后,才能正常退出。
shutdown timeout
旧 worker 进程不能及时退出,就会一直占用着系统资源(CPU、内存和文件描述符等),这对系统资源是一种浪费,因此 Nginx/1.11.11 加入了一个新的指令(即 worker_shutdown_timeout,见 Core functionality)[4],允许用户自定义 shutdown 超时时间,如果一个 worker 在接收到退出的指令后经过 worker_shutdown_timeout 时长后还不能退出,就会被强制退出。
它的实现原理(Nginx: 97c99bb43737)[5]也是通过创建定时器来实现的,一旦定时器过期, 所有连接都会被设置为 close 和 error 状态(c->error = 1,c->close = 1),这个标志位事实上意味着 TCP 连接异常,Nginx 设计上对于这种状态的连接,都会立刻结束对应的所有请求、事件。通过这样一个标志位的设置,就达到了强制关闭所有连接、删除所有定时器的目的,最终及时退出旧的 worker 进程,释放系统资源。
虽然这个功能早在 Nginx/1.11.11 就加入了,但是没有完全覆盖到所有的情况,例如上文所述的 websocket 连接的处理,那部分代码并没有判断 c->close 和 c->error 的状态位。所以仍然无法尽快终止这些 websocket 连接。直到 Nginx/1.13.7,这个问题才被修复。所以如果读者们遇到类似的问题,可以考虑升级 Nginx 至少到 1.13.7 版本。
[1]
issue 页面: https://github.com/openresty/lua-nginx-module/issues/1279
[2]
缺陷: https://trac.nginx.org/nginx/ticket/1106
[3]
修复: http://hg.nginx.org/nginx/rev/5e95b9fb33b7
[4]
指令: http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout
[5]
原理: http://hg.nginx.org/nginx/rev/97c99bb43737
《我眼中的 Nginx》系列:
我眼中的 Nginx(三):Nginx 变量和变量插值
我眼中的 Nginx(二):HTTP/2 dynamic table size update
我眼中的 Nginx(一):Nginx 和位运算
我眼中的 Nginx(四):是什么让你的 Nginx 服务退出这么慢?的更多相关文章
- 异数OS 织梦师-Xnign(四)-- 挑战100倍速Nginx,脚踩F5硬件负载均衡
. 异数OS 织梦师-Xnign(四)– 挑战100倍速Nginx,脚踩F5硬件负载均衡 本文来自异数OS社区 github: https://github.com/yds086/HereticOS ...
- Nginx知多少系列之(十四)Linux下.NET Core项目Nginx+Keepalived高可用(主从模式)
目录 1.前言 2.安装 3.配置文件详解 4.工作原理 5.Linux下托管.NET Core项目 6.Linux下.NET Core项目负载均衡 7.负载均衡策略 8.加权轮询(round rob ...
- 《nginx 四》双机主从热备
lvs+keepalived+nginx实现高性能负载均衡集群 LVS作用 LVS是一个开源的软件,可以实现传输层四层负载均衡.LVS是Linux Virtual Server的缩写,意思是Linux ...
- Nginx(四) nginx+consul+upasync 在ubnutu18带桌面系统 实现动态负载均衡
1.1 什么是动态负载均衡 传统的负载均衡,如果Upstream参数发生变化,每次都需要重新加载nginx.conf文件,因此扩展性不是很高,所以我们可以采用动态负载均衡,实现Upstream可配置化 ...
- Nginx四个作用
本文只针对Nginx在不加载第三方模块的情况能处理哪些事情,由于第三方模块太多所以也介绍不完. Nginx能做什么 ——反向代理 ——负载均衡 ——HTTP服务器(动静分离) ——正向代理 以上就是我 ...
- Nginx主配置参数详解,Nginx配置网站
1.Niginx主配置文件参数详解 a.上面博客说了在Linux中安装nginx.博文地址为:http://www.cnblogs.com/hanyinglong/p/5102141.html b.当 ...
- Nginx系列一:正向代理和反向代理、Nginx工作原理、Nginx常用命令和升级、搭建Nginx负载均衡
转自https://www.cnblogs.com/leeSmall/p/9351343.html 仅供个人学习 一.什么是正向代理.什么是反向代理 1. 正向代理,意思是一个位于客户端和原始服务器( ...
- nginx入门与实战 安装 启动 配置nginx Nginx状态信息(status)配置 正向代理 反向代理 nginx语法之location详解
nginx入门与实战 网站服务 想必我们大多数人都是通过访问网站而开始接触互联网的吧.我们平时访问的网站服务 就是 Web 网络服务,一般是指允许用户通过浏览器访问到互联网中各种资源的服务. Web ...
- Lua:Nginx Lua环境配置,第一个Nginx Lua代码
一.编译安装LuaJIT Lua:编译安装LuaJIT,第一个Lua程序 http://blog.csdn.net/guowenyan001/article/details/48250427 二.下载 ...
随机推荐
- es6(五):class关键字(extends,super,static)
ES5中,生成对象通过构造函数: function A(name,age){ this.name=name; this.age=age } // 在A的prototype属性上定义一个test方法,即 ...
- EntityFramework 6.x和EntityFramework Core插入数据探讨
前言 一直以来对EF和EF Core都是单独分开来写,从未以比较的形式来讲解,如果您既用过EF 6.x也用过用EF Core是否有了解过EF和EF Core在插入数据时和返回主键有何异同呢?本篇博客是 ...
- 开发自己的 chart - 每天5分钟玩转 Docker 容器技术(167)
Kubernetes 给我们提供了大量官方 chart,不过要部署微服务应用,还是需要开发自己的 chart,下面就来实践这个主题. 创建 chart 执行 helm create mychart 的 ...
- 如何查看chrome浏览器已保存的密码
该方法是针对在chrome中已经存储了登陆密码的情况. chrome版本是 66.0.3359.139(正式版本) (64 位),不知道哪天会改了这个bug. 一般来说,我们登陆chrome浏览器已经 ...
- .NET之EntityFramework框架运用
1.创建EF模型库 创建类库-->添加新建项-->选择ADO.NET实体数据模型-->选择 来自数据库的EF选择器-->配置数据库链接以及相应的数据库-->看底部(将ap ...
- 跨域cors方法(jsonp,document.domain,document.name)及iframe性质
这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被 ...
- Linux内核参数调优
用法: vim /etc/sysctl.conf #修改内容 sysctl -p #生效 相关参数仅供参考,具体数值还需要根据机器性能,应用场景等实际情况来做更细微调整. net.core.net ...
- pycharm跨目录调用文件
笔者今天在一个测试qq群中遇到解决跨目录中调用文件的问题.现在来详细说一下. 以下图目录为例: 1.同目录下直接调用掉用.test2.py和test3.py在同一个目录test2下. 所以在test3 ...
- 在腾讯云(windows)上搭建node.js服务器
1:安装Node.js 使用MSI文件,并按照提示安装node.js,默认情况下,安装程序将 Node.js 发行到 C:\Program Files\nodejs. 但这里我们需要修改安装路径到:D ...
- 让站点支持MarkDown语法~(转)
Markdown是一种可以使用普通文本编辑器编写的标记语言,通过类似HTML的标记语法,它可以使普通文本内容具有一定的格式.Markdown的语法简洁明了.学习容易,而且功能比纯文本更强,因此有很多人 ...