【问题标题】:How to manage a stream in order to process incoming data in node.js如何管理流以处理 node.js 中的传入数据
【发布时间】:2020-03-05 09:20:32
【问题描述】:

对于文件中的每一行,我想执行计算密集型任务,例如图像压缩。我遇到的问题是数据输入得太快并且压倒了内存。理想情况下,我希望能够在处理数据时暂停并继续流。

我最初尝试将readline 模块与这样的文件流一起使用:

const fileStream = fs.createReadStream('long-list.txt')
const rl = readline.createInterface({ input: fileStream })
rl.on('line', (line, lineCount) => {
   doTheHeavyTask(line)
})

但是,这很快就会在成千上万次调用doTheHeavyTask() 时使内存不堪重负。

我决定将每一行推入一个队列并创建一个事件,当上一行处理完毕后,将下一行出列:

const lineQ = new Queue() // From the 'queue-fifo' module
rl.on('line', (line, lineCount) => {
   lineQ.enqueue(line)
})
const lineEmitter = new EventEmitter() // From the 'events' module
lineEmitter.on('processNextLine', async () => {
    await doTheHeavyTask( lineQ.dequeue() )
    if (!lineQ.isEmpty()) lineEmitter.emit('processNextLine')
})
setTimeout( () => lineEmitter.emit('processNextLine'), 20) // Give rl a moment to enqueue some lines

这行得通,但它似乎有点老套,并不比一次读取文件好多少。

我对 Javascript 中的“背压”和“生成器”等概念有模糊的了解,但不知道如何应用它们。

【问题讨论】:

  • doHeavyTask 是异步的吗?
  • @JonasWilms 是的,doTheHeavyTask 是异步的。但即使它是同步的,我相信rl 将继续发出'line' 事件,只要它可以在文件中读取。
  • 没有。它不会。 JS 有一个可观察到的同步执行模型。不会同时运行任何代码。

标签: javascript node.js stream fs


【解决方案1】:

这里的问题不是流本身,而是你触发的异步任务。每个任务(无论是带有闭包的回调还是异步函数)都会消耗内存。如果您同时启动多个(数千个)任务,则会使用资源。

您可以使用异步迭代器遍历这些行,并为每行执行一项任务(并等待它):

 (async function () {
     for await (const el of rl) {
        await doHeavyTask(el);
     }
 })();

这将是正确的背压。

但是,它一次只执行一项任务,这可能会很慢。要缓冲一些元素并同时处理它们,您可以这样做:

 const SIZE = 10; // to be tested with different values

 (async function () {
   let chunk = [];
   for await(const el of rl) {
      chunk.push(el);
      if(chunk.length >= SIZE) {
         await Promise.all(chunk.map(doHeavyTask));
         chunk.length = 0;
      }
   }
   await Promise.all(chunk.map(doHeavyTask));
})();

你需要 at least Node 11.14.0 才能工作

【讨论】:

  • 有什么想法可以在 Node 10 中做类似的事情吗?
  • 不好玩的polyfilling。
  • 或者更具体地说:正确处理流的背压/关闭很复杂。最好在此处使用官方的 asyncIterator,因为它可以正确执行所有操作。
  • 看看readline documentation 我敢打赌我可以在各自的事件中使用pause()resume() 函数。然而,异步迭代器更加地道
  • 是的,rl.on("line", async el => { rl.pause(); await doHeavyTask(el); rl.resume(); }); 之类的东西可能会起作用。这是official way
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 2014-06-24
  • 2017-07-07
  • 2018-03-31
相关资源
最近更新 更多