这段时间思考了一个问题,怎么用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”
此处,按照pm2官方的解释及我的个人实验观察。reload命令会先创建一个server,然后将原server kill掉,此时原server上的正在执行的request因为进程被关闭而失败。而postman后续的request进来时,因为新server已经启动了
gracefulReload
此时你就该使用gracefulReload了,这个命令我就不多做赘述了,使用它,你可以在你的进程里监听SIGINT事件,然后调用server.close函数,设置server不再接收新的连接。而等待现有的连接全部关闭后出发server.close事件。我们这这个事件中执行process.exit()退出进程。这样用户的请求就不会因为进程被kill而失败了。
直接代码说话:
1 | const server = http.createServer(app); |
这样添加了对 mysql 和 redis 资源的释放,同时在连接断开后等待 10 秒,让异步的请求执行完(requert 上的超时为 5s,所有 10s 应该是足够了)。
1 | 然后 pm2.json 需要添加 → "kill_timeout": 15000 或者 pm2 start app.js --kill-timeout 15000 |
经过测试,这样的配置运行良好,从日志可以看出来在执行了 startOrGracefulReload 之后,PM2 会马上启动一个新的进程处理新进的请求,同时等待原有的进程停止后删除退出。至此,应该算是大功告成了。