避免node.js web应用程序中的竞争条件

我想知道如何devise一个这样的Web服务:

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

这似乎工作正常,但如果两个客户端请求相同的不存在的密钥会发生什么? 在这种情况下会出现竞争状况,所以看起来像

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 

避免此问题的一种方法是使用DB中的UNIQUE特性,因此无论何时生成第二个答案并写入数据库,都会发生一些错误。 这很好,但似乎更像是一个补丁,而不是一个真正的解决scheme。 特别是,设想一个产生答案需要大量处理的情况,那么其他的东西会更好。

我能想到的一个select是使用作业队列,所以无论何时收到一个密钥,密钥要么附加到现有的作业,要么将新的作业添加到队列中。

我一直在玩node.js几个星期,我很惊讶,我还没有发现这种用例的例子。 所以我想知道这是一个可接受的解决scheme这样的情况下,还是更好的存在?

以下是您可以如何在单一stream程设置中执行此操作:

 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。

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

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

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

  1. 请求检查密钥x的'caching',得到一个错过
  2. 工作人员完成键x的答案,将其插入“caching”中,从队列中删除x
  3. 请求收到关键x的遗漏,将其添加到队列中
  4. 工作人员从队列中提取密钥x,开始计算

在此(可能不太可能)的事件序列之后,第二个工作人员会在插入密钥时出错。 在我看来,这可能是一个不太可能的事件,添加一个唯一的密钥约束,而忽略第二个工人的唯一约束违规错误可能是一个足够可行的select。