Принцип и реализация горячего развертывания онлайн-кода на основе nodejs

Node.js JavaScript

Автор статьи: первый головастик

Введение Решение для горячего развертывания, которое позволяет вам заставить новый обновленный код вступить в силу без перезапуска онлайн-сервиса nodejs.

задний план

Все мы знаем, что серверная служба запускается nodejs, и в случае изменения кода процесс необходимо перезапустить, прежде чем код вступит в силу.

Когда процесс nodejs будет перезапущен, будет короткий период времени, когда пользователь получит доступ к сервису.502 bad gateway

Если ваш сервер добавляет механизм наблюдения

Когда код на сервере часто меняется или часто меняется за короткий промежуток времени, он всегда будет502 bad gateway

В последнее время при выполнении требований, связанных с компиляцией онлайн-сервиса, за короткий период времени часто происходили изменения кода онлайн-сервиса, а функциональные модули кода часто обновлялись, и обновленный код может вступить в силу, когда сервис не может быть перезапущен.

Это включает в себя концепцию горячего развертывания, когда вновь развернутый код вступает в силу без перезапуска службы.

Далее я объясню вам принцип и план реализации горячего развертывания.

Причина, по которой код не действует в режиме реального времени

когда мы проходимrequire('xx/xx.js')При загрузке функционального модуля узел будетrequire('xx/xx.js')Полученный результат кэшируется вrequire.cache('xx/xx.js')середина

когда мы звоним несколько разrequire('xx/xx.js'), нода уже не перезагружается, а напрямую изrequire.cache('xx/xx.js')чтение кеша

Итак, когда друг модифицирует на сервереxx/xx.jsКогда используется файл по этому пути, узел будет только читать кеш и не будет загружать последний код малого партнера.

Исходный адрес и использование

Чтобы реализовать этот механизм горячего развертывания, я искал информацию повсюду в Интернете и преодолел множество ям, чтобы сделать это правильно.

Следующий код представляет собой извлеченный, полный и работоспособный базовый код горячего развертывания. Вы можете самостоятельно расширить его на основе этого кода:smart-node-reload

Обратите внимание, что последняя версия узла 12 сообщит об ошибке.Официал скорректировал 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 }

Служба горячего развертывания отслеживает изменения кода и перезагружает код, а небольшие партнеры могут получать результаты выполнения последнего кода в режиме реального времени Весь процесс выполняется в онлайн-среде, и процесс узла не перезапускается.

Анализ исходного кода

основная функция 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;

Здесь возникает ловушка, просто установите кеш в ноль, будет утечка памяти, нам также нужно очистить ссылку родителя кеша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

Используйте метод vm.Script собственного модуля vm узла, чтобы предварительно скомпилировать измененный код файла, проверить синтаксические ошибки и заранее выяснить, есть ли синтаксические ошибки и другие ошибки.

После прохождения теста перезагрузите требуемый файл с помощью метода resolve(require(filename)) и автоматически добавьте его в кэш require.cache.

конец:

Выше приведено все содержание горячего развертывания.Кодовой адрес:smart-node-reload

Этот код мой упрощенный код, который удобно читать и понимать всем.Заинтересованные друзья могут использовать этот код для дальнейшего расширения