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

问题描述 投票:0回答:4

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

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

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

有没有办法可以锁定文件直到写入完成?我研究了

lockfile
模块,但不幸的是我不相信它适用于这个特定的应用程序。

======

完整的代码放在这里远非有意义,但其要点是:

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

听众:

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

守望者:

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

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

node.js fs
4个回答
16
投票

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

4
投票

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

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

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

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

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


0
投票

使用这个专为此类场景设计的软件包

https://www.npmjs.com/package/node-locksmith


-1
投票

重命名文件是原子的。 使用某个特定名称(例如扩展名)写入文件,当写入完成且文件关闭时,将其重命名为其他特定名称。仅注意具有第二个特定名称的文件。 或者将文件重命名到另一个(子)目录中。 当底层操作系统暴露部分刷新的关闭文件时,可能会出现唯一的问题,不太可能

© www.soinside.com 2019 - 2024. All rights reserved.