【问题标题】:Patterns for implementing transactions outside of a database在数据库之外实现事务的模式
【发布时间】:2010-10-09 11:34:05
【问题描述】:

我必须发送电子邮件、写入文件并调用网络服务。为了保持一致性,所有步骤都必须发生。如果任何步骤抛出异常或错误,则必须回滚所有步骤。

在我开始使用自己的对象 ACID 引擎之前,是否有任何普遍接受的模式用于在对象级别实现 ACID 语义?

更好的是,是否有任何现有的库可以用于 .NET 平台?

编辑:我知道发送电子邮件无法撤消,但无法连接到 SMTP 服务器会导致整个事务终止。另外,我希望它可以扩展以用于未来的操作。

【问题讨论】:

  • 如何回滚电子邮件?
  • 撤回会起作用。理论上,我可以将它从 Exchange 服务器中取出,就好像它从未发生过一样,但这可能会变得很可疑。
  • 怀疑它会起作用。如果电子邮件已被阅读,通常无法撤回。如果是 SMTP 发送,您将(几乎)永远无法回忆它。
  • 无论哪种方式,未能连接到 SMTP 中继足以导致事务终止,并且可以检测到。
  • 重点是,您需要为要回滚的每个操作定义一个 UNDO 操作。一旦你有了它,你的答案可能就像 try..catch 一样简单,catch 调用撤消操作。

标签: .net transactions acid


【解决方案1】:

我上一次看到这样的东西是几年前的事了。我记得的一点是它使用命令模式并将每个命令对象存储在队列中。我认为这是一个 LIFO 堆栈。

所以如果“事务”失败,引擎会弹出一个命令对象,撤消命令,然后销毁命令对象。重复直到堆栈为空。如果“事务”成功,堆栈就会被清除。

不幸的是,我不记得更多了。

CSLA.NET 实现了类似的撤消堆栈。这是我能想到的唯一代码示例。

【讨论】:

    【解决方案2】:

    当 ACID 语义可能不合适时,Windows Workflow Foundation 有一个 compensation 的概念(使用复合活动)。当然,它也支持 ACID 事务。

    一个很好的问题是为什么要打扰 赔偿?不是一种大酸吗 自动回滚事务 一样好吗? ACID 事务是 最合适的操作发生时 在同一数据库中或在同一数据库中 相同的信息系统。也是 操作结束时最合适 迅速地。当不同的公司和 涉及服务,定义 根据 ACID 语义处理 往往具有挑战性。让它成为 孤立和耐用,你必须保持 不同公司的所有资源 在任务期间锁定。 这往往是不合理的, 特别是如果任务很长。为了它 为了保持一致和原子,你需要 临时补偿代码。

    【讨论】:

      【解决方案3】:

      不严重依赖外部库的最简单技术是prevalence。通过使用序列化来定期检查点 对您的状态进行快照,然后通过序列化针对您的数据的每个副作用操作的足够信息来维护一个日志,以便以后重复。如果出现问题,请重新加载最近的检查点,然后重新应用在该点之后写入的所有日志记录。

      如需更复杂的内容,请尝试software transactional memory。在当前语言中实现可能有些笨拙,但功能非常强大,并且还可以为您提供一些额外的并发技术。

      对于访问 Web 服务或发送电子邮件等不可逆转的操作,您需要使用 compensating transactions:进行另一个 Web 服务调用以取消或更新前一个服务的结果,或者发送另一封电子邮件通知收件人事情没有按预期工作。

      【讨论】:

        【解决方案4】:

        由于您无法取消发送电子邮件,而且编写文件相对便宜,所以我会按照正确的顺序执行这些操作:

        1. 尝试写入文件/写入文件。如果不成功,停止,否则继续:
        2. 调用网络服务。如果不成功,删除文件并停止,否则继续:
        3. 发送电子邮件 - 电子邮件无论如何都是异步的,因此您永远不会真正知道它是否已发送,因为大多数电子邮件服务器都设置为在发生错误时重试几天而您永远不会收到返回确认电子邮件已通过,即使它成功成功。

        【讨论】:

        • 如果 Web 服务接收并处理调用但响应丢失,您可能仍会遇到麻烦。我在 eBay 的 AddItem 调用中遇到了这个问题,它具有花钱的不可逆转的副作用。
        • 在网络世界中,从不接收 ACK 与 NACK 是一样的,不是吗?发生这种情况时,一些人工干预通常是必要且不可避免的。
        【解决方案5】:

        一个想法是使用 JMS 作为“引擎”,您可以利用 JMS 事务(可以加入现有事务,例如 DB 事务)。这开始导致异步事件驱动架构,这可能是一件好事,除非我们谈论的是简单的应用程序——在这种情况下,您可能不需要问这个问题。

        简单的帐户创建就是一个例子。为此,您希望将帐户信息保存到数据库,并向用户发送一封电子邮件以进行激活 - 但出于显而易见的原因,您希望他们在同一事务中。

        您不应该将电子邮件发送代码放在事务中,因为即使您可以发送电子邮件 - db 事务提交也可能由于某种原因而失败。 您也不应该将电子邮件发送放在事务之外(在提交之后),因为电子邮件发送可能会失败并导致一个孤立帐户。

        所以要在这种情况下使用 JMS - 将 JMS 发送代码放在 DB 事务中并让它加入该事务。保证您的消息传递。在另一端有一些消耗队列发送电子邮件的东西。如果电子邮件发送失败,最好的选择是记录/发出警报 - JMS 将回滚并将消息放回队列以供以后使用。即,一旦您希望解决任何问题,就尝试重新发送电子邮件。

        关键是 - DB 记录是一致的,并且最终会发送电子邮件。

        【讨论】:

          【解决方案6】:

          两个想法:

          【讨论】:

            【解决方案7】:

            另外,最近发布了一个实验项目STM .NET。该项目将事务内存添加到 C#。它实际上修改了 CLR 以支持这一点。

            【讨论】:

              猜你喜欢
              • 2018-12-04
              • 1970-01-01
              • 1970-01-01
              • 2012-05-14
              • 2021-03-28
              • 2011-10-08
              • 1970-01-01
              • 2011-10-05
              • 1970-01-01
              相关资源
              最近更新 更多