确保事件最终发布到消息排队系统的最佳方法

时间:2015-06-11 12:36:16

标签: c# events rabbitmq message-queue eventual-consistency

请假设您有以下方法:

public void PlaceOrder(Order order)
{
     this.SaveOrderToDataBase(order);
     this.bus.Publish(new OrderPlaced(Order));    
}

将订单保存到数据库后,会向消息排队系统发布一个事件,因此同一台机器或另一台机器上的其他子系统可以处理它。

但是,如果this.bus.Publish(new OrderPlaced(Order))调用失败会发生什么?或者在将订单保存到数据库后机器崩溃了?事件未发布,其他子系统无法处理。这是无法接受的。如果发生这种情况,我需要确保最终发布该事件。

我可以使用哪些可接受的策略?哪个是最好的?

注意:我不想使用分布式事务。

编辑:

保罗萨西克非常接近,我认为我可以达到100%。这就是我的想法:

首先在数据库中创建一个表,如下所示:

CREATE TABLE Events (EventId int PRIMARY KEY)

您可能希望使用guids而不是int,或者您可以使用序列或身份。

然后执行以下伪代码:

open transaction
save order and event via A SINGLE transaction
in case of failure, report error and return
place order in message queue
in case of failure, report error, roll back transaction and return
commit transaction

所有活动必须包含EventId。当事件订阅者收到事件时,他们首先检查数据库中是否存在EventId。

通过这种方式,您可以获得100%的可靠性,而不仅仅是99.999%

2 个答案:

答案 0 :(得分:3)

您可以this.bus.Publish调用this.SaveOrderToDataBase数据库事务的this.SaveOrderToDataBase调用部分。这意味着open transaction save order via transaction in case of failure, report error and return place order in message queue in case of failure, report error, roll back transaction and return commit transaction 在事务范围内执行,如果db调用失败,则永远不会调用mq,如果mq调用失败,则回滚db事务,使两个系统处于一致状态。如果两个调用都成功,则提交db事务。

伪代码:

name

您没有提及任何特定的数据库技术,所以这里是a wiki article on transactions的链接。即使您不熟悉交易,也是一个很好的起点。还有一点好消息:它们并不难实现。

答案 1 :(得分:1)

videothis blog post

中说明了确保事件最终发布到消息排队系统的正确方法。

基本上你需要在你执行bussines逻辑操作的同一个事务中存储要发送到数据库的menssage,然后异步地将消息发送到总线并在另一个事务中从数据库中删除消息:

public void PlaceOrder(Order order)
{
     BeginTransaction();
     Try 
     {
         SaveOrderToDataBase(order);
         ev = new OrderPlaced(Order);
         SaveEventToDataBase(ev);
         CommitTransaction();
     }
     Catch 
     {
          RollbackTransaction();
          return;
     }

     PublishEventAsync(ev);    
}

async Task PublishEventAsync(BussinesEvent ev) 
{
    BegintTransaction();
    try 
    {
         await DeleteEventAsync(ev);
         await bus.PublishAsync(ev);
         CommitTransaction();
    }
    catch 
    {
         RollbackTransaction();
    }

}

由于PublishEventAsync可能会失败,因此您必须稍后重试,因此您需要一个后台进程来重试失败的发送,如下所示:

foreach (ev in eventsThatNeedsToBeSent) {
    await PublishEventAsync(ev);
}