热更新部署_基于nodejs线上代码热部署原理与实现

本文介绍了如何在不重启NodeJS服务的情况下,实现线上代码热部署,避免502错误。通过理解require.cache的工作原理,创建loadHandlers、watchHandlers和loadHandler等函数,监听并动态加载文件,实现实时生效的热更新方案。提供的源码可直接运行,适用于需要频繁更新代码的线上环境。
摘要由CSDN通过智能技术生成

导语 一个让你不用重启nodejs线上服务,就能使新更新代码生效的热部署方案

背景

大家都知道,nodejs启的后端服务,如果有代码变动,要重启进程,代码才能生效。

nodejs的进程在重启的时候,用户去访问服务,就会出现短暂的 502 bad gateway

如果你的服务器加上了watch机制

当服务器上的代码频繁发生变动,或者短时间内发生高频变动,那就会一直 502 bad gateway

近段时间在做线上服务编译相关需求的时候,就出现了短时间内线上服务代码高频变动,代码功能模块高频更新,在不能重启服务的情况下,让更新的代码生效的场景。

这就涉及到一个热部署的概念,在不重启服务的情况下,让新部署的代码生效。

接下来我来给大家讲解热部署的原理和实现方案

代码没法实时生效的原因

当我们通过require('xx/xx.js')去加载一个功能模块的时候,node会把require('xx/xx.js')得到的结果缓存在require.cache('xx/xx.js')中

当我们多次调用require('xx/xx.js'),node就不再重新加载,而是直接从require.cache('xx/xx.js')读取缓存

所以当小伙伴在服务器上修改xx/xx.js这个路径下的文件时,node只会去读取缓存,不会去加载小伙伴的最新代码

源码地址和使用

为了实现这个热部署机制,在网上到处查资料,踩了好多坑才弄好

以下代码是提炼出来、完整可运行的热部署基础原理代码,大家可以基于这个代码去自行拓展:smart-node-reload

注意最新版本12版本的node运行会报错,官方对require.cache做了调整,已经上报问题给官方,建议使用nodejs版本:v10.5.0

git clone下来以后,无需安装,直接运行

npm start

这时候就开启了热部署变动监听

如何看到效果呢

小伙伴请看/hots/hot.js文件

    const hot = 1    module.exports = hot

将第一行代码改为const hot = 111

    const hot = 111    module.exports = hot

这时候就能看到终端里监听到代码变动,然后动态加载你的最新代码并得到执行结果,输出为:

    热部署文件: hot.js ,执行结果: { 'hot.js': 111 }

热部署服务监听到代码变动,并重新加载了代码,小伙伴就可以实时拿到最新代码的执行结果了,整个过程都在线上环境运行,node进程也没有重启

源码解析

loadHandlers主函数
const handlerMap = {};// 缓存const hotsPath = path.join(__dirname, "hots");// 加载文件代码 并 监听指定文件夹目录文件内容变动const loadHandlers = async () => {  // 遍历出指定文件夹下的所有文件  const files = await new Promise((resolve, reject) => {    fs.readdir(hotsPath, (err, files) => {      if (err) {        reject(err);      } else {        resolve(files);      }    });  });  // 初始化加载所有文件 把每个文件结果缓存到handlerMap变量当中  for (let f in files) {    handlerMap[files[f]] = await loadHandler(path.join(hotsPath, files[f]));  }  // 监听指定文件夹的文件内容变动  await watchHandlers();};

loadHandlers是整个热部署服务的主函数,我们指定了服务器根目录下的hots文件夹是用来监听变动和热部署的文件夹

用fs.readdir扫描hots文件夹下的所有文件,通过loadHandler方法去加载和运行每一个扫描到的文件,将结果缓存到handlerMap里

然后用watchHandlers方法开启文件变动监听

watchHandlers监听文件变动
// 监视指定文件夹下的文件变动const watchHandlers = async () => {  // 这里建议用chokidar的npm包代替文件夹监听  fs.watch(hotsPath, { recursive: true }, async (eventType, filename) => {    // 获取到每个文件的绝对路径    // 包一层require.resolve的原因,拼接好路径以后,它会主动去帮你判断这个路径下的文件是否存在    const targetFile = require.resolve(path.join(hotsPath, filename));    // 当你适应require加载一个模块后,模块的数据就会缓存到require.cache中,下次再加载相同模块,就会直接走require.cache    // 所以我们热加载部署,首要做的就是清除require.cache中对应文件的缓存    const cacheModule = require.cache[targetFile];    // 去除掉在require.cache缓存中parent对当前模块的引用,否则会引起内存泄露,具体解释可以看下面的文章//《记录一次由一行代码引发的“血案”》https://cnodejs.org/topic/5aaba2dc19b2e3db18959e63//《一行 delete require.cache 引发的内存泄漏血案》https://zhuanlan.zhihu.com/p/34702356    if (cacheModule.parent) {        cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);    }    // 清除指定路径对应模块的require.cache缓存    require.cache[targetFile] = null;    // 重新加载发生变动后的模块文件,实现热加载部署效果,并将重新加载后的结果,更新到handlerMap变量当中    const code = await loadHandler(targetFile)    handlerMap[filename] = code;    console.log("热部署文件:", filename, ",执行结果:", handlerMap);  });};

