使用Node.js写入磁盘的速度会随着时间的推移变慢。

3

我尝试使用Node.js将大文件(500 MByte)写入磁盘。 我发现,虽然前几个文件可以在几秒钟内完成写入(通常为3至5秒),但从第10个文件开始,速度趋于变慢(而且无法恢复)。

设置包括一个通过TCP/IP套接字接受文件并将它们传输到磁盘的服务器:

var fs = require('fs'),
    net = require('net'),
    path = require('path');

var counter = 0;

net.createServer(function (socket) {
  console.time('received');
  console.time('written');

  counter++;

  var filename = path.join(__dirname, 'temp' + counter + '.tmp');
  var file = fs.createWriteStream(filename, { encoding: 'utf8' });

  socket.pipe(file);

  socket.once('end', function () {
    console.timeEnd('written');
  });

  file.once('finish', function () {
    console.timeEnd('received');
  });
}).listen(3000);

我通过以下方式使用nc从终端发送数据:

$ while [ true ]; do `cat input.tmp | nc localhost 3000`; done

运行中

$ time cat input.tmp > /dev/null

研究表明,猫咪总是在同一时间读取文件。如果我将Node.js脚本的输出路径替换为/dev/null,写入也总是在同一时间发生。

因此,问题显然与实际写入磁盘有关。

起初我以为这可能是并发读写的问题,但即使我运行以下命令,问题仍然存在:

$ while [ true ]; do `cat input.tmp | nc localhost 3000; sleep 5`; done

如果我使用比之前更大的文件(两倍大,即1GB),运行相同的测试,则需要的时间减少了一半,直到写入变慢。

更新

我已经修改了我的Node.js应用程序,将所有内容都写入一个单独的文件中,并不断追加...服务器现在如下:

var fs = require('fs'),
    net = require('net'),
    path = require('path');

var filename = path.join(__dirname, 'temp.tmp');
var file = fs.createWriteStream(filename, { encoding: 'utf8' });

net.createServer(function (socket) {
  console.time('received');
  console.time('written');

  socket.pipe(file, { end: false });

  socket.once('end', function () {
    console.timeEnd('written');
  });
}).listen(3000);

现在问题已经解决了,显然这与连续写入多个文件有关。至少我没有看到自己同时写入多个文件的地方(难道我有吗?),所以我想不出为什么会发生这种情况。特别是使用sleep 5应该确保操作系统已经将所有内容写入磁盘。
更新2: 我最初使用的是Node.js 0.10.32版本。当我切换到0.11.13版本时,效果并没有完全消失,但是发生的时间要晚得多。在最初的设置中,问题在大约10个周期后出现,在Node.js 0.11.13中最早在第30个周期发生。
您有什么想法可以解释这种行为吗?

你能在没有Node.js的情况下测试一下吗?很有可能你会观察到相同的模式。操作系统构建了抽象层来减轻硬盘I/O的痛苦,涉及到许多因素,我们只需确保它实际上是Node.js,而不是操作系统、缓存或其他任何东西。附注:这里有一些用NC替换Node的示例:http://www.g-loaded.eu/2006/11/06/netcat-a-couple-of-useful-examples/ - alandarev
1
你确定你没有泄露资源吗?并且,所有的文件句柄都被关闭了吗? - jfriend00
某些类型的泄漏可能会导致过多的内存消耗,从而导致交换或某种内存抖动。我提到文件句柄,因为在node.js示例中并不完全清楚如何正确关闭所有内容。 - jfriend00
我也有同样的想法,但是OP似乎捕获了“完成”事件,所以我想那部分应该没问题。 - xShirase
1
我非常确定没有资源泄漏,但我不能保证。换句话说:可能会有一些泄漏的情况,但至少我没有看到任何明显可能发生泄漏的地方。pipe应该能够很好地清理,而对于其他情况,我使用once - Golo Roden
显示剩余2条评论
1个回答

3

我之前遇到了类似的问题。NodeJS最多只能同时处理一定数量的I/O操作,因此它会尽可能地同时写入多个文件,而其他的文件则需要排队等待空闲的位置。

file 1 |-----------------------------------|
file 2  |-----------------------------------|
file 3   |-----------------------------------|
file 4                                      |-------------------------------------|

以上只是一个示例,但它展示了这个原则:在这种情况下写4个文件将需要比只写3个文件多一倍的时间。


嗯,我不太确定这是否有帮助,因为我正在按顺序发送输入文件,而不是并行发送。或者我的测试设置建立方式有什么问题吗? - Golo Roden
我不太确定;) 但我很有信心,这很可能不是一个Node.js的问题,你的代码在我看来是没什么问题的。你是否尝试像评论中建议的使用netcat来验证“操作系统问题”的理论呢? - xShirase
是的,我做了。不幸的是,没有提供任何额外的信息 :-( - Golo Roden
(https://github.com/joyent/node/issues/6118)显然在0.11之前,某种错误会使可写流在完成事件后“逗留”,这可能与此有关。 - xShirase
谢谢你的提示 :-) - Golo Roden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接