【问题标题】:Spring Data MongoDB Concurrent Updates BehaviorSpring Data MongoDB 并发更新行为
【发布时间】:2019-06-22 06:34:43
【问题描述】:

假设有一个包含单个字段的文档:{availableSpots: 100}

并且有数百万用户通过向 API 服务器发送请求来争夺一席之地。

每次请求到来时,服务器都会读取文档,如果 availableSpot > 0,则将其减 1,并在另一个集合中创建预订。

现在我读到,每当执行更新操作时,mongodb 都会锁定文档。

如果有一百万个并发请求会发生什么?是否会因为同一个文档一直被锁定而需要很长时间?此外,服务器在尝试更新文档之前读取文档的值,并且在它获取锁时,该位置可能不再可用。

线程在同一时刻获得“availableSpot > 0”也是可能的,但实际上availableSpot 可能不足以满足所有请求。如何处理?

【问题讨论】:

    标签: mongodb concurrency mongodb-java


    【解决方案1】:

    这里最重要的是原子性和并发性。

    1.原子性

    如果可用Spots > 0,您要更新的操作(减一):

    db.collection.updateOne({"availableSpots" :{$gt : 0}}, { $inc: { availableSpots: -1 })
    

    是原子的。

    $inc 是单个文档中的原子操作。

    参考:https://docs.mongodb.com/manual/reference/operator/update/inc/

    2。并发 由于 MongoDB 具有用于写入操作的文档级并发控制。每次更新都会锁定文档。

    现在你的问题:

    如果有一百万个并发请求会发生什么?

    是的,每次更新都会一个一个地执行(由于锁定),因此会变慢。

    服务器在尝试更新文档之前读取文档的值 文件,并且在它获得锁时,该点可能不在 不再可用。

    由于操作是原子的,这不会发生。它将按您的意愿工作,仅会执行 100 次更新,受影响的行数大于 0 或等于 1。

    【讨论】:

      【解决方案2】:

      MongoDB 从 3.2 版开始使用 Wired Tiger 作为默认存储引擎。

      Wired Tiger 提供document level concurrency:

      来自文档:

      WiredTiger 使用文档级并发控制进行写入 操作。因此,多个客户端可以修改不同的 一个集合的文档。

      对于大多数读写操作,WiredTiger 使用乐观 并发控制。 WiredTiger 仅在全局使用意图锁, 数据库和集合级别。当存储引擎检测到 两个操作之间的冲突,一个会导致写冲突 导致 MongoDB 透明地重试该操作。

      当多个客户端尝试更新文档中的值时,只有该文档会被锁定,而不是整个集合。

      【讨论】:

      • 好的,但这仍然不能回答我的问题......当有一百万个同时更新同一文档的请求时会发生什么,我认为这是很常见的情况
      • 为了保证一致性,会锁定文档,防止多个客户端同时修改同一条数据
      【解决方案3】:

      我的理解是,您担心针对两个独立集合的许多并发 ACID 兼容事务的性能:

      • 一个包含一个文档的集合(我们称之为spots){availableSpots: 999..}
      • 另一个集合(让我们称之为bookings)包含多个文档,每个预订一个。 现在我读到 mongodb 会在执行更新操作时锁定文档。

      线程也有可能获得“availableSpot > 0” 在同一时刻为真,但实际上 availableSpot 可能不足以满足所有请求。如何处理?

      在 4.0 版中,MongoDB 提供了针对副本集执行多文档事务的能力。 (即将推出的 MongoDB 4.2 会将这种多文档 ACID 事务能力扩展到分片集群。)

      这意味着在事务提交之前,多文档事务中的任何写入操作(例如更新 spotsbookings 集合,根据您建议的方法)在事务外部可见。

      不过,正如 MongoDB documentation on transactions 中所述,非规范化方法通常会提供比多文档事务更好的性能:

      在大多数情况下,多文档事务会带来更好的性能 单个文档写入的成本,以及 多文件交易不应替代有效 架构设计。对于许多场景,非规范化数据模型 (嵌入式文档和数组)将继续是您的最佳选择 数据和用例。也就是说,对于许多场景,对数据进行建模 适当地减少对多文档事务的需求。

      在 MongoDB 中,对单个文档的操作是原子的。由于您可以使用嵌入式文档和数组来捕获单个文档结构中的数据之间的关系,而不是跨多个文档和集合进行规范化,因此这种单文档原子性消除了许多实际用例对多文档事务的需求。

      但请记住,如果您的用例在一个集合中实现为包含一个 availableSpots 子文档和数千个 bookings 子文档的单个非规范化文档,则可能不可行,因为最大文档大小为16MB。

      因此,总而言之,写入原子性的非规范化方法通常比多文档方法执行得更好,但受到最大文档大小 16MB 的限制。

      【讨论】:

        【解决方案4】:

        您可以在尝试更新文档时尝试使用findAndModify() 选项。每次您都需要在该特定文档中选择要更新的任何字段。此外,由于 mongo db 将数据复制到主节点和辅助节点,您可能还需要调整 WriteConcern 值。您可以在官方文档中阅读更多相关信息。我有类似的代码,可以使用 spring mongoTemplatemongoDB 中处理类似的并发问题。如果您需要任何与 java 相关的参考,请告诉我。

        【讨论】:

          猜你喜欢
          • 2012-05-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-21
          • 1970-01-01
          • 2012-08-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多