【问题标题】:MongoDB multi step transaction problemMongoDB多步事务问题
【发布时间】:2011-07-15 18:20:38
【问题描述】:

如果我们需要更新 CUSTOMER 文档并在同一事务中发送电子邮件,那么确保以原子方式完成此操作的最佳方法是什么?我们正在构建一个 ecomm 网站,我们需要这个功能,因为当客户购买商品时,我们必须更新他们的订单历史记录并向他们发送电子邮件确认。在使用 RDBMS 数据库的 Java 中,我们很容易做到这一点,只需更新数据库并发送包含电子邮件内容和详细信息的 JMS 消息; JDBC 和 JMS 都支持分布式事务,因此如果出现问题,都可以回滚,但 MongoDB 则不然。 Mongo 中有消息传递功能吗?

我们正在考虑在 Customer 的 orderHistory 嵌入文档中使用标志“emailSentFlag”。下订单时,该标志设置为 false。然后,我们将使用一个外部作业来扫描 emailSentFlag="false" 的所有订单历史记录并在那时发送电子邮件,但这会使我们回到相同的情况,因为我们必须在发送后将标志设置回“true”电子邮件,这不是原子的。

> customer {
>     name:
>     email:
>     orderHistory{
>        orderId:
>        status:
>        emailSentFlag:

【问题讨论】:

  • 小心! MongoDB 不支持事务! mongodb.org/display/DOCS/…
  • 我知道,但我必须让两个操作一起工作,有什么建议吗?
  • JMS 如何确保邮件发送一次且准确一次?当然,它可以将它放入队列并确保它至少被处理一次,但它可能会在发送邮件到 smtp 之后以及在设置作业完成之前崩溃,因此它会再次执行它。我错过了什么吗?

标签: email mongodb transactions


【解决方案1】:

其他人说的Mongo中没有交易。

我建议您建立一个邮件队列,而不是设置一个标志,然后搜索整个集合。我宁愿在邮件队列中放入一个条目,然后使用外部作业处理该队列。

至少我会先尝试这样做。任何人,任何评论是好是坏?

【讨论】:

    【解决方案2】:

    您可以在 DOCUMENT 级别执行原子操作: http://www.mongodb.org/display/DOCS/Atomic+Operations

    我有一个用于排队您的电子邮件的集合,并在电子邮件文档中放置一个字段以在发送电子邮件时更新记录。然后让您的发送作业在发送完成后更新父记录。

    您甚至可以使用电子邮件文档和客户文档中的数据库参考。

    【讨论】:

    • 我们的想法是一样的,但是你的建议不是有同样的问题吗?使用 Mongo 更新父记录和发送电子邮件不能是原子的。
    • 查看我的评论。我认为您不能在本质上不是事务性的东西(电子邮件到 smtp)之上构建事务。当然,它会像一年一次那样崩溃,然后您会再次重新发送该邮件,没有人会真正抱怨重复的邮件(尤其是如果它发生在一百万个案例中)。
    【解决方案3】:

    值得思考这些动作是否真的需要以原子方式发生:

    • 添加订单历史订单项
    • 发送确认邮件

    发送电子邮件的回滚标准是什么?无效的邮件地址?无法连接到邮件服务器?电子邮件已发送但未收到? 您想因为邮件服务器停机而取消客户的交易吗?

    我会说可以使用customer.orderHistory.emailSentFlag 创建一个新订单行,然后安排一个计划作业来处理emailSentFlag = 0 订单以发送确认电子邮件,然后在电子邮件成功发送后设置标志.

    如果您收到退回邮件或由于某种原因交付失败,您可以随时重置标记并重试。或者更好的是,将发送电子邮件的工作分派到一个为您处理所有这些事情的邮件队列系统。

    【讨论】:

      【解决方案4】:

      这是一个无模式的数据库,所以好好利用它吧! :-) 您可以使用鸭式输入,但将字段添加到跨类型识别的任何文档类型。确保使用文档中未使用的名称。

      在任何包含任务(如发送电子邮件)的文档中添加一个tasks: [] 数组。当你更新一个文档并且你需要一个与之关联的“原子”事物时,将一个任务推送到这个队列。例如,对于电子邮件:{ email: "cust@client.com", at: 0 }。后台进程轮询这些任务,将它们设置为将来到期,执行它们并删除它们。如果某处发生故障,任务将过期并重新运行。因此,您可能会发送两封邮件,但这在邮件中或多或少是不可避免的,即使是交易也是如此。

      你可以在 mongodb shell 中运行它:

      #db.orders.remove()
      db.orders.save( { name: "abc", tasks : [  { email: 'a@dom.com', at: 0}, { email: 'b@dom.com', at: 0} ] })
      db.orders.save( { name: "def", tasks : [  { email: 'c@dom.com', at: 0}] })
      
      now  = 10; future = 100; me = "some unique identifier for this scanning process"
      
      while ( true ) {
        task = db.orders.findAndModify({
          query:   { "tasks.at": { $lt: now }},
          update : { $set : { "tasks.$.at": future, "tasks.$.who": me } }, 
          fields:  { "tasks":1},
          new:     true } )
      
        if ( !task)
          break;
      
        for ( var i in task.tasks ) {
          if ( task.tasks[i].who === me )
            print( "Send email " + task.tasks[i].email )  
        }
      
        db.orders.update( { _id: task._id }, { $pull: { tasks: { who: me } } } )
      }
      

      查看http://www.mongodb.org/display/DOCS/findAndModify+Command 的类似示例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-08
        • 1970-01-01
        • 1970-01-01
        • 2019-03-28
        • 1970-01-01
        • 2011-10-01
        相关资源
        最近更新 更多