在我的PHP应用程序(使用symfony框架和Propel ORM构建)中,当我向MYSQL数据库添加记录时,我需要使用外部供应商提供的Web服务API更新外部MYSQL数据库。
问题是维护数据库完整性的最佳做法是什么。例如,如果第一次更新成功,而第二次更新不成功,由于Web服务不可用,我必须能够
具体来说,我正在寻找像
这样的语法void RootMethod()
{
using(TransactionScope scope = new TransactionScope())
{
try
{
SomeMethod();
scope.Complete();
CallWebService();
}
catch
{
scope.abort();
}
}
}
但不确定是否
您怎么看?
编辑:有些人问我为什么需要更新两部分。这是因为我正在创建一个连接到现有后端应用程序的前端应用程序。我不想改变后端应用程序。因此不可避免地会有一些重叠。因此需要同步数据
另一个编辑:两个部分的事务必须一起完成,做一个cron作业来同步表是不可取的
答案 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
这种方法可靠地处理所有故障情况,并确保最终完成两项工作。
基本失败:
第一次提交完成前的失败:一切都回滚。
第一次提交后但在Web服务完成之前发生故障(这包括Web服务本身的瞬时故障):恢复任务重播远程Web服务事务。
Web服务完成后但第二次提交完成之前失败:恢复任务检测到重复的Web服务事务,并且本地记录已出列。
恢复任务失败:基本上与第二次交易中的失败相同。
其他说明:
渐进的退避方法对失败很有用。如果服务出现暂时故障,您希望减慢重试速度。
如果您对外部服务有订购要求,则可能需要一些额外的结构。
根据您实施恢复任务的方式,您可以将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)
是否有特定原因后端数据库必须与前端同时更新?
如果没有,一种方法是将前端更新为记录数据库,并将更新的记录标记为需要同步。内务处理任务可以定期从前端获取标记为需要同步的所有记录,然后更新后端。更新后端后,清除前端的同步标志。