我试图使用node.js转换流来组合一堆不同的数据源(基本上是gzip压缩的csvs,但结构很奇怪,它们都有相同的字节长度)并将组合数据写成MongoDB文件而不用将任何文件完全加载到内存中。一张图片讲述了千言万语。它看起来像这样:
file1 file2 file3 file4 file5 file6 ...
\ | | | | /
--(transform steps: gunzip, remove newlines, etc.)---
\ | | | | /
doc: {data1, data2, data3, data4, data5, data6, metadata} <------ combine step
||
(MongoDB)
每个可读流(file1,2等)通过管道传输到自己的gunzip和其他一些变换的串行管道中。然后将每个文件流传送到组合器的一个实例。就在它之前,我将一个streamIndex添加到块中,以便我可以在组合器变换中区分它们。像这样:
let slice = chunk.slice(i, i + length);
slice.streamIndex = this.streamIndex;
this.push(slice);
我在组合步骤中需要来自每个流的一个数据,所以当接收到数据时,我会查看我收到的块的streamIndex并缓冲数据,直到每个源都有足够的数据。然后,我将源组合到一个文档中,添加元数据,并通过批量插入将其发送到MongoDB。
问题是流以略微不同的速率读取,如果必须开始缓冲过多的数据,则节点会锁定。如果不加以控制,其中一个流可能很容易遇到比另一个更缓冲的1,000,000多个条目,这会导致巨大的垃圾收集,从而导致节点进程停止运行。
当我知道他们通过以下方式提供了太多数据时,我试图向个别上游添加背压以减慢它们的速度:
file1.cork()
this._writableState.corked = true
最可靠的 - 通过暂时删除违规流来表现:
this.streams.forEach((stream, i) => {
if (this.dataBuffers[i].length > MAX_BUFFER_LENGTH) {
stream.unpipe(this);
};
});
最后一步(当缓冲区变低时重新进行管道处理)使所有缓冲区平稳地保持在MAX_BUFFER_LENGTH
。只有我得到一个流&#39;结束&#39;通过阅读数据,事件只有10-15%左右。我观察到一些数据在试验不同的东西时会丢失(流出管道并流入深渊),所以我只能想象在这里发生同样的事情。
这真的是一个非常微不足道的问题,而且我对溪流来说还是一个新手,所以我可能只做一些愚蠢的事情。基本上我的问题是:我如何让每个流提供一些数据,等待其他人做同样的事情,合并,从每个流中读取更多;等等,不会丢失数据(并且不会缓冲太多)?
最终细节: