【问题标题】:Nodejs: How can I optimize writing many files?Nodejs:如何优化写入多个文件?
【发布时间】:2018-05-18 17:17:36
【问题描述】:

我在 Windows 上的 Node 环境中工作。我的代码每秒接收 30 个Buffer 对象(每个对象约 500-900kb),我需要尽快将这些数据保存到文件系统中,而不要从事任何阻止接收以下Buffer 的工作(即目标是保存 每个 缓冲区中的数据约 30-45 分钟)。值得一提的是,这些数据是来自 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() 来获得最佳的整体服务器响应能力。它会阻塞整个事件循环(意味着您的服务器不能做任何其他事情),直到磁盘写入完成。
  • @jfriend00 有什么替代建议吗?使用fs.writeFile(),许多传入的文件都没有写入。我认为这是因为所有线程都忙于现有文件写入。
  • 那是因为在前一个块完成之前,您不能在下一个块上调用fs.writeFile()。我已经写了自己的答案给你看。

标签: node.js optimization file-io kinect writefile


【解决方案1】:

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

好的,基于将每个数据块写入不同的文件,那么您希望允许同时进行多个磁盘写入,但不是无限的磁盘写入。所以,使用队列仍然是合适的,但是这次队列不只是一次有一个正在写入的进程,它同时有一些正在写入的进程:

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 值进行实验。从值 5 开始。然后看看再提高该值是否会给您带来更好或更差的性能。 node.js 文件 I/O 子系统使用线程池来实现异步磁盘写入,因此存在允许的最大并发写入数,因此将并发数提高到非常高可能不会让事情进展得更快。

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

  3. 您最大的朋友是磁盘写入速度。因此,请确保您有一个带有良好磁盘控制器的快速磁盘。快速总线上的快速 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. 您最大的朋友是磁盘写入速度。因此,请确保您有一个带有良好磁盘控制器的快速磁盘。最好使用快速 SSD。

【讨论】:

  • 抱歉!这些是我正在编写的单独文件。不写入同一个文件。我编辑了问题以反映这一点。
  • @ACPrice - 好吧,在我根据您之前的问题的样子做出所有这些努力后,这令人沮丧。我现在没有时间为这个完全不同的问题写一个完全不同的答案。
  • 很公平!感谢您对错误伪代码的帮助和道歉。
  • 对于它的价值,看起来您的答案可以很容易地调整为编写单独的文件。会试一试。
  • @ACPrice - 使用单独的文件,您可以一次运行 N 个(其中 N 是一个较小的数字,您可以试验)这将提高一些性能,因为您不必运行它们串行。 Node.js 为磁盘 I/O 使用线程池,因此您还可以尝试并行运行更多项目并增加线程池大小。请参阅Has anyone tried using the UV_THREADPOOL_SIZE environment variable? 了解更多信息。
【解决方案2】:

我已经编写了一个广泛执行此操作的服务,您可以做的最好的事情是将输入数据直接通过管道传输到文件(如果您也有输入流)。 以这种方式下载文件的简单示例:

const http = require('http')

const ostream = fs.createWriteStream('./output')
http.get('http://nodejs.org/dist/index.json', (res) => {
    res.pipe(ostream)                                                                                                                                                                                              
})
.on('error', (e) => {
    console.error(`Got error: ${e.message}`);
})

因此,在此示例中,不涉及整个文件的中间复制。当文件从远程 http 服务器以块的形式读取时,它被写入磁盘上的文件。这比从服务器下载整个文件、将其保存在内存中然后将其写入磁盘上的文件要高效得多。

流是 Node.js 中许多操作的基础,因此您也应该研究这些。

根据您的场景,您应该调查的另一件事是 UV_THREADPOOL_SIZE,因为 I/O 操作使用默认设置为 4 的 libuv 线程池,如果您进行大量编写,您可能会填满它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-07
    • 2020-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多