作为对@MikeC 的excellent answer 的补充,以下是writable.write() 的当前文档(v8.4.0)中的一些相关细节:
如果返回false,则应停止向流中写入数据的进一步尝试,直到发出'drain' 事件。
当流没有耗尽时,对write() 的调用将缓冲chunk,并返回false。一旦所有当前缓冲的块被耗尽(操作系统接受交付),'drain' 事件将被发出。建议一旦write() 返回false,在发出'drain' 事件之前不要再写入块。虽然允许在未耗尽的流上调用 write(),但 Node.js 将缓冲所有写入的块,直到达到最大内存使用量,此时它将无条件中止。甚至在它中止之前,高内存使用会导致垃圾收集器性能不佳和高RSS(通常不会释放回系统,即使在不再需要内存之后也是如此)。
对于backpressuring in streams:
在数据缓冲区超过highWaterMark 或写入队列当前繁忙的任何情况下,.write() 将返回false。
当返回 false 值时,背压系统启动。
一旦数据缓冲区被清空,将发出.drain() 事件并恢复传入的数据流。
队列完成后,背压将允许再次发送数据。正在使用的内存空间将自行释放并为下一批数据做准备。
+-------------------+ +=================+
| Writable Stream +---------> .write(chunk) |
+-------------------+ +=======+=========+
|
+------------------v---------+
+-> if (!chunk) | Is this chunk too big? |
| emit .end(); | Is the queue busy? |
+-> else +-------+----------------+---+
| emit .write(); | |
^ +--v---+ +---v---+
^-----------------------------------< No | | Yes |
+------+ +---v---+
|
emit .pause(); +=================+ |
^-----------------------+ return false; <-----+---+
+=================+ |
|
when queue is empty +============+ |
^-----------------------< Buffering | |
| |============| |
+> emit .drain(); | ^Buffer^ | |
+> emit .resume(); +------------+ |
| ^Buffer^ | |
+------------+ add chunk to queue |
| <---^---------------------<
+============+
这里有一些可视化(使用--max-old-space-size=512 运行 V8 堆内存大小为 512MB 的脚本)。
此可视化显示i 每 10,000 步的heap memory usage(红色)和增量时间(紫色)(X 轴显示i):
'use strict'
var fs = require('fs');
var wstream = fs.createWriteStream('myOutput.txt');
var latestTime = (new Date()).getTime();
var currentTime;
for (var i = 0; i < 10000000000; i++) {
wstream.write(i+'\n');
if (i % 10000 === 0) {
currentTime = (new Date()).getTime();
console.log([ // Output CSV data for visualisation
i,
(currentTime - latestTime) / 5,
process.memoryUsage().heapUsed / (1024 * 1024)
].join(','));
latestTime = currentTime;
}
}
console.log('End!')
wstream.end();
随着内存使用量接近最大限制 512MB,脚本运行速度越来越慢,直到达到限制时它最终崩溃。
此可视化使用v8.setFlagsFromString() 和--trace_gc 来显示每个垃圾回收的当前内存使用情况(红色)和执行时间(紫色)(X 轴以秒为单位显示总经过时间):
'use strict'
var fs = require('fs');
var v8 = require('v8');
var wstream = fs.createWriteStream('myOutput.txt');
v8.setFlagsFromString('--trace_gc');
for (var i = 0; i < 10000000000; i++) {
wstream.write(i+'\n');
}
console.log('End!')
wstream.end();
大约 4 秒后内存使用率达到 80%,而垃圾收集器 gives up trying to Scavenge and is forced to use Mark-sweep(慢了 10 倍以上)——请参阅 this article 了解更多详细信息。
为了比较,下面是@MikeC 的代码的相同可视化,当write 缓冲区变满时等待drain: