6

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

问题是维护数据库完整性的最佳实践是什么。举个例子,如果第一次更新成功,第二次更新失败,由于网络服务不可用,我必须能够

  1. 回滚第一次更新的事务,或者
  2. 缓存对 Web 服务的调用并继续调用 Web 服务,直到服务可用
  3. 其他一些可以维护多个数据库完整性的技术。

具体来说,我正在寻找类似的语法

void RootMethod()
{
     using(TransactionScope scope = new TransactionScope())
     {
        try
         { 
          SomeMethod();
          scope.Complete();
          CallWebService();
         }
         catch
         {
             scope.abort();
          }
     }
}

但不确定是否

  1. 这是一个很好的技术
  2. 或者这在 symfony 中是否可行,就像在 C# 中一样

你怎么看?

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

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

4

8 回答 8

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. 恢复任务中的失败:与第二个事务中的失败基本相同。

其他注意事项:

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

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

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

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

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

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

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

如果您确实找到了解决此问题的解决方案,该解决方案满足了两个独立系统在多个事务中保持一致的要求,并且在发生故障后不再需要后续协调,您应该将其写下来并发表在 The VLDB Journal、ACM TODS 或 IEEE TKDE .

于 2009-02-23T11:32:43.127 回答
4

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

也许实际上并不需要一个很好的解决方案。您是否处于困难的性能限制之下?一般来说,交易时间应该很短......但也许你应该在网络服务调用周围保持交易开放?这将降低数据库的整体吞吐量(至少)......但这可能是完全可以接受的。

您展示的方法在处理硬系统故障(电源故障、硬件故障等)时会遇到问题。要解决这个问题,您需要向主数据库和后台进程/启动进程添加跟踪以处理故障。做起来很繁琐,但肯定有可能。

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

祝你好运。

于 2009-02-22T03:24:17.647 回答
3

我不认为回滚真的会帮助这种情况。如果您的 Web 服务出现故障,则进行更多调用只会使问题更加复杂,然后您必须担心回滚是否通过等。

我会通过预定的完全同步来做到这一点。你的误差范围是多少?你愿意让数据库稍微不同步吗?多少?每天晚上运行一个同步器来解决任何突然出现的问题会很重要吗?您需要担心的 Web 服务多久关闭一次?

失败的 Web 服务调用的更新队列是一个不错的主意,但是如果您的 Web 服务出现故障,可能会同时出现很多这样的情况,而不仅仅是一两个,所以您不妨在一个反正停电。

你的答案真的取决于这些问题。如果 0.01% 不同步 10 分钟,请不要错误地假设您的整个程序会崩溃。找出可接受的错误余量是多少。

于 2009-02-24T18:10:25.707 回答
2

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

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

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

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

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

于 2009-02-23T11:01:01.317 回答
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

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

于 2009-02-26T15:57:56.553 回答
1

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

于 2009-02-23T11:11:36.237 回答
1

也许您可以尝试不将这种多次更新的逻辑放在您的应用程序中,而是使用一个外部进程,该进程知道何时更新以及如果更新失败该怎么办。例如,Oracle BPEL 就是这样一个流程。您可以将其配置为协调不同的服务,例如参见http://alisonatoracle.blogspot.com/2006_01_01_archive.html

不过,这可能对您的应用程序来说太过分了,具体取决于它的大小......

于 2009-02-26T20:12:37.753 回答
1

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

如果没有,一种方法是将前端更新为您的记录数据库,并将更新的记录标记为需要同步。管家任务可以定期从前端提取所有标记为需要同步的记录,然后更新后端。当后端更新完毕后,清除前端的同步标志。

于 2009-02-27T18:47:27.573 回答