我有一个REST (分布在多个主机/容器中),它使用MongoDB作为数据库。在我的数据库中的集合中,我想关注本例中的Users和Games集合。
假设我有一个名为/join_game的端点(由用户/客户端调用)。此端点将逐步通过以下逻辑:
Games模型)Games模型中的参与者字段并更新该文档Users文档中的一些字段(stats等)并让另一个端点(由服务器上的cron作业调用)称为/close_game,它通过以下逻辑执行:
Games模型)Users模型中的更新)现在,我知道在每个端点处理的两个并发请求之间可能存在以下争用条件:
/join_game控制器调用的请求A到/join_game检查游戏是否是打开的(因此它继续进行端点逻辑的其余部分)/close_game - /close_game控制器将游戏设置为在游戏文档中关闭如果这些请求是并发的,请求A在请求B之前被调用,那么剩余的/join_game逻辑可能会被执行,尽管游戏技术上是锁定的。现在,这是我不想看到的明显的行为,可以带来许多错误/意想不到的结果。
为了防止这种情况,我研究了使用transaction,因为它使事务中的所有数据库操作都是原子化的。但是,我不确定事务是否真的解决了我的问题,因为我不确定它们是否对被查询和修改的文档设置了完整的锁(我看到mongodb使用共享锁进行读取,而使用排他锁进行写入)。如果它们确实设置了一个完整的锁,那么对事务中那些文档的其他数据库调用是否会等待这些事务完成呢?我还读到,如果事务在一段时间后等待(这也会导致不必要的行为),事务就会中止。
如果事务不是防止跨多个不同端点的竞争条件的方法,我想知道有什么好的替代方法。
最初,我使用内存中的队列来处理这些争用条件,这些条件似乎是在一个节点上运行REST的服务器上工作的。但是随着我的扩展,在分布式服务器中管理这个队列将成为一个更大的问题,所以如果可能的话,我想直接在mongo中处理这些竞争条件。
谢谢!
发布于 2022-07-11 08:48:39
据我理解,您似乎不需要使用MongoDB中的事务,但可以使用MongoDB原子更新操作。
文档的每个操作一次只执行一个,这意味着如果您加入一个游戏,而关闭游戏被调用并首先执行,那么您就无法加入。
这就是为什么模式设计很重要的原因,您需要弄清楚如何对文档建模以允许原子更新来解决并发问题。用于更新其他集合,即视图,这可能是玩家赢/输/加入游戏的次数。我会让这一切最终在事件背后一致。
您还可以使用FindOneAndModify操作,它可以在更新完成后返回文档的新状态。这对于处理并发性非常有用。
db.test.insert({ _id: 1, name: "Game 1", players : [ ], isClosed: false})
db.test.insert({ _id: 2, name: "Game 2", players : [ ], isClosed: false})
db.test.insert({ _id: 3, name: "Game 2", players : [ ], isClosed: false})
db.test.update({ _id: 1, isClosed: false}, {$push: { players: 20} })
db.test.update({ _id: 1, isClosed: false}, {isClosed: true, players : [] })https://stackoverflow.com/questions/72933736
复制相似问题