【问题标题】:How can I synchronize access to a file within a single process in node.js?如何在 node.js 的单个进程中同步对文件的访问?
【发布时间】:2012-10-23 17:35:02
【问题描述】:

我有一个 Web 服务器,可以读取和写入磁盘上的数据文件。我希望仅在单个 Web 请求中写入文件。

这是一个说明我的问题的示例程序。它在“/tmp/rw.txt”中保存一个状态文件,并在每次 Web 命中时递增整数内容。运行这个程序,然后运行类似ab -n 10000 -c 1000 http://localhost:3000/ 的东西,表明多次命中从文件中读取了相同的值,并且多次写入。

注意:我知道flock() 和fs-ext。但是,flock() 会将文件锁定到当前进程;由于这里的所有访问都在同一个进程中,flock() 不起作用(并且使这个示例相当复杂)。

另外请注意,我通常会使用 express、async 等来完成大部分工作;只是为了举例而坚持基础。

var http = require("http"),
    fs = require("fs");

var stateFile = "/tmp/rw.txt";

var server = http.createServer(function(req, res) {

    var writeNum = function(num) {
        var ns = num.toString(10);
        console.log("Writing " + ns);
        fs.writeFile(stateFile, ns, function(err) {
            if (err) {
                res.writeHead(500, {"Content-Type": "text/plain"});
                res.end(err.message);
            } else {
                res.writeHead(200, {"Content-Type": "text/plain"});
                res.end(ns);
            }
        });
    };

    switch (req.url) {
    case "/reset":
        writeNum(0);
        break;
    case "/":
        fs.readFile(stateFile, function(err, data) {
            if (err && err.code == "ENOENT") {
                // First time, set it to zero
                writeNum(0);
            } else if (err) {
                res.writeHead(500, {"Content-Type": "text/plain"});
                res.end(err.message);
            } else {
                writeNum(parseInt(data, 10) + 1);
            }
        });
        break;
    default:
        res.writeHead(404, {"Content-Type": "text/plain"});
        res.end("No such resource: " + req.url);
    }
});

server.listen(3000);

【问题讨论】:

  • 本案例与node.js无关。这是一个多线程功能。您不能保证下一个请求会从文件中读取存储上一个请求的值。如果它们之间的时间小于一毫秒或类似(小于磁盘访问),第二个请求将不会知道先前的请求正在更新文件。你可以写一个队列或类似的东西,但这不是必需的,所有的互联网都是这样工作的,因为访问磁盘是 sloooowwww。
  • @GabrielLlamas 您是否尝试运行示例代码?它是用 node.js 编写的,并说明了问题。大多数多线程系统都有锁定机制,如内置的信号量、互斥锁和同步部分。目前,我认为最好的方法是使用进程内锁定机制。

标签: node.js synchronization fs flock


【解决方案1】:

将数据存储在文件中并不是 Web 服务器等多用户环境中的首选方式。数据库更适合于此。但如果你真的想坚持使用文件,我建议使用缓冲区。也就是说,您可以写入/读取的内存对象,以及定期将其内容转储到磁盘的单独函数,如下所示:

var server = http.createServer(function(req, res) {
    ----- cut ----
    switch (req.url) {
        case "/reset":
            value = 0;
            break;
        case "/":
            value ++;
            break;
        default:
            res.writeHead(404, {"Content-Type": "text/plain"});
            res.end("No such resource: " + req.url);
        }
    }
    ----- cut ----
}

(function() {
    var cb = arguments.callee;
    fs.writeFile(stateFile, value, function(err) {
        // handle error
        setTimeout(cb, 100); // schedule next call
    });
})();

【讨论】:

  • 示例代码是一个显示问题的示例——异步代码中对资源的非原子访问。该资源可以是数据库记录(“SELECT”然后是“UPDATE”)、文件,甚至是内存中的资源。
  • 在对我的回答投反对票之前,您是否尝试过重新阅读您的问题并找到您提到的除抽象文件读写问题之外的任何内容?您需要学习如何以清晰的方式表达问题。
  • 我确实清楚地表达了这个问题。你犯了一个典型的错误,试图以更聪明的方式重新实现我的示例代码。我不需要应用程序来增加数字。我需要跨多个异步请求同步对文件的访问,这正是这里的问题所说的。按照给出的问题回答问题,而不是试图智取提问者。
【解决方案2】:

另一种方法是创建一个处理队列(如果您的问题背后有问题,所以您不接受简单的解决方案;))是创建一个处理队列。下面是一个简单的队列模式,它按照请求提交的顺序执行请求,并在函数执行后向提供的回调返回错误(如果有)。

var Queue = function(fn) {
  var queue = [];
  var processingFn = fn;

  var iterator = function(callback) {
      return function(err) {
        queue.shift();  // remove processed value
        callback(err);

        var next = queue[0];
        if(next)
          processingFn(next.arg, iterator(next.cb));
    };
  }

  this.emit = function(obj, callback) {
    var empty = !queue.length;
    queue.push({ arg: obj, cb: callback});

    if(empty) { // start processing
      processingFn(obj, iterator(callback));
    }
  }
}



function writeNum(inc, continueCb) {
  fs.readFile(stateFile, function(err, data) {
    if(err)
      return continueCb(err);

    var value = data || 0;
    fs.writeFile(stateFile, value + inc, function(err) {
      continueCb(err);
    });
  });
}


var writer = new Queue(writeNum);

// on each request
writer.emit(1, function(err) {
  if(err) {
    // respond with error
  }
  else {
    // value written
  }
});

【讨论】:

  • 我认为使用队列是行不通的,因为你会排队一堆读取和写入,例如:读取、读取、读取、读取、写入、写入、写入、写入、读取,写,... ...真正你想要的是:读,写,读,写,读,写,读,写,...
  • 你试过了吗?它以您想要的方式解决示例中的问题:读取,然后写入,然后移动到下一个队列项。
【解决方案3】:

我无法找到符合我要求的库,所以我在这里创建了一个:

https://npmjs.org/package/schlock

这是上面使用读/写锁定的示例程序。我还使用了“Step”来使整个内容更具可读性。

var http = require("http"),
    fs = require("fs"),
    Schlock = require("schlock"),
    Step = require("step");

var stateFile = "/tmp/rw.txt";

var schlock = new Schlock();

var server = http.createServer(function(req, res) {

    var num;

    Step(
        function() {
            schlock.writeLock(stateFile, this);
        },
        function(err) {
            if (err) throw err;
            fs.readFile(stateFile, this);
        },
        function(err, data) {
            if (err && err.code == "ENOENT") {
                num = 0;
            } else if (err) {
                throw err;
            } else {
                num = parseInt(data, 10) + 1;
            }
            fs.writeFile(stateFile, num.toString(10), this);
        },
        function(err) {
            if (err) throw err;
            schlock.writeUnlock(stateFile, this);
        },
        function(err) {
            if (err) {
                res.writeHead(500, {"Content-Type": "text/plain"});
                res.end(err.message);
            } else {
                res.writeHead(200, {"Content-Type": "text/plain"});
                res.end(num.toString(10));
            }
        }
    );
});

server.listen(3000);

【讨论】:

    【解决方案4】:

    您可以使用fs.writeFileSync 和 readFileSync。

    【讨论】:

    • 必须从 node.js 中删除同步功能,因为这有助于错误地使用 node.js。如果您编写一个 node.js 应用程序,那么所有应用程序都需要是异步的,否则您将失去使用 node.js 的所有好处...
    • @OneOfOne 这绝对是一种解决方案。问题是它锁定了整个应用程序。尽管在此示例中,每次点击都会写入资源,但您可以想象一个包含数十万个文件的程序(例如,将每个页面存储在 markdown 文件中的 wiki)。这使得所有请求都等待他们(可能)没有使用的资源。
    • Web 应用程序不应该阻塞,不应该使用任何锁定系统。特别是在 node.js 中阻止事件循环是被禁止的。
    • 唯一的其他解决方案是在可能阻塞的不同进程中处理写入,这里没有那么多选项可以做你想做的事。
    【解决方案5】:

    我刚刚通过npm search 找到的一个解决方案是储物柜服务器:https://github.com/bobrik/locker

    我认为这是解决问题的好方法,而且是关于我如何设计它。

    最大的问题是它处理需要单独服务器的一般情况(使用资源的多个进程)。

    我仍在寻找可以做同样事情的进程内解决方案。

    【讨论】:

      猜你喜欢
      • 2010-10-03
      • 1970-01-01
      • 2015-03-11
      • 2015-07-04
      • 2015-07-24
      • 2012-12-30
      • 1970-01-01
      • 2011-02-12
      • 2011-04-05
      相关资源
      最近更新 更多