【问题标题】:How can I lock a file while writing to it asynchronously如何在异步写入文件时锁定文件
【发布时间】:2016-06-07 13:50:41
【问题描述】:

我有两个节点线程正在运行,一个监视一个目录以进行文件消耗,另一个负责将文件写入给定目录。

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

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

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

=====

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

  1. 应用衍生出观察者和听众

听众:

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

观察者:

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

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

【问题讨论】:

  • 这是两个独立的程序,还是只是两个不同的功能?
  • 虽然它是一个应用程序,但监视类不知道另一个。此外,一旦完全实现,它们将被移动到衍生线程中
  • 如果你能提供一些代码也很棒。人们倾向于对没有代码的事情投反对票。

标签: node.js fs


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 这几乎是正确的,但就目前而言是完全错误的。锁定文件不是目录中的空文件。它是目录中的符号链接(至少在 Linux、Mac 等 unixen 上)。这是因为就像您所说的那样,创建、检查、读取和删除文件没有原子保证。然而,创建符号链接有原子保证。所以作者和读者都创建了锁文件。编写者创建锁定文件以检查它是否未被读取并锁定它。阅读器创建锁定文件来检查并锁定它。
  • 我正在查看lockfile 模块,但我不清楚在先锁定文件后如何写入文件。似乎 fs.writeFile 应该内置。
  • @Andrew,但是其他与您的进程不匹配的进程又如何在开始写入目录之前忽略整个“锁定过程”?
  • @Pacerier,您的代码必须运行良好。对我来说,这意味着为我的应用程序中的每个 fs-writing 服务分配一个特定的目录或路径,并且在服务需要写入的任何时候始终实现路径验证。 LogService 只能覆盖AvatarService 的锁定文件,如果它可以到达~/avatars/,所以不要让它。如果你在任何地方都这样做,那么你只需要在需要它的服务中实现锁定行为,而不必担心覆盖。如果您有两个服务可以写入相同的路径并且只有一个锁,那么您应该预料到会有麻烦。
  • 您可以使用{ flag: 'wx' } 写入文件,如果该文件已经存在,您将收到{ code: 'EEXIST' } 的错误,并且您知道无法获取锁。如果我没记错的话,这个操作原子的。
【解决方案2】:

我会为此使用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();
});

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-07
    • 2015-01-26
    • 2013-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多