在node.js网络应用程序中避免竞争条件

时间:2014-06-10 23:26:22

标签: node.js design-patterns

我想知道如何设计这样的网络服务:

假设我有一个服务器正在侦听请求,它会收到一些密钥并检查它是否被缓存(例如使用某个数据库),如果它不进行某些处理,则生成答案,将其存储在缓存数据库中并返回答案客户端。

这似乎工作正常但是如果两个客户端请求相同的不存在的密钥会发生什么?在这种情况下会发生竞争条件,因此它看起来像

client 1 -> check cache DB -> generate answer -> store in cache -> reply to client
client 2 -> check cache DB -> generate answer -> store in cache -> reply to client

避免此问题的一种方法是在数据库中使用UNIQUE功能,因此每当生成第二个答案并将其写入数据库时​​,就会发生一些错误。这很好但似乎更像是一个补丁而不是一个真正的解决方案。特别地,想象一下生成答案需要大量处理的情况,然后其他更好的事情。

我能想到的一个选项是使用作业队列,因此无论何时收到密钥,密钥都会附加到现有作业,或者新作业会添加到队列中。

我一直在使用node.js几周,我很惊讶我没有找到显示这种用例的例子。所以我想知道这对于这样的案例是否是可接受的解决方案,还是更好的存在?

2 个答案:

答案 0 :(得分:2)

以下是在单进程设置中如何做到这一点:

var Emitter = require('events').EventEmitter;

var requests = Object.create(null);

function getSomething (key, callback) {

  var request = requests[key];

  if (!request) {
    request = requests[key] = new Emitter;

    getSomethingActually(key, function (err, result) {
      delete requests[key];
      if (err) return request.emit('error', err);
      request.emit('result', result);
    });
  }

  request.once('result', function (result) {
    callback(null, result);
  });

  request.once('error', function (err) {
    callback(err);
  });

}

如果你想扩展它,你需要使用一些外部存储+事件总线,比如redis。

答案 1 :(得分:0)

您应该使用作业队列(或其他类型的卸载作业)。应始终从主节点应用程序中取出处理密集型任务(通过队列,将其作为单独的进程生成等),否则它将阻止事件循环,从而阻止所有其他请求。

这就是说,如果您选择使用某种可以具有唯一约束的队列(例如postgres支持的队列),并在该键上设置唯一约束,则重复项将永远不会插入到工作队列中,所以永远不会被处理两次。在这种情况下,您可以简单地忽略唯一约束错误。

请注意,仍有可能(但不太可能)有一系列事件,例如:

  1. 请求检查'缓存'对于关键x,得到一个小姐
  2. 工作人员完成对密钥x的回答,将其插入'缓存',从队列中删除x
  3. 请求收到密钥x的未命中,将其添加到队列
  4. worker从队列中拉出键x,开始计算
  5. 在此(可能不太可能)事件序列之后,第二个工作人员在插入密钥时会出错。在我看来,这可能是一个不太可能的事件,添加一个唯一的键约束,而忽略第二个工作者的唯一约束违规错误可能是一个可行的选择。