如何在异步写入文件时锁定文件

and*_*ter 8 fs node.js

我有两个节点线程在运行,一个观察目录以使用文件,另一个负责将文件写入给定目录。

通常,它们不会在同一目录上运行,但对于我正在处理的边缘情况,它们将是。

似乎消费应用程序在文件被完全写入之前抓取文件,导致文件损坏。

有没有办法锁定文件直到写入完成?我已经研究过这个lockfile模块,但不幸的是我不相信它适用于这个特定的应用程序。

======

把完整的代码放在这里远远没有意义,但它的要点是:

  1. 应用程序脱离观察者和听众

听众:

  • 监听添加到数据库的文件,使用导出 fs.writeFile

观察者:

  • watcher 用于chokidar跟踪每个被监视目录中添加的文件
  • 当 foundfs.access被调用以确保我们可以访问该文件
    • fs.access 似乎对正在写入的文件无动于衷
  • 该文件通过消费fs.createReadStream然后发送到服务器
    • 文件流是必要的,因为我们需要文件哈希

在这种情况下,文件被导出到被监视的目录,然后被监视进程重新导入。

DJD*_*ark 11

我会为此使用正确的锁文件。您可以指定重试次数或使用重试配置对象来使用指数退避策略。这样您就可以处理两个进程需要同时修改同一个文件的情况。

这是一个带有一些重试选项的简单示例:

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();
});
Run Code Online (Sandbox Code Playgroud)


And*_*rew 4

编写锁状态系统实际上非常简单。我找不到我在哪里做的,但我的想法是:

  1. 每当您获取锁时创建锁文件,
  2. 释放锁时删除它们,
  3. 超时后删除它们,
  4. 每当为锁定文件已存在的文件请求锁定时抛出。

锁定文件只是单个目录中的空文件。每个锁定文件从它所代表的文件的完整路径的散列中获取其名称。我使用了 MD5(相对较慢),但是只要您确信路径字符串不会发生冲突,任何哈希算法都应该没问题。

这不是 100% 线程安全的,因为(除非我错过了一些愚蠢的事情)你无法自动检查文件是否存在并在 Node 中创建它,但在我的用例中,我持有锁 10 秒或更重要的是,微秒竞争条件似乎并没有那么大的威胁。如果您每秒在同一文件上持有和释放数千个锁,那么这种竞争条件可能适用于您。

显然,这些只是咨询锁,因此您需要确保您的代码请求锁定并捕获预期的异常。

  • 这几乎是正确的,但就目前而言是完全错误的。锁定文件不是目录中的空文件。它是目录中的符号链接(至少在 Linux、Mac 等 unixen 上)。这是因为正如您所说,创建、检查、读取和删除文件没有原子保证。然而,创建符号链接有原子保证。因此,写入者和读取者都创建锁定文件。编写者创建锁文件既是为了检查它是否未被读取,也是为了锁定它。读者创建锁文件来检查并锁定它。 (6认同)