张超:又拍云系统开发高级工程师,负责又拍云 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 页面: http://link.zhihu.com/?target=https%3A//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 变量和变量插值


快 来 找 又 小 拍



推 荐 阅 读


关于技术

第三届万物生长大会在杭举办,又拍云再次跻身“准独角兽”行列

这样介绍 CDN,老司机也能听懂

聊聊常见的网络攻击

5G 网络与 4G 相比,有什么区别?


•好看的人都「在看」啦  

nginx 超时时间_我眼中的 Nginx(四):是什么让你的 Nginx 服务退出这么慢?相关推荐

  1. nginx 超时时间_Linux从入门到放弃 Nginx

    1.nginx概念 nginx是一个高性能http和反向代理web服务器. 占有内存少.并发能力强. 最高支持50000并发数. 1.正向代理 在客户端(浏览器)配置代理服务器.通过代理服务器进行互联 ...

  2. python给函数设置超时时间_在 Linux/Mac 下为Python函数添加超时时间的方法

    我们在使用 requests 这类网络请求第三方库时,可以看到它有一个参数叫做 timeout ,就是指在网络请求发出开始计算,如果超过 timeout 还没有收到返回,就抛出超时异常.(当然存在特殊 ...

  3. python给函数设置超时时间_在 Linux/Mac 下为Python函数添加超时时间

    我们在使用 requests 这类网络请求第三方库时,可以看到它有一个参数叫做timeout,就是指在网络请求发出开始计算,如果超过 timeout 还没有收到返回,就抛出超时异常.(当然存在特殊情况 ...

  4. ping 超时时间_华为交换机ping命令详解

    1.Ping简介 Ping是排除设备访问故障的常见方法.它使用Internet控制消息协议ICMP(Internet Control Message Protocol)确定以下内容: 远程设备是否处于 ...

  5. 同时设置超时时间_刚入职的小菜鸡,设错了RPC超时,搞了个线上事故

    上面这张监控图,对于服务端的研发同学来说再熟悉不过了.在日常的系统维护中,『服务超时』应该属于监控报警最多的一类问题. 尤其在微服务架构下,一次请求可能要经过一条很长的链路,跨多个服务调用后才能返回结 ...

  6. qt连接错误ip的sqlserver超时时间_参数设置导致请求超时案例

    问题现象 晚上版本上线后,发现工号进入首页后页面空白,显示INTERNAL_SERVER_ERROR 过程回顾 通过fiddler抓包,发现某个请求出现500错误 检查应用,was,ihs日志,没有发 ...

  7. httpclient 设置超时时间_面试官:技术选型,HttpClient还是OkHttp?

    你知道的越多,不知道的就越多,业余的像一棵小草! 你来,我们一起精进!你不来,我和你的竞争对手一起精进! 编辑:业余草 来源:juejin.im/post/6844904040644476941 推荐 ...

  8. mongodb spring 超时时间_拥抱NoSQL--谈Spring Data MongoDB的简单使用

    先说说MongoDB是什么. MongoDB官网也有各语言所用的API,当然也有Java的了.但是平日写的东西大部分是基于Spring框架的,自然希望对于数据库的操作也在Spring的掌控之下.前几日 ...

  9. mysql 事务 超时时间_设置事务超时时间的问题及数据库update和锁

    Oracle的update语句问题: update config t set t.value =1 where t.key='DB_ KEY' 或者: select * from config t w ...

最新文章

  1. VS Code插件之Cordova Tools
  2. Cortex-M3 内部寄存器
  3. stm32f105使用12M外部晶振
  4. x86服务器中网络性能分析与调优(高并发、大流量网卡调优)
  5. SOCK_DGRAM(数据报套接字)与SOCK_STREAM(流套接口)的区别
  6. 基于Java的TCP Socket通信详解(计算机端/Android手机端)
  7. Struts2中UI标签之非表单标签
  8. iOS如何检测app从后台调回前台
  9. Kali Linux 网络扫描秘籍 第七章 Web 应用扫描(三)
  10. SQL SERCER 控制 SERVERICE BROKER 服务
  11. 软考网络工程师必过教程---必看
  12. 【Modern Robotics】 Mechanics, Planning and Control现代机器人学:机构、规划与控制
  13. lbp2900打印机linux驱动下载,lbp2900打印机驱动下载
  14. android文献翻译,Android外文文献翻译
  15. 马哥2018linux课程目录,马哥教育2018年Linux第30期视频教程
  16. vue项目中,使用mint-ui的picker实现省市区三级联动,并且编辑时选中默认值(封装成独立的组件)
  17. 第一次登陆阿里云ECS云服务器及其配置
  18. 复习vue+前后端分离开发
  19. 在rails中pluck和select和collect区别
  20. C语言小项目实践——日历程序

热门文章

  1. CefSharp For WPF响应页面点击事件
  2. 阿里巴巴成立首个IoT生态联盟 将打通技术标准
  3. hdu 4923 Room and Moor (单调栈+思维)
  4. 思科谈OpenDaylight
  5. java.lang.InstantiationException
  6. saltstack模块 --cp
  7. jquery and event
  8. java中c/s模式传送数据
  9. 找工作是一种必须的生活阅历
  10. 合并多个Word文档