【问题标题】:Why isn't Google App engine freeing memory using Node.js为什么 Google App 引擎不使用 Node.js 释放内存
【发布时间】:2020-03-18 01:16:51
【问题描述】:

所以这篇文章是一个由两部分组成的问题。 我正在使用实例类 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. 显示从这些非重路由导航时在 Google StackDriver 上看到的内存使用情况

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

我正在使用 Chrome Devtools Node.js Memory Profiler 在我的 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.当导航到图像大小调整路线一次时,来自 Chrome devtools for Node.js 的堆快照

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

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

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

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

图 4.StackDriver profiler 堆快照

所以如果我的分析是正确的,我假设它与运行应用引擎中的实例的操作系统有关?我不知道。

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

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

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

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

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

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

【问题讨论】:

  • 请记住,堆并不是程序分配的唯一内存类型...我建议在您的窗口上查看整个进程的内存使用情况,以及它产生的任何进程机器,而不仅仅是堆大小。我怀疑它看起来会与您在应用引擎上看到的相似。
  • 您检查流程是对的,但它仍然与应用引擎显示的内容相去甚远。请查看更新后的问题。
  • 你在 node 上使用什么图片库?通常那些包括产生额外的图像处理进程的二进制文件。我的意思是你在比较苹果和橘子。了解程序的总内存消耗比仅查看堆大小要复杂得多。事实上,您已经注意到两个平台上的堆大小是相同的......
  • 正如我所提到的,我正在使用Sharp。问题是为什么它在应用引擎上比在 Windows 10 上需要更多的内存。
  • 除了运行图像处理之外,在应用程序空闲时不断使用 500mb 的内存对我来说似乎遥不可及。

标签: javascript node.js google-app-engine google-cloud-platform memory-leaks


【解决方案1】:

因此,由于前面提到的 cmets 和您的上次更新(更新 4),我们可以意识到以下区别:

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

是一种预期的行为。

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

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

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

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

【讨论】:

    猜你喜欢
    • 2014-12-27
    • 2014-04-11
    • 2013-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-23
    • 1970-01-01
    相关资源
    最近更新 更多