watchHandlers函数是用来监听指定文件夹下的文件变动、清理缓存更新缓存用的。

用fs.watch原生函数监听hots文件夹下文件变动,当文件发生变动,就算出文件的绝对路径targetFile

而require.cache[targetFile]就是require对targetFile原文件的缓存,清除缓存用require.cache[targetFile] = null;

坑爹的地方来了,仅仅只是将缓存置为null,会发生内存泄露,我们还需要清除缓存父级的引用require.cache[targetFile].parent,就是下面这段代码

    if (cacheModule.parent) {        cacheModule.parent.children.splice(cacheModule.parent.children.indexOf(cacheModule), 1);    }
loadHandler加载文件
// 加载指定文件的代码const loadHandler = filename => {  return new Promise((resolve, reject) => {    fs.readFile(filename, (err, data) => {      if (err) {        resolve(null);      } else {        try {          // 使用vm模块的Script方法来预编译发生变化后的文件代码,检查语法错误,提前发现是否存在语法错误等报错          new vm.Script(data);        } catch (e) {          // 语法错误,编译失败          reject(e);          return;        }        // 编译通过后,重新require加载最新的代码        resolve(require(filename));      }    });  });};

loadHandler函数的作用是加载指定文件,并校验新文件代码语法等。

通过fs.readFile读取文件内容

用node原生vm模块vm.Script方法去预编译发生变化后的文件代码,检查语法错误,提前发现是否存在语法错误等报错

检验通过后,通过resolve(require(filename))方法重新将文件require加载,并自动加入到require.cache缓存中

结尾:

这个代码是我经过极简后的代码,方便大家阅读和理解,感兴趣的小伙伴可以通过这个代码去进行深一步拓展,可以私信或者评论一起聊天哦

814b9e7ce96124304ca86ae2f6a5a58c.gif


作者:腾讯IVWEB团队
链接:https://juejin.im/post/5e4e9c6451882549331ce8d4
来源:掘金

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Node.js是一种基于JavaScript的运行时环境,它让我们能够使用JavaScript编写服务器端的代码。在部署和发布Node.js应用程序时,有几个关键步骤需要注意。 首先,我们需要选择一个合适的云服务提供商或虚拟专用服务器(VPS)来部署我们的Node.js应用程序。一些常见的云服务提供商包括Amazon Web Services(AWS)、Microsoft Azure、Google Cloud等。我们也可以选择使用容器化技术,如Docker,来进行部署。 接下来,我们需要将我们的应用程序代码上传到选择的服务器或云服务提供商中。我们可以使用SSH(Secure Shell)协议来远程连接服务器,并通过Git或FTP将代码上传到服务器上。另外,如果我们使用的是云服务提供商,他们通常会提供一些命令行工具或Web界面来简化代码上传的过程。 一旦我们的代码上传到服务器上,我们就需要安装并配置Node.js环境。我们可以使用包管理器,如npm(Node.js Package Manager)或Yarn来安装我们应用程序所需的依赖包。通过运行"npm install"或"yarn install"命令,我们可以自动安装所需的包。然后,我们可以使用pm2(一个Node.js应用程序进程管理器)来管理我们的应用程序的进程和日志。 最后,我们需要配置和启动我们的Node.js应用程序。我们可以创建一个配置文件,例如使用JSON或YAML格式的文件,来指定应用程序的环境变量、端口号和其他相关设置。然后,我们可以使用命令行工具或脚本来启动我们的应用程序。一些常见的命令包括"node app.js"或"pm2 start app.js"等。 总之,Node.js线上服务器部署和发布涉及选择合适的云服务提供商或VPS、上传代码、安装和配置Node.js环境、启动和管理应用程序。通过正确的配置和管理,我们可以确保我们的应用程序能够在线上环境中稳定运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值