使用 Node.js 搭建 HTTP Server 已是司空见惯的事。在生产环境中,Node 进程平滑重启直接关系到服务的可靠性,它的重要性不容我们忽视。既然是平滑重启,就涉及到新旧进程的接替过渡:

  • 首先,保证新进程平滑入场
  • 其次,保证旧进程平滑离场

本文主要谈论下,在新旧进程接替过渡期间,如何保证旧进程平滑离场。那怎样的离场才算平滑的呢?

如何定义平滑离场

以进程离场作为时间分割点,我们可以把请求分为两类:增量请求存量请求

  • 在进程离场前,停止接收新的(增量)请求
  • 在进程离场前,保证未完成的(存量)请求正常响应

所以,达成以上两个目标,基本上我们就认为进程的离场是平滑的。在谈如何做到进程平滑离场前,我们需要一种机制,这种机制能让我们主动通知进程何时离场,这就涉及到进程间通信(IPC)的知识了,我们先简单了解下。

进程间通信

对 Unix 或类 Unix 系统而言,进程间通信的方式有很多种 —— 信号(Signal)是其中的一种。

信号的种类有很多,如 SIGINT、 SIGTERM SIGKILL 等。这些信号视具体需要用于不同的场景,比如 SIGKILL 一般用于强杀进程。

我们可以在命令行执行 kill -l 查看所有的信号,如下所示(其中的数字表示 signal number):

$ kill -l
 1) SIGHUP   2) SIGINT   3) SIGQUIT   4) SIGILL
 5) SIGTRAP   6) SIGABRT   7) SIGEMT   8) SIGFPE
 9) SIGKILL  10) SIGBUS  11) SIGSEGV  12) SIGSYS
13) SIGPIPE  14) SIGALRM  15) SIGTERM  16) SIGURG
17) SIGSTOP  18) SIGTSTP  19) SIGCONT  20) SIGCHLD
21) SIGTTIN  22) SIGTTOU  23) SIGIO  24) SIGXCPU
25) SIGXFSZ  26) SIGVTALRM  27) SIGPROF  28) SIGWINCH
29) SIGINFO  30) SIGUSR1  31) SIGUSR2

我们可以使用 kill 命令向进程发送指定信号:

# 发送 SIGTERM 信号(默认,无须指定信号类型)给进程
$ kill <pid>

# 发送 SIGINT 信号给进程,其中 <pid> 为具体的进程 ID
$ kill -INT <pid>

# 发送 SIGKILL 信号给进程
$ kill -KILL <pid>

# 或者
$ kill -9 <pid>

进程可以对接收到的信号作出回应。对 Node 应用而言,信号是被当作事件发送给 Node 进程的,进程接收到 SIGTERM 及 SIGINT 事件有默认回调,官方文档是这么描述的:

'SIGTERM' and 'SIGINT' have default handlers on non-Windows platforms that reset the terminal mode before exiting with code 128 signal number. If one of these signals has a listener installed, its default behavior will be removed (Node.js will no longer exit).

这句话写的很抽象,它是什么意思呢?我们以一个简单的 Node 应用为例。

新建文件,键入如下代码,将其保存为 server.js:

const http = require('http');

const server = http.createServer((req, res) => {
 setTimeout(() => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('It works');
 }, 5000);
});

server.listen(9420);

这里为了方便测试,对应用接收到的每个 http 请求,等待 5 秒后再进行响应。
执行 node server.js 启动应用。为了给应用发送信号,我们需要获取应用的进程 ID,我们可以使用 lsof 命令查看:

$ lsof -i TCP:9420
COMMAND  PID    USER  FD  TYPE       DEVICE SIZE/OFF NODE NAME
node  70826 myunlessor  13u IPv6 0xd250033eef8912eb   0t0 TCP *:9420 (LISTEN)

事实上,我们也可以在代码里通过 console.log(process.pid) 获取进程 ID。这里只是顺便介绍一种,在知道监听 TCP 端口的情况获取进程的方式。

随后,我们发起一个请求,在收到响应之前(有 5 秒等待时间),我们给应用发送 SIGINT 信号。

$ curl http://localhost:9420 &

$ kill -INT 70826
curl: (52) Empty reply from server
[1]  Exit 52         curl http://localhost:9420

可以看到,请求没能正常收到响应。也就是说,默认情况下,Node 应用在接收到 SIGINT 信号时,会马上把进程杀死,无视进程还没处理完成的请求。所幸的是,我们可以手动监听进程的 SIGINT 事件,像这样:

process.on('SIGINT', () => {
 // do something here
});

如果我们在事件回调里什么都不做,就意味着忽略该信号,进程该干嘛干嘛,像什么事情都没发生一样。

那么,如果我手动监听 SIGKILL 会如何呢?对不起,SIGKILL 是不能被监听的,官方文档如是说:

'SIGKILL' cannot have a listener installed, it will unconditionally terminate Node.js on all platforms.

这是合情合理的,要知道 SIGKILL 是用于强杀进程的,你无法干预它的行为。

回到上面的问题,我们可以近似地理解为 Node 应用响应 SIGINT 事件的默认回调是这样子的:

process.on('SIGINT', () => {
 process.exit(128   2/* signal number */);
});

我们可以打印 exit code 来验证:

$ node server.js

$ echo $?
130

有了信号,我们就能主动通知进程何时离场了,下面谈一谈进程如何平滑离场。

如何让进程平滑离场

我们在上面示例基础上,也就是在文件 server.js 中,补充如下代码:

process.on('SIGINT', () => {
 server.close(err => {
  process.exit(err ? 1 : 0);
 });
});

这段代码很简单,我们改写应用接收到 SIGINT 事件的默认行为,不再简单粗暴直接杀死进程,而是在 server.close 方法回调中再调用 process.exit 方法,接着继续试验一下。

$ lsof -i TCP:9420
COMMAND  PID    USER  FD  TYPE       DEVICE SIZE/OFF NODE NAME
node  75842 myunlessor  13u IPv6 0xd250033ec7c9362b   0t0 TCP *:9420 (LISTEN)

$ curl http://localhost:9420 &
[1] 75878

$ kill -2 75842

$ It works
[1]  Done          curl http://localhost:9420

可以看到,应用在退出前(即进程离场前),成功地响应了存量请求。

我们还可以验证,进程离场前,确实不再接收增量请求:

$ curl http://127.0.0.1:9420
curl: (7) Failed to connect to 127.0.0.1 port 9420: Connection refused

这正是 server.close 所做的事,进程平滑离场就是这么简单,官方文档是这么描述这个 API 的:

Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. Unlike that event, it will be called with an Error as its only argument if the server was not open when it was closed.

结束语

进程平滑离场只是 Node 进程平滑重启的一部分。生产环境中,新旧进程的接替涉及进程负载均衡、进程生命周期管理等方方面面的考虑。专业的工具做专业的事,PM2 就是 Node 进程管理很好的选择。

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

Node.js 进程平滑离场剖析小结的更多相关文章

  1. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  3. Node.js调试技术总结分享

    Node.js是一个可以快速构建网络服务及应用的平台。该平台的构建是基于Chrome's JavaScript runtime,也就是说,实际上它是对Google V8引擎(应用于Google Chrome浏览器)进行了封装。 今天介绍Node.js调式目前有几种技术,需要的朋友可以参考下。

  4. node.js实现http服务器与浏览器之间的内容缓存操作示例

    这篇文章主要介绍了node.js实现http服务器与浏览器之间的内容缓存操作,结合实例形式分析了node.js http服务器与浏览器之间的内容缓存原理与具体实现技巧,需要的朋友可以参考下

  5. 教你如何使用node.js制作代理服务器

    本文介绍了如何使用node.js制作代理服务器,图文并茂,十分的详细,代码很简洁易懂,这里推荐给大家。

  6. Python语法学习之进程间的通信方式

    进程在创建之后是没有办法获取返回值的,但有的时候两个进程之间需要进行相互之间的配合才能完成工作,这就需要通信的帮助。本文主要介绍了Python中进程间的通信方式,需要的可以了解一下

  7. node.js中的fs.openSync方法使用说明

    这篇文章主要介绍了node.js中的fs.openSync方法使用说明,本文介绍了fs.openSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

  8. Node.js+ELK日志规范的实现

    这篇文章主要介绍了Node.js+ELK日志规范的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  9. node.js爬虫框架node-crawler初体验

    这篇文章主要介绍了node.js爬虫框架node-crawler的相关资料,帮助大家利用node.js进行爬虫,感兴趣的朋友可以了解下

  10. node.js中的fs.existsSync方法使用说明

    这篇文章主要介绍了node.js中的fs.existsSync方法使用说明,本文介绍了fs.existsSync方法说明、语法、接收参数、使用实例和实现源码,需要的朋友可以参考下

随机推荐

  1. Error: Cannot find module ‘node:util‘问题解决

    控制台 安装 Vue-Cli 最后一步出现 Error: Cannot find module 'node:util' 问题解决方案1.问题C:\Windows\System32>cnpm install -g @vue/cli@4.0.3internal/modules/cjs/loader.js:638 throw err; &nbs

  2. yarn的安装和使用(全网最详细)

    一、yarn的简介:Yarn是facebook发布的一款取代npm的包管理工具。二、yarn的特点:速度超快。Yarn 缓存了每个下载过的包,所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率,因此安装速度更快。超级安全。在执行代码之前,Yarn 会通过算法校验每个安装包的完整性。超级可靠。使用详细、简洁的锁文件格式和明确的安装算法,Yarn 能够保证在不同系统上无差异的工作。三、y

  3. 前端环境 本机可切换node多版本 问题源头是node使用的高版本

    前言投降投降 重头再来 重装环境 也就分分钟的事 偏要折腾 这下好了1天了 还没折腾出来问题的源头是node 使用的高版本 方案那就用 本机可切换多版本最终问题是因为nodejs的版本太高,导致的node-sass不兼容问题,我的node是v16.14.0的版本,项目中用了"node-sass": "^4.7.2"版本,无法匹配当前的node版本根据文章的提

  4. nodejs模块学习之connect解析

    这篇文章主要介绍了nodejs模块学习之connect解析,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. nodejs npm package.json中文文档

    这篇文章主要介绍了nodejs npm package.json中文文档,本文档中描述的很多行为都受npm-config(7)的影响,需要的朋友可以参考下

  6. 详解koa2学习中使用 async 、await、promise解决异步的问题

    这篇文章主要介绍了详解koa2学习中使用 async 、await、promise解决异步的问题,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. Node.js编写爬虫的基本思路及抓取百度图片的实例分享

    这篇文章主要介绍了Node.js编写爬虫的基本思路及抓取百度图片的实例分享,其中作者提到了需要特别注意GBK转码的转码问题,需要的朋友可以参考下

  8. CentOS 8.2服务器上安装最新版Node.js的方法

    这篇文章主要介绍了CentOS 8.2服务器上安装最新版Node.js的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. node.js三个步骤实现一个服务器及Express包使用

    这篇文章主要介绍了node.js三个步骤实现一个服务器及Express包使用,文章通过新建一个文件展开全文内容,具有一定的参考价值,需要的小伙伴可以参考一下

  10. node下使用UglifyJS压缩合并JS文件的方法

    下面小编就为大家分享一篇node下使用UglifyJS压缩合并JS文件的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

返回
顶部