多个数据库事务

时间:2009-02-18 09:06:42

标签: database-design symfony1

在我的PHP应用程序(使用symfony框架和Propel ORM构建)中,当我向MYSQL数据库添加记录时,我需要使用外部供应商提供的Web服务API更新外部MYSQL数据库。

问题是维护数据库完整性的最佳做法是什么。例如,如果第一次更新成功,而第二次更新不成功,由于Web服务不可用,我必须能够

  1. 回滚第一次更新的交易,或
  2. 缓存对Web服务的调用并继续调用Web服务,直到服务可用
  3. 可以维护多个数据库完整性的其他一些技术。
  4. 具体来说,我正在寻找像

    这样的语法
    void RootMethod()
    {
         using(TransactionScope scope = new TransactionScope())
         {
            try
             { 
              SomeMethod();
              scope.Complete();
              CallWebService();
             }
             catch
             {
                 scope.abort();
              }
         }
    }
    

    但不确定是否

    1. 这是一项很好的技巧
    2. 或者这在symfony中是可行的,就像在C#
    3. 中一样

      您怎么看?

      编辑:有些人问我为什么需要更新两部分。这是因为我正在创建一个连接到现有后端应用程序的前端应用程序。我不想改变后端应用程序。因此不可避免地会有一些重叠。因此需要同步数据

      另一个编辑:两个部分的事务必须一起完成,做一个cron作业来同步表是不可取的

8 个答案:

答案 0 :(得分:5)

最大的问题是,对Web服务的重复更新是否重要,以及是否可以检测到它们。如果您可以检测重复项(通常使用唯一的事务编号),或者重复项无关紧要,那么您可以构建一个可靠的两阶段提交样式方法。

如果无法检测到Web服务的重复事务且更新不是幂等的,那么您就不走运了。

这是基本算法:

begin transaction;
do local work;
save information for external call;
set an appropriate time for next attempt;
mark external call as not performed;
commit work;

begin transaction;
make external call;
if successful
   mark external call as performed (or delete the record)
else
   set the time for the next attempt
commit;

然后,您需要一个常规任务,线程或其他类似的东西:

for each record where the time for the next attempt <= now
    begin work;
    if the remote service has not performed this transaction
        make the remote call;
        if successful
            mark as done;
        else if too many attempts
            mark the transaction as permanently failed;
            alert operator;
        else
            set the time for the next attempt;
        endif
    else
        mark as done;
    endif

    commit;
 endfor

这种方法可靠地处理所有故障情况,并确保最终完成两项工作。

基本失败:

  1. 第一次提交完成前的失败:一切都回滚。

  2. 第一次提交后但在Web服务完成之前发生故障(这包括Web服务本身的瞬时故障):恢复任务重播远程Web服务事务。

  3. Web服务完成后但第二次提交完成之前失败:恢复任务检测到重复的Web服务事务,并且本地记录已出列。

  4. 恢复任务失败:基本上与第二次交易中的失败相同。

  5. 其他说明:

    • 渐进的退避方法对失败很有用。如果服务出现暂时故障,您希望减慢重试速度。

    • 如果您对外部服务有订购要求,则可能需要一些额外的结构。

    • 根据您实施恢复任务的方式,您可以将Web服务调用留给该任务,而不是在主应用程序流中拥有第二个事务。

    对附加要求的回应:“两部分交易必须一起完成,做一个cron作业来同步表是不可取的”

    我对这个要求的解读是:“这两个系统永远不会失败。”

    当其中一个或两个系统发生故障时,您需要一些东西来拾取碎片并协调一些事情。您可以使用完全成熟的TP监视器来进行事务协调,或者您可以构建一个简单的监视器,就像我的示例中处理您的特定情况的监视器一样。无论哪种方式,都有一些东西可以跟踪发生的事情,以便在发生故障后能够正确解决问题。

    如果您的要求确实是事情总是一起发生(并且事务性消息队列或两阶段提交方法对您不起作用),那么最好将两个系统的数据存储在同一个数据库中(也称为“资源管理器“)并且只有一个资源管理器事务。

    如果你确实得到了这个问题的解决方案,该解决方案满足了在多个事务中保持两个独立系统一致的要求,并且在失败后永远不需要后续协调,那么你应该将其编写并在VLDB Journal,ACM TODS中发布。或IEEE TKDE。

答案 1 :(得分:4)

这将是棘手的。您需要两阶段提交才能获得可靠的解决方案,但这需要为您的特定需求而实施大量工作。

也许并没有真正需要一个好的解决方案。您是否面临困难的性能限制?一般来说,交易时间应该很短......但也许您应该围绕网络服务电话保持交易开放?这会降低数据库的整体吞吐量(至少)......但这可能是完全可以接受的。

您展示的方法在处理硬系统故障(电源故障,硬件故障等)时会遇到问题。要解决此问题,您需要将跟踪添加到主数据库以及后台进程/启动过程以处理故障。非常繁琐,但肯定是可能的。

某些故障可能最终无法修复(第一部分成功,第二部分失败,第一部分无法撤消,因为另一个事务已更新相同数据)。这一切都取决于您的确切业务规则。会计系统是最简单的,因为撤消交易实际上是作为违规记录而不是更新。

祝你好运。

答案 2 :(得分:3)

我认为回滚确实无助于这种情况。如果您的网络服务中断,拨打更多电话只会使问题更加复杂,然后您必须担心您的回滚是否通过等。

我会使用预定的完全同步执行此操作。你的错误保证金是多少?您是否愿意让数据库略微不同步?多少钱?每晚都要运行一个同步器以解决任何问题,这是一件大事吗? Web服务的频率是多少,你不得不担心这个?

失败的Web服务调用的更新队列是一个不错的主意,但是如果您的Web服务停止运行,那么可能会同时存在大量这些服务,而不仅仅是一两个,所以您不妨完成无论如何都要在停电后同步。

真的,你的答案取决于那些问题。如果事情在10分钟内不同步0.01%,那么不要误以为假设整个程序都会崩溃。找出错误的可接受余量。

答案 3 :(得分:2)

保持数据库同步是一项艰巨的任务,具体取决于您拥有的数据,您是否可以添加另一个包含已更改内容的表,然后通过cron或单独的代码运行单独的脚本,以尝试更新Web服务并将其带入与存储在数据库中的更改同步。如果更改成功,则会删除指定尚未将更改发送到远程服务器的标志。

在插入数据后的本地数据库上,您可以使用一个标志来指定它不应该生效,然后同步的任何和所有数据都会将该标志更改为完全提交。

这样做的具体原因是什么?为什么你需要在应用程序本身中保持两个数据库同步,你是否能够每小时同步一次?

这将需要状态跟踪数据以及是否已成功提交到两端。

我个人的选择是1号。回滚本地交易,除非绝对不可能使用2号。

答案 4 :(得分:2)

不要自己尝试构建两阶段事务管理逻辑。你会弄错的,相信我。如果它在您的proggy环境中可用,就像它在C#中一样,那么就使用它。如果没有,那就不要自己建造它。

在大多数情况下,使用跨多个在线数据库的分布式事务设计系统更容易,但如果系统的操作方面一起生活,则更容易它包括一个队列,用于(1)面对网络不可用时的弹性,(2)面对高负载时的一致延迟行为。

所以,将您的交易限制在本地资源:

make a change to reliable store (I believe this would be called a "resource" in X/Open transaction parlance)
enqueue a record of that change in a disk-backed log

然后,按时间表(每小时,每天,无论如何)

while not done
    pop item from queue or log
    sync that change with the external, remote resource

在高负载时,您的队列将填满,但网络负载和事务延迟将保持相对稳定。这有点像您家庭取暖费的月度预算计划。在负载相对较低的情况下,队列将耗尽。

答案 5 :(得分:1)

我不明白,你的应用程序是PHP还是C#?如果它在C#(WCF)中并且Web服务是WCF(或支持WS-AtomicTransaction),那么这是可能的。

答案 6 :(得分:1)

也许您可以尝试不在您的应用程序中放置多个更新的逻辑,但使用外部进程知道更新失败时何时以及如何更新。例如,Oracle BPEL就是这样一个过程。您可以将其配置为编排不同的服务,例如,请参阅http://alisonatoracle.blogspot.com/2006_01_01_archive.html

虽然可能会对您的应用程序造成过大的影响,具体取决于它的大小......

答案 7 :(得分:1)

是否有特定原因后端数据库必须与前端同时更新?

如果没有,一种方法是将前端更新为记录数据库,并将更新的记录标记为需要同步。内务处理任务可以定期从前端获取标记为需要同步的所有记录,然后更新后端。更新后端后,清除前端的同步标志。