您可以查看 MongoDB 的两阶段提交方法,或者您可以完全忘记事务并通过服务总线方法解耦您的流程。以亚马逊为例 - 他们将允许您提交订单,但在他们能够保护您的库存物品、从您的卡中扣款等之前,他们不会确认。这一切都不会发生在单笔交易中 - 这是一个可以单独发生的一系列步骤,并且可以在必要时应用补偿步骤。
一个简单的总线实现会执行以下操作(请记住,这只是一个通用的建议,供您使用,具体的实现将取决于您对并发的特定需求等):
- 将订单放在队列中。此时,您可以继续让您的客户等待,或者您可以感谢他们的订单,并让他们知道他们会在处理完成后收到一封电子邮件。
- “库存工作人员”将抓取订单并锁定需要保留的库存项目。这可以通过许多不同的方式来完成。使用 Mongo,您可以创建一个集合,其中每个 orderid 都有一个文档。该文档的 ID 是库存项目 ID 和一个合理的 TTL(比如 30 秒)。只要工人有锁,它就可以管理它有锁的物品的库存水平。一旦进行了更改,它就可以删除“锁定”文档。
- 如果另一个工作人员出现想要在锁定时管理相同的项目,您可以将被阻止的工作人员置于睡眠模式 X 秒,然后重试,或者更好的是,您可以将请求放回消息总线上以被拾取后来由另一名工人。
- 一旦工作人员解决了所有库存项目,它就可以在服务总线上放置另一条消息,指示应该对卡进行收费,或者处理应该收到通知以提取库存,或者可以向制作人发送电子邮件订单等
听起来很复杂,但是一旦您设置了消息总线,它实际上就相对简单了。 可以在此处找到节点消息总线实现的列表。
一些开发人员甚至会完全跳过正式的消息总线并使用数据库作为他们的消息传递引擎,它可以在简单的实现中工作。谷歌 Mongo 和队列。
如果您不希望超过 1 个服务器并且消息总线实现过于庞大,节点可以为您处理锁定和消息传递。例如,如果您真的想用节点锁定,您可以创建一个存储库存项目 ID 的数组。虽然,坦率地说,我认为消息总线是最好的方式。不管怎样,这里有一些我过去用来处理简单的外部资源锁定的代码。
// attempt to take out a lock, if the lock exists, then place the callback into the array.
this.getLock = function( id, cb ) {
if(locks[id] ) {
locks[id].push( cb );
return false;
}
else {
locks[id] = [];
return true;
}
};
// call freelock when done
this.freeLock = function( that, id ) {
async.forEach(locks[id], function(item, callback) {
item.apply( that,[id]);
callback();
}, function(err){
if(err) {
// do something on error
}
locks[id] = null;
});
};