如何在node.js中的单个进程内同步对文件的访问?

时间:2012-11-03 03:45:50

标签: node.js synchronization fs flock

我有一个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);

5 个答案:

答案 0 :(得分:2)

在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
    });
})();

答案 1 :(得分: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
  }
});

答案 2 :(得分:2)

我无法找到满足我想要的库,所以我在这里创建了一个:

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);

答案 3 :(得分:1)

您可以使用fs.writeFileSync和readFileSync。

答案 4 :(得分:1)

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

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

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

我仍然在寻找能够做同样事情的进程内解决方案。