节点流导致大量内存占用或泄漏

我正在使用节点v0.12.7并希望直接从数据库流到客户端(用于文件下载)。 但是,当使用流时,我注意到了大量内存占用(可能会发生内存泄漏)。

使用express,我创建了一个端点,只需将可读流传递给响应,如下所示:

app.post('/query/stream', function(req, res) {

  res.setHeader('Content-Type', 'application/octet-stream');
  res.setHeader('Content-Disposition', 'attachment; filename="blah.txt"');

  //...retrieve stream from somewhere...
  // stream is a readable stream in object mode

  stream
    .pipe(json_to_csv_transform_stream) // I've removed this and see the same behavior
    .pipe(res);
});

在生产中,可读stream从数据库中检索数据。 数据量非常大(1M +行)。 我用一个虚拟流(见下面的代码)替换了这个可读流,以简化调试并注意到相同的行为:我的内存使用量每次跳跃大约200M。 有时候垃圾收集将会启动,内存会下降一点,但它会线性增加,直到我的服务器耗尽内存。

我开始使用流的原因是不必将大量数据加载到内存中。 预期这种行为?

我还注意到,在流式传输时,我的CPU使用率跳跃到100%和块(这意味着其他请求无法处理)。

我使用这个不正确吗?

虚拟可读流码

// Setup a custom readable
var Readable = require('stream').Readable;

function Counter(opt) {
  Readable.call(this, opt);
  this._max = 1000000; // Maximum number of records to generate
  this._index = 1;
}
require('util').inherits(Counter, Readable);

// Override internal read
// Send dummy objects until max is reached
Counter.prototype._read = function() {
  var i = this._index++;
  if (i > this._max) {
    this.push(null);
  }
  else {
    this.push({
      foo: i,
      bar: i * 10,
      hey: 'dfjasiooas' + i,
      dude: 'd9h9adn-09asd-09nas-0da' + i
    });
  }
};

// Create the readable stream
var counter = new Counter({objectMode: true});

//...return it to calling endpoint handler...

更新

只是一个小小的更新,我从来没有找到原因。 我最初的解决方案是使用集群产生新的进程,以便其他请求仍然可以处理。

我已经更新到节点v4。 尽管在处理过程中cpu / mem的使用率仍然很高,但似乎已经修复了漏洞(意味着mem的使用率回落)。


看起来你正在做的一切正确。 我复制了你的测试用例,并且在v4.0.0中遇到了同样的问题。 将它从objectMode中取出并在对象上使用JSON.stringify似乎可以防止高内存和高CPU。 这导致我建立了JSON.stringify ,这似乎是问题的根源。 使用流式库JSONStream而不是v8方法为我解决了这个问题。 它可以像这样使用: .pipe(JSONStream.stringify())


更新2 :以下是各种Stream API的历史记录:

https://medium.com/the-node-js-collection/a-brief-history-of-node-streams-pt-2-bcb6b1fd7468

0.12使用Streams 3。

更新 :对于旧的node.js流,这个答案是正确的。 如果可写入的流跟不上,新的Stream API有一种机制来暂停可读流。

背压

看起来你受到了经典的“backpressure”node.js问题的困扰。 本文详细解释它。

但是这里有一个TL; DR:

你是对的,流被用来不必将大量的数据加载到内存中。

但不幸的是,流没有机制知道是否可以继续流式传输。 流是愚蠢的。 他们只是尽可能快地将数据投入下一个数据流。

在你的例子中,你正在读取一个大型的csv文件并将其传输到客户端。 问题是读取文件的速度大于通过网络上传文件的速度。 所以数据需要存储在某个地方,直到成功被遗忘。 这就是为什么你的内存不断增长,直到客户端完成下载。

解决方法是将阅读流限制为管道中最慢流的速度。 也就是说,您可以在另一个流的前面加上另一个流,它将告诉您的阅读流什么时候可以阅读下一个数据块。


在Node.js中发生内存泄漏非常容易

通常,这是一件小事,例如在创建匿名函数或在回调中使用函数参数后声明变量。 但是它对封闭上下文有很大的影响。 因此一些变量永远不能被释放。

本文解释了您可能具有的不同类型的内存泄漏以及如何找到它们。 数字4 - 闭包 - 是最常见的一个。

我发现了一条可以避免泄漏的规则:

  • 在分配它们之前,请始终声明所有变量。
  • 声明所有变量后声明函数
  • 避免在靠近循环或大块数据的地方关闭
  • 链接地址: http://www.djcxy.com/p/66927.html

    上一篇: Node streams cause large memory footprint or leak

    下一篇: Process Memory grows huge