如何在异步写入文件时锁定文件
How can I lock a file while writing to it asynchronously
我有两个节点线程 运行,一个负责监视文件消耗的目录,另一个负责将文件写入给定目录。
通常它们不会在同一个目录上运行,但对于我正在处理的极端情况,它们会是。
消费应用程序似乎在文件完全写入之前抓取文件,导致文件损坏。
有什么方法可以锁定文件直到写入完成?我查看了 lockfile
模块,但遗憾的是我认为它不适用于这个特定的应用程序。
=====
完整的代码放在这里意义不大,但要点是这样的:
- App 分离出 watchers 和 listener
听众:
- 侦听正在添加到数据库的文件,使用
fs.writeFile
将其导出
观察者:
- watcher 使用
chokidar
跟踪每个监视目录中添加的文件
- 找到时调用
fs.access
以确保我们可以访问该文件
fs.access
似乎对正在写入的文件不感兴趣
- 文件通过
fs.createReadStream
使用,然后发送到服务器
- 文件流是必需的,因为我们需要文件哈希
在这种情况下,文件被导出到监视目录,然后重新导入
通过观看过程。
编写锁态系统实际上非常简单。我找不到我在哪里做的,但我的想法是:
- 获取锁时创建锁文件,
- 释放锁时删除它们,
- 超时后删除它们,
- 每当请求锁定其锁定文件已存在的文件时抛出。
锁定文件只是单个目录中的一个空文件。每个锁定文件都从它所代表的文件的完整路径的哈希值中获取其名称。我使用了 MD5(相对较慢),但只要您确信路径字符串不会发生冲突,任何哈希算法都应该没问题。
这不是 100% 线程安全的,因为(除非我错过了一些愚蠢的东西)你不能自动检查文件是否存在并在 Node 中创建它,但在我的用例中,我持有锁定 10 秒或更长时间,因此微秒竞争条件似乎并没有太大的威胁。如果您每秒对同一文件持有和释放数千个锁,那么这种竞争条件可能适用于您。
显然,这些锁只是咨询锁,因此您需要确保您的代码请求锁并捕获预期的异常。
为此我会使用 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();
});
我有两个节点线程 运行,一个负责监视文件消耗的目录,另一个负责将文件写入给定目录。
通常它们不会在同一个目录上运行,但对于我正在处理的极端情况,它们会是。
消费应用程序似乎在文件完全写入之前抓取文件,导致文件损坏。
有什么方法可以锁定文件直到写入完成?我查看了 lockfile
模块,但遗憾的是我认为它不适用于这个特定的应用程序。
=====
完整的代码放在这里意义不大,但要点是这样的:
- App 分离出 watchers 和 listener
听众:
- 侦听正在添加到数据库的文件,使用
fs.writeFile
将其导出
观察者:
- watcher 使用
chokidar
跟踪每个监视目录中添加的文件 - 找到时调用
fs.access
以确保我们可以访问该文件fs.access
似乎对正在写入的文件不感兴趣
- 文件通过
fs.createReadStream
使用,然后发送到服务器- 文件流是必需的,因为我们需要文件哈希
在这种情况下,文件被导出到监视目录,然后重新导入 通过观看过程。
编写锁态系统实际上非常简单。我找不到我在哪里做的,但我的想法是:
- 获取锁时创建锁文件,
- 释放锁时删除它们,
- 超时后删除它们,
- 每当请求锁定其锁定文件已存在的文件时抛出。
锁定文件只是单个目录中的一个空文件。每个锁定文件都从它所代表的文件的完整路径的哈希值中获取其名称。我使用了 MD5(相对较慢),但只要您确信路径字符串不会发生冲突,任何哈希算法都应该没问题。
这不是 100% 线程安全的,因为(除非我错过了一些愚蠢的东西)你不能自动检查文件是否存在并在 Node 中创建它,但在我的用例中,我持有锁定 10 秒或更长时间,因此微秒竞争条件似乎并没有太大的威胁。如果您每秒对同一文件持有和释放数千个锁,那么这种竞争条件可能适用于您。
显然,这些锁只是咨询锁,因此您需要确保您的代码请求锁并捕获预期的异常。
为此我会使用 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();
});