DingMing

丁大铭的个人空间,用来分享一些前端小技巧,默默成长吧,哈哈

使用PM2的GracefulReload无停机更新

  |  
 阅读次数

这段时间思考了一个问题,怎么用pm2实现平滑更新(即更新不会影响影响线上服务)?

起因

最近的项目上遇到一个问题,做功能迭代就会发现一个很麻烦的事情,每次部署 node 服务需要 查看用户是否有网络请求,在部署停服的过程中,请求过来的时候导致500的发生。

发展

通过查阅了解到了 PM2 的 GracefulReload,而且在实际项目中也有使用,但是基本都是以连接断开为标记,这次就顺便研究了一下怎么样更优雅的实现无停机更新。

介绍

何谓平滑更新?我自己的定义是在更新时不需要停止服务,不会影响现有连接,在前端无感知的情况下完成新版本的发布。我们这次讨论的平滑更新区别于java等语言里的热更新,他们的热更新是通过在运行的过程中重新加载新代码实现的,整个过程服务器不需要重启。而我们讨论的是服务器需要重启的情况。请看官们区别对待。

大家都知道pm2中对重启服务的命令有三种,分别是“restart”、“reload”、“gracefulReload”。
备注:往下我们所有实验都是建立在pm2使用cluster模式的前提下,fork模式不适用。具体原因又是另外一个故事。

restart 是直接将server kill掉然后重新启动一个server。这其中在重新的这一段时间内,server是有一个空窗期的。这个时间内所有的request都会被内核拒绝,因为根本没有它们需要找的server存在。

reload 命令在pm2官网的解释是0秒重启,说它是在一个个重启“With reload, pm2 restarts all processes one by one, always keeping at least one process running”

(原文连接:https://pm2.io/doc/en/runtime/guide/load-balancing/?utm_source=pm2&utm_medium=website&utm_campaign=rebranding)

此处,按照pm2官方的解释及我的个人实验观察。reload命令会先创建一个server,然后将原server kill掉,此时原server上的正在执行的request因为进程被关闭而失败。而postman后续的request进来时,因为新server已经启动了

gracefulReload

此时你就该使用gracefulReload了,这个命令我就不多做赘述了,使用它,你可以在你的进程里监听SIGINT事件,然后调用server.close函数,设置server不再接收新的连接。而等待现有的连接全部关闭后出发server.close事件。我们这这个事件中执行process.exit()退出进程。这样用户的请求就不会因为进程被kill而失败了。

直接代码说话:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const server = http.createServer(app);

server.listen(3000, () => {
console.log('Express is listening on 3000');
});

process.on('SIGINT', () => {

const cleanUp = () => {
mysql.end(console.error);
redis.end();
};

server.close(() => {
// Stop after 10 secs
setTimeout(() => {
cleanUp();
process.exit();
}, 10000);
});

// Force close server after 15 secs
setTimeout((e) => {
console.log('Forcing server close !!!', e);
cleanUp();
process.exit(1);
}, 15000);

});

这样添加了对 mysql 和 redis 资源的释放,同时在连接断开后等待 10 秒,让异步的请求执行完(requert 上的超时为 5s,所有 10s 应该是足够了)。

1
然后 pm2.json 需要添加 →  "kill_timeout": 15000   或者 pm2 start app.js --kill-timeout 15000

经过测试,这样的配置运行良好,从日志可以看出来在执行了 startOrGracefulReload 之后,PM2 会马上启动一个新的进程处理新进的请求,同时等待原有的进程停止后删除退出。至此,应该算是大功告成了。