gpt4 book ai didi

javascript - Node.js fs.writeFile() 清空文件

转载 作者:行者123 更新时间:2023-11-29 19:25:48 26 4
gpt4 key购买 nike

我有一个大约每 16-40 毫秒调用一次的更新方法,里面有这段代码:

this.fs.writeFile("./data.json", JSON.stringify({
totalPlayersOnline: this.totalPlayersOnline,
previousDay: this.previousDay,
gamesToday: this.gamesToday
}), function (err) {
if (err) {
return console.log(err);
}
});

如果服务器抛出错误,“data.json”文件有时会变成空的。我该如何防止这种情况发生?

最佳答案

问题

fs.writeFile不是原子操作。这是一个示例程序,我将在其上运行 strace:

#!/usr/bin/env node
const { writeFile, } = require('fs');

// nodejs won’t exit until the Promise completes.
new Promise(function (resolve, reject) {
writeFile('file.txt', 'content\n', function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
});

当我在 strace -f 下运行它并整理输出以仅显示来自 writeFile 操作 (which spans multiple IO threads, actually) 的系统调用时,我得到:

open("file.txt", O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC, 0666) = 9
pwrite(9, "content\n", 8, 0) = 8
close(9) = 0

如您所见,writeFile 分三步完成。

  1. 文件是open()编辑。这是一个原子操作,它使用提供的标志在磁盘上创建一个空文件,或者如果文件存在,则将其 chop 。 chop 文件是确保只有您写入的内容最终出现在文件中的一种简单方法。如果文件中存在现有数据并且文件比您随后写入文件的数据长,则额外的数据将保留。为避免这种情况,您会 chop 。
  2. 内容写好了。因为我写了这么短的字符串,所以这是用一个 pwrite() 完成的。调用,但对于大量数据,我认为 nodejs 可能一次只写入一个 block 。
  3. handle 已关闭。

我的 strace 让这些步骤中的每一个都发生在不同的 Node IO 线程上。这向我暗示 fs.writeFile() 可能实际上是根据 fs.open() 实现的, fs.write() , 和 fs.close() .因此,nodejs 不会将此复杂操作视为任何级别的原子操作——因为它不是。因此,如果您的 Node 进程在没有等待操作完成的情况下终止,即使是正常终止,操作也可以在上述任何步骤中进行。在您的情况下,您会看到您的进程在 writeFile() 完成第 1 步之后但在完成第 2 步之前退出。

解决方案

用 POSIX 层事务性替换文件内容的常见模式是使用以下步骤:

  1. 将数据写入一个不同名称的文件,fsync()文件(参见 “Ensuring data reaches disk” 中的“你什么时候应该 fsync?”),然后 close() 它。
  2. rename() (或者,在 Windows 上,MoveFileEx() with MOVEFILE_REPLACE_EXISTING)您要替换的文件上的不同名称的文件。

使用此算法,无论您的程序何时终止,目标文件都会更新或不更新。而且,更好的是,日志式(现代)文件系统将确保,只要您在步骤 1 中 fsync() 文件,然后再继续执行步骤 2,这两个操作就会按顺序进行。即,如果您的程序执行第 1 步,然后执行第 2 步,但您拔下插头,当您启动时,您会发现文件系统处于以下状态之一:

  • 这两个步骤都没有完成。原始文件完好无损(或者如果它以前不存在,则它不存在)。替换文件不存在(writeFile() 算法的第 1 步,open(),实际上从未成功),存在但为空(writeFile 的第 1 步) () 算法完成),或存在一些数据(writeFile() 算法的第 2 步部分完成)。
  • 第一步完成。原始文件完好无损(或者如果它之前不存在,它仍然不存在)。替换文件包含您想要的所有数据。
  • 两个步骤都已完成。在原始文件的路径上,您现在可以访问您的替换数据——全部,而不是一个空白文件。您在第一步中写入替换数据的路径已不存在。

使用此模式的代码可能如下所示:

const { writeFile, rename, } = require('fs');

function writeFileTransactional (path, content, cb) {
// The replacement file must be in the same directory as the
// destination because rename() does not work across device
// boundaries.

// This simple choice of replacement filename means that this
// function must never be called concurrently with itself for the
// same path value. Also, properly guarding against other
// processes trying to use the same temporary path would make this
// function more complicated. If that is a concern, a proper
// temporary file strategy should be used. However, this
// implementation ensures that any files left behind during an
// unclean termination will be cleaned up on a future run.
let temporaryPath = `${path}.new`;
writeFile(temporaryPath, content, function (err) {
if (err) {
return cb(err);
}

rename(temporaryPath, path, cb);
});
};

这基本上是您在任何语言/框架中用于解决相同问题的相同解决方案。

关于javascript - Node.js fs.writeFile() 清空文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30886217/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com