node.js - Nodejs : How can I optimize writing many files?

标签 node.js optimization file-io kinect writefile

我在 Windows 上的 Node 环境中工作。我的代码每秒接收 30 个 Buffer 对象(每个约 500-900kb),我需要尽快将此数据保存到文件系统,而不参与任何阻止接收的工作以下缓冲区(即目标是保存每个缓冲区中的数据,持续约30-45分钟)。就其值(value)而言,数据是来自 Kinect 传感器的连续深度帧。

我的问题是:在 Node 中写入文件的最高效的方式是什么?

这是伪代码:

let num = 0

async function writeFile(filename, data) {
  fs.writeFileSync(filename, data)
}

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){

  let filename = 'file-' + num++

  // Do anything with data here to optimize write?
  writeFile(filename, data)
}

fs.writeFileSync 似乎比 fs.writeFile 快得多,这就是我在上面使用它的原因。但是是否有其他方法可以对数据进行操作或写入文件来加快每次保存的速度?

最佳答案

首先,你永远不想使用 fs.writefileSync()处理实时请求,因为这会阻塞整个 Node.js 事件循环,直到文件写入完成。

好的,基于将每个数据 block 写入不同的文件,那么您希望允许同时进行多个磁盘写入,但不允许无限的磁盘写入。因此,使用队列仍然是合适的,但是这次队列不仅仅一次只有一个写入过程,它同时有一定数量的写入过程:

const EventEmitter = require('events');

class Queue extends EventEmitter {
    constructor(basePath, baseIndex, concurrent = 5) {
        this.q = [];
        this.paused = false;
        this.inFlightCntr = 0;
        this.fileCntr = baseIndex;
        this.maxConcurrent = concurrent;
    }

    // add item to the queue and write (if not already writing)
    add(data) {
        this.q.push(data);
        write();
    }

    // write next block from the queue (if not already writing)
    write() {
        while (!paused && this.q.length && this.inFlightCntr < this.maxConcurrent) {
            this.inFlightCntr++;
            let buf = this.q.shift();
            try {
                fs.writeFile(basePath + this.fileCntr++, buf, err => {
                    this.inFlightCntr--;
                    if (err) {
                        this.err(err);
                    } else {
                        // write more data
                        this.write();
                    }
                });
            } catch(e) {
                this.err(e);
            }
        }
    }

    err(e) {
        this.pause();
        this.emit('error', e)
    }

    pause() {
        this.paused = true;
    }

    resume() {
        this.paused = false;
        this.write();
    }
}

let q = new Queue("file-", 0, 5);

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){
    q.add(data);
}

q.on('error', function(e) {
    // go some sort of write error here
    console.log(e);
});

需要考虑的事情:

  1. 使用 concurrent 进行实验传递给 Queue 构造函数的值。从值 5 开始。然后看看将该值提高得更高是否会带来更好或更差的性能。 Node.js 文件 I/O 子系统使用线程池来实现异步磁盘写入,因此存在最大并发写入数量,因此将并发数量调高可能不会让事情变得更快。

  2. 您可以通过设置 UV_THREADPOOL_SIZE 来尝试增加磁盘 I/O 线程池的大小。启动 Node.js 应用程序之前的环境变量。

  3. 这里您最大的 friend 是磁盘写入速度。因此,请确保您拥有一个具有良好磁盘 Controller 的快速磁盘。快速总线上的快速 SSD 是最好的。

  4. 如果您可以将写入分散到多个实际物理磁盘上,这也可能会增加写入吞吐量(更多磁盘头在工作)。

<小时/>

这是基于问题的初步解释的先前答案(在编辑更改之前)。

既然您需要按顺序进行磁盘写入(全部写入同一个文件),那么我建议您使用写入流并让流对象序列化并缓存数据,或者您可以像这样自己创建一个队列:

const EventEmitter = require('events');

class Queue extends EventEmitter {
    // takes an already opened file handle
    constructor(fileHandle) {
        this.f = fileHandle;
        this.q = [];
        this.nowWriting = false;
        this.paused = false;
    }

    // add item to the queue and write (if not already writing)
    add(data) {
        this.q.push(data);
        write();
    }

    // write next block from the queue (if not already writing)
    write() {
        if (!nowWriting && !paused && this.q.length) {
            this.nowWriting = true;
            let buf = this.q.shift();
            fs.write(this.f, buf, (err, bytesWritten) => {
                this.nowWriting = false;
                if (err) {
                    this.pause();
                    this.emit('error', err);
                } else {
                    // write next block
                    this.write();
                }
            });
        }
    }

    pause() {
        this.paused = true;
    }

    resume() {
        this.paused = false;
        this.write();
    }
}

// pass an already opened file handle
let q = new Queue(fileHandle);

// This fires 30 times/sec and runs for 30-45 min
dataSender.on('gotData', function(data){
    q.add(data);
}

q.on('error', function(err) {
    // got disk write error here
});
<小时/>

您可以使用 writeStream 而不是这个自定义 Queue 类,但问题是 writeStream 可能会填满,然后您必须有一个单独的缓冲区作为放置数据的地方。像上面一样使用您自己的自定义队列可以同时解决这两个问题。

其他可扩展性/性能评论

  1. 由于您似乎将数据串行写入同一个文件,因此您的磁盘写入不会从集群或并行运行多个操作中受益,因为它们基本上必须序列化。

  2. 如果您的 Node.js 服务器除了执行这些写入之外还有其他事情要做,那么创建第二个 Node.js 进程并执行所有操作可能会有一点优势(必须通过测试进行验证)在另一个进程中进行磁盘写入。您的主 Node.js 进程将接收数据,然后将其传递给子进程,该子进程将维护队列并进行写入。

  3. 您可以尝试的另一件事是合并写入。当队列中有多个项目时,您可以将它们组合在一起形成一个写入。如果写入量已经很大,这可能不会有太大区别,但如果写入量很小,这可能会产生很大的差异(将大量小磁盘写入合并为一个较大的写入通常会更有效)。

  4. 这里您最大的 friend 是磁盘写入速度。因此,请确保您拥有一个具有良好磁盘 Controller 的快速磁盘。快速 SSD 是最好的。

关于node.js - Nodejs : How can I optimize writing many files?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50416359/

相关文章:

javascript - 如何检索拆分的值并将它们放入单独的变量中

php - 我应该在一页中添加和删除索引吗?

optimization - 编译器中间阶段代码优化的目的是什么?

python - 按顺序依次读取文件

javascript - readline 不暂停或允许输入

javascript - 在 ExpressJS 中向文件路径添加参数

c# - 使用文件

javascript - 将文本文件解析为数组?

node.js - PostgreSQL : How to accurately define a schema in a model? 的 Sequelize 架构

database - 使用单个数据库与多个数据库时性能有提升吗?