首页 > 解决方案 > 为什么 Google App 引擎不使用 Node.js 释放内存

问题描述

所以这篇文章是一个两部分的问题。我正在使用实例类 F4 测试 Google 应用引擎(内存:1024 MB 和 CPU 2.4GHz,具有自动缩放功能)。这是在 App 引擎上找到的配置文件:

runtime: nodejs10
env: standard
instance_class: F4
handlers:
  - url: .*
    script: auto
automatic_scaling:
  min_idle_instances: automatic
  max_idle_instances: automatic
  min_pending_latency: automatic
  max_pending_latency: automatic
network: {}

我的 Nodejs 服务器有正常的 Post 和 Get 路由,除了提供静态文件和从 MongoDB 获取数据之外,这些路由不会繁重。这是使用这些路由时的内存使用图:

图 1.Graph 显示了从这些非重路由获取时在 Google StackDriver 上看到的内存使用情况 图 1. 显示从这些非重路由导航时在 Google StackDriver 上看到的内存使用情况的图表

我的第一个问题是,为什么这么小的任务需要这么多内存(大约 500mb)?

我正在使用 Chrome Devtools Node.js 内存分析器在我的 Windows 10 机器上运行分析测试,它提供的数据远不及这些数字,在获取堆快照时我得到了大约 52MB。

我的第二个问题是关于一个特定的路线,该路线采用上传的文件并使用sharp将其调整为3个不同的图像。

尽管在我的 Windows 10 机器上按预期工作,但为什么调整大小完成后内存没有被释放?

我正在对10mb图像进行大小调整测试,该图像被转换为​​缓冲区然后存储在内存中。转换代码如下:

const resizeAndSave = (buffer, filePath, widthResize, transform) => {
  const { width, height, x, y } = transform;
  //create sharp instance
  const image = sharp(buffer);
  //prepare to upload to Cloud storage
  const file = publicBucket.file(filePath);
  return new Promise((resolve, reject) => {
    image
      .metadata()
      .then(metadata => {
        //do image operations
        const img = image
          .extract({
            width: isEmpty(transform) ? metadata.width : parseInt(width),
            height: isEmpty(transform) ? metadata.height : parseInt(height),
            left: isEmpty(transform) ? 0 : parseInt(x),
            top: isEmpty(transform) ? 0 : parseInt(y)
          })
          .resize(
            metadata.width > widthResize
              ? {
                  width: widthResize || metadata.width,
                  withoutEnlargement: true
                }
              : metadata.height > widthResize
              ? {
                  height: widthResize || metadata.height,
                  withoutEnlargement: true
                }
              : {
                  width: widthResize || metadata.width,
                  withoutEnlargement: true
                }
          )
          .jpeg({
            quality: 40
          });
        //pipe to cloud storage and resolve filepath when done
        img
          .pipe(file.createWriteStream({ gzip: true }))
          .on("error", function(err) {
            reject(err);
          })
          .on("finish", function() {
            // The file upload is complete.
            resolve(filePath);
          });
      })
      .catch(err => {
        reject(err);
      });
  });
};

这个函数每张图片会被串联调用3次,为了测试,没有使用Promise.all来阻止它们并行运行:

async function createAvatar(identifier, buffer, transform, done) {
  const dir = `uploads/${identifier}/avatar`;
  const imageId = nanoid_(20);
  const thumbName = `thumb_${imageId}.jpeg`;
  await resizeAndSave(buffer, `${dir}/${thumbName}`, 100, transform);
  const mediumName = `medium_${imageId}.jpeg`;
  await resizeAndSave(buffer, `${dir}/${mediumName}`, 400, transform);
  const originalName = `original_${imageId}.jpeg`;
  await resizeAndSave(buffer, `${dir}/${originalName}`, 1080, {});
  done(null, {
    thumbUrl: `https://bucket.storage.googleapis.com/${dir_}/${thumbName}`,
    mediumUrl: `https://bucket.storage.googleapis.com/${dir_}/${mediumName}`,
    originalUrl: `https://bucket.storage.googleapis.com/${dir_}/${originalName}`
  });
  /* Promise.all([thumb])
    .then(values => {

      done(null, {
        thumbUrl: `https://bucket.storage.googleapis.com/${dir_}/${thumbName}`,
        mediumUrl: `https://bucket.storage.googleapis.com/${dir_}/${mediumName}`,
        originalUrl: `https://bucket.storage.googleapis.com/${dir_}/${originalName}`
      });
    })
    .catch(err => {

      done(err, null);
    }); */
}

在我的 Window 10 机器上运行服务器时的堆快照是:

图 2.Node.js 的 chrome devtools 的堆快照

图 2.当导航到图像大小调整路线一次时,来自 Chrome devtools for Node.js 的堆快照

这些堆快照清楚地表明,用于在内存中存储 10mb 图像并调整其大小的内存正在返回到我机器上的操作系统。

StackDriver 上报告的内存使用情况是: 图 3. Google StackDriver 上导航到图像调整大小路线一次时的内存使用情况图 3. 使用图像大小调整路由时 Google StackDriver 的内存使用情况

这清楚地表明,操作完成时内存没有被释放,而且非常高,从晚上 8 点左右开始,它上升到 800mb 并且从未下降。

我也尝试了 Stackdriver 分析器,但它没有显示任何高内存使用情况,但实际上,它显示大约 55mb,这接近我的 Windows 机器:

Fig 4.StackDriver profiler heap shot 图 4.StackDriver profiler 堆快照

因此,如果我的分析是正确的,我假设它与运行应用引擎中的实例的操作系统有关?我没有任何线索。

更新:这是在使用图像处理路线并且一个小时未接触应用后从 Stackdriver 获得的最新内存使用情况:

图 5. 调整大小后让应用闲置一小时时的内存使用情况 图 5. 导航到图像大小调整路径后,让应用程序空闲一个小时时的内存使用情况

更新 2:根据 Travis 的建议,我在导航到路由时查看了进程,发现内存使用率略高于堆,但与应用引擎显示的相差甚远: 图 6.Windows 10 处理 Nodejs 内存而图像processign 正在运行图 6.Windows 10 Nodejs 进程内存

更新 3:在与图 5 相同的时间间隔内使用的实例数(内存使用率很高): 图 7.在与图 5 相同的时间间隔内使用的实例数在与图 5 相同的时间间隔内使用的实例数

更新 4:所以我尝试切换到实例类 F1 (256 MB 600 MHz) 看看会发生什么。结果显示空闲时内存使用量减少,但当我处理 10mb 图像时,应用程序引擎会发送一条警告说升级内存。(它显示了 2 个正在运行的实例)。 图 8.F1 实例类,应用空闲时内存为 256MB具有 256MB 内存的 F1 实例类

这使我认为这些实例无论如何都试图占用大部分可用的内存。

标签: javascriptnode.jsgoogle-app-enginegoogle-cloud-platformmemory-leaks

解决方案


因此,作为上述评论和您上次更新(更新 4)的结果,我们可以意识到:

  • Stackdriver Profiler 指标,仅显示堆内存的平均内存使用情况
  • 和 GCP 指标,它展示了总内存使用情况(所有实例)

是预期的行为。

调查 Node.js 应用程序内存使用的一种方法是深入了解垃圾收集器的概念。

垃圾收集器(GC)的工作是回收未被使用的对象(垃圾)占用的内存。

在这里这里您可以找到有关 Node.js 中的内存管理和使用的信息

此外,Node.js 在认为有必要时会启动 GC。根据这篇文章,检测可能的内存泄漏的一个想法是手动强制垃圾收集器并检查内存是否仍在上升。然而,这不是一个好的做法,不建议在生产中使用,而只是作为一种诊断方法。


推荐阅读