eggjs 单机热更新解决方案
后端采用 eggjs 作为框架,由于项目体量小,没有采用集群部署,而是将 nginx 和 eggjs、前端都部署到了一台机器上,由于更新频繁,导致用户经常断线、响应错误,用户体验非常不好,造成程序不稳定的印象。
用户内心 OS:什么垃圾程序员,写的程序经常出问题,还强制退出,烦死了~
因此,不论如何,为了尊严,一定得实现热更新!
百度大法
要解决问题,按照习俗,肯定是先问下百度。
一通查找,发现官方建议使用 SLB 来实现热更新,那我不具备这个条件咋办,然后又发现一个新的思路 eggjs 的单机热部署,但作者已不再维护,因此根据 egg-deploy 思路,自己进行一些优化实现了eggjs 的单机热更新。
解决思路
热更新的总体思路与集群热更新的方式一致,只不是实现方式不一样,核心思想是:
在服务器上启动临时 eggjs 实例,然后通过 nginx 的 reload 将流量切到临时实例后,更新主服务,再将 nginx 切换加主服务上。
按思路,首先要考虑 2 个问题:
- schedule 任务可能在临时实例上执行
- 在关闭服务时,可能还存在未完成的连接,导致用户端响应错误
对于第 1 个问题,可以将 schedule 单独用一个实例去承载。由于是一些定时任务,不会频繁地去更新,即使关闭重启,对用户的使用完全没有影响。
对于第 2 个问题,据 官方回复,eggjs 有做优雅退出,因此该问题不需要进行处理。
完整方案
独立 Schedule
schedule 服务单独使用一个实例来承载。因此,在使用
egg-scripts start
启动时,要向 eggjs
传递启动参数,来区分实例的类型。可以通过下列两个方式来实现:
- 如果是 eggjs3.x 的话,可以在启动时,传递一个
--env
来指定环境变量,从而调用指定的配置文件来初始化 eggjs,这个时候就可以在指定的配置文件中增加配置来表明当前实例的类型 - 在 eggjs2.x 中,则无法修改
--env
,因此只能通过process.argv
的第 3 个参数来进行判断
下面介绍一下在 eggjs2.x 的实例类型识别方法
在 eggjs 实例中,process.argv[2]
是由 egg-scripts
传递的参数,它是一个 json 字符串,我们可以通过其中的 title 或者 port
来区分实例的类型,在 configWillLoad()
钩子函数中将增加实例类型的配置。
最后在定义 schedule 时,根据配置来判断是否启动该 schedule。
代码如下:
1 | // app.js |
1
2
3
4
5
6
7
8
9
10
11
12
13// setStartupEnv 定义
function setStartupEnv(config) {
const startupEnv = JSON.parse(process.argv[2])
// 不覆盖设置
if (config.deploy || config.env === 'local') return
if (startupEnv.port === 7010) {
config.deploy = {
env: 'schedule'
}
}
}
1 | // shedule 定义 |
1 | // scheduleBase.js |
主服务热更新流程
1 | sequenceDiagram |
Nginx 流量切换实现
nginx 中使用 upstream 来进行流量切换。
1 | upstream backend_stream { |
通过修改 upstream 中的服务启用或关闭来进行流量切换,切换后,需要使用
nginx -s reload
来重载配置。
具体的实施代码可以参考:egg-deploy
参考
本文参考以下文章,在此致以诚挚谢意!