首先,您永远不想在处理实时请求时使用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);
});
需要考虑的事项:
-
使用您传递给队列构造函数的concurrent 值进行实验。从值 5 开始。然后看看再提高该值是否会给您带来更好或更差的性能。 node.js 文件 I/O 子系统使用线程池来实现异步磁盘写入,因此存在允许的最大并发写入数,因此将并发数提高到非常高可能不会让事情进展得更快。
您可以在启动 node.js 应用程序之前通过设置 UV_THREADPOOL_SIZE 环境变量来增加磁盘 I/O 线程池的大小。
您最大的朋友是磁盘写入速度。因此,请确保您有一个带有良好磁盘控制器的快速磁盘。快速总线上的快速 SSD 是最好的。
如果您可以将写入分散到多个实际物理磁盘上,则可能还会增加写入吞吐量(更多磁盘磁头在工作)。
这是基于对问题的初始解释的先前答案(在编辑更改之前)。
由于您似乎需要按顺序执行磁盘写入(全部写入同一个文件),那么我建议您使用写入流并让流对象为您序列化和缓存数据,或者您可以像这样自己创建一个队列:
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 可能会填满,然后您必须有一个单独的缓冲区作为放置数据的地方。像上面那样使用您自己的自定义队列可以同时解决这两个问题。
其他可扩展性/性能评论
因为您似乎将数据串行写入同一个文件,因此您的磁盘写入不会受益于集群或并行运行多个操作,因为它们基本上必须被序列化。
如果您的 node.js 服务器除了执行这些写入之外还有其他事情要做,那么创建第二个 node.js 进程并执行所有操作可能会有一点优势(必须通过测试进行验证)在其他进程中写入磁盘。您的主 node.js 进程将接收数据,然后将其传递给将维护队列并进行写入的子进程。
-
您可以尝试的另一件事是合并写入。当队列中有多个项目时,可以将它们组合成一个写入。如果写入已经相当大,这可能没有太大区别,但如果写入很小,这可能会产生很大的不同(将大量小磁盘写入组合成一个更大的写入通常更有效)。
您最大的朋友是磁盘写入速度。因此,请确保您有一个带有良好磁盘控制器的快速磁盘。最好使用快速 SSD。