我有两个节点线程正在运行,一个监视目录的文件消耗,另一个负责将文件写入给定目录。
通常它们不会在同一个目录上运行,但对于我正在处理的边缘情况,它们会在同一个目录上运行。
消费应用程序似乎在文件完全写入之前抓取文件,从而导致文件损坏。
有没有办法可以锁定文件直到写入完成?我研究了
lockfile
模块,但不幸的是我不相信它适用于这个特定的应用程序。
======
完整的代码放在这里远非有意义,但其要点是:
听众:
fs.writeFile
守望者:
chokidar
来跟踪每个监视目录中添加的文件fs.access
被调用以确保我们可以访问该文件
fs.access
似乎不受正在写入的文件的影响fs.createReadStream
使用,然后发送到服务器
在这种情况下,文件将导出到监视目录,然后重新导入 通过观看过程。
我会使用 proper-lockfile 为此。您可以指定重试次数或使用重试配置对象来使用指数退避策略。这样你就可以处理两个进程需要同时修改同一个文件的情况。
这是一个带有一些重试选项的简单示例:
const lockfile = require('proper-lockfile');
const Promise = require('bluebird');
const fs = require('fs-extra');
const crypto = require('crypto'); // random buffer contents
const retryOptions = {
retries: {
retries: 5,
factor: 3,
minTimeout: 1 * 1000,
maxTimeout: 60 * 1000,
randomize: true,
}
};
let file;
let cleanup;
Promise.try(() => {
file = '/var/tmp/file.txt';
return fs.ensureFile(file); // fs-extra creates file if needed
}).then(() => {
return lockfile.lock(file, retryOptions);
}).then(release => {
cleanup = release;
let buffer = crypto.randomBytes(4);
let stream = fs.createWriteStream(file, {flags: 'a', encoding: 'binary'});
stream.write(buffer);
stream.end();
return new Promise(function (resolve, reject) {
stream.on('finish', () => resolve());
stream.on('error', (err) => reject(err));
});
}).then(() => {
console.log('Finished!');
}).catch((err) => {
console.error(err);
}).finally(() => {
cleanup && cleanup();
});
编写一个锁状态系统实际上非常简单。我找不到我在哪里做的,但想法是:
锁定文件只是单个目录中的一个空文件。每个锁定文件从它所代表的文件的完整路径的散列中获取其名称。我使用了 MD5(相对较慢),但是只要您确信路径字符串不会发生冲突,任何哈希算法都应该没问题。
这不是 100% 线程安全的,因为(除非我错过了一些愚蠢的事情)你无法自动检查文件是否存在并在 Node 中创建它,但在我的用例中,我持有锁 10 秒或更多,因此微秒竞争条件似乎并没有那么大的威胁。如果您每秒在同一文件上持有和释放数千个锁,那么这种竞争条件可能适用于您。
这些显然只是建议锁,因此您需要确保您的代码请求锁定并捕获预期的异常。
使用这个专为此类场景设计的软件包
重命名文件是原子的。 使用某个特定名称(例如扩展名)写入文件,当写入完成且文件关闭时,将其重命名为其他特定名称。仅注意具有第二个特定名称的文件。 或者将文件重命名到另一个(子)目录中。 当底层操作系统暴露部分刷新的关闭文件时,可能会出现唯一的问题,不太可能