eggjs 的单机热部署

4,747 阅读4分钟
原文链接: zhuanlan.zhihu.com

为什么不推荐使用 PM2 管理进程

在使用 eggjs 前,生产环境的应用基本使用 pm2 管理,似乎也没出现过问题,为什么 egg-script 不推荐?

在官方文档里特意在常见问题说明了原因(常见问题 - 为企业级框架和应用而生)抄一下:

  1. PM2 模块本身复杂度很高,出了问题很难排查。我们认为框架使用的工具复杂度不应该过高,而 PM2 自身的复杂度超越了大部分应用本身。
  2. 没法做非常深的优化。
  3. 切实的需求问题,一个进程里跑 leader,其他进程代理到 leader 这种模式(多进程模型),在企业级开发中对于减少远端连接,降低数据通信压力等都是切实的需求。特别当应用规模大到一定程度,这就会是刚需。egg 本身起源于蚂蚁金服和阿里,我们对标的起点就是大规模企业应用的构建,所以要非常全面。这些特性通过 PM2 很难做到

issue 里同样提到

Node.js 本来就无法做到真真完善的热重启。目前由的所谓热重启:
* 重启Worker: 无法控制流量,也无法保证正在处理的请求不终端。
* 删除require.cahe : 这个会造成内存泄露不能用。

虽然官方文档还是提供了 PM2 的使用方式,但一个 web 应用的标准重启方式应该是在前端负载机器(集群)中摘除并完全重启后再重新接收请求进行处理。一个大型依赖复杂的项目使用类 PM2 的进程管理极可能出现问题,eggjs 本身也提供了一个足够稳固的进程管理器 egg-script。但是 egg-script 并没有 reload 功能,应该如何实现?

不使用 PM2 如何实现?

小公司或者个人小项目基本不会使用集群部署,但在一台机器上使用 Nginx 代理 eggjs 应用并配置 upstream 也是实现了负载均衡,也正是这种部署方式使得单机热部署变成可能。Nginx 支持平滑重载配置,所以这里其实是动态修改 upstream 列表使用 egg-script 结束或者重启应用并 reload nginx,做到了没有副作用,但是本质上不应该叫热重启。

步骤如下:

  • nginx 配置定义两个以上作为运行端口
upstream nginxconf {
    server localhost:8001;
    server localhost:8002;
}

server {
    listen 443;
    ssl on;
    ssl_certificate *.fullchain.cer;
    ssl_certificate_key *.key
    server_name vux.li;
    location / {
        proxy_pass http://nginxconf;
        add_header x-upstream $upstream_addr;
    }
}
  • 循环端口列表,作以下处理:
    • nginx upstream 中移除
    • reload nginx,此时流量已经切走
    • 等待 5s(举例),处理可能存在的未完成逻辑(用队列或者定时任务来避免使用 setTimeout setInterval)
    • 使用 egg-script stop 停止当前端口实例
    • 使用 egg-script start 重启当前端口实例,等待 ready(egg-script 最多等待 300s)
    • 重新加入到 nginx 的 upstream 列表
    • reload nginx
上面nginx 配置添加的 header x-upsteam 是为了方便观察当前 upstream 是哪个

如何使用

将上面逻辑写成模块就有了 egg-deploy,目前仅实现了自己小项目的需求,有类似需求可参考,也欢迎来 pr。

傻瓜式使用:

## 安装
yarn add egg-deploy --dev

在目录下添加 nginx.conf,记得在全局 nginx conf 里 include。

package.json 里配置:

{
  "scripts": {
    "deploy": "egg-deploy"
  }
}
yarn deploy

高级配置

在项目目录下添加 .deploy.yml

instances:
  - 
    port: 8001
    title: 8001 # 自定义标题,避免与同机上其他 eggjs 重名
  -
    port: 8002
    title: 8002
startCommand: service nginx start # nginx 启动命令,运行时若 nginx 未运行会尝试执行
reloadCommand: nginx -s reload # nginx reload 命令
nginxConfig: nginx.conf # nginx 配置地址,可以是绝对地址,如果放置于项目下,记得在 nginx 全局配置里 include
waitStopTime: 5000 # 停止前的等待时间

自动部署

结合 circle ci 等 ci 系统的 hook 通知可以实现自动部署,执行 git pull + npm run deploy 就搞定了。

代码实现

目前仅是简陋实现。

github.com/airyland/eg…


其他

扩展一下其实可以替换 PM2 作为相对通用的 Node.js 应用单机部署方案。

动态编辑 nginx 配置的方式简单粗暴,但可能并不太优雅,业界的其他方式这里不做讨论。

知乎编辑器简直太难用了。