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

Jes*_*pez 6 c# events message-queue eventual-consistency rabbitmq

请假设您有类似以下的方法:

public void PlaceOrder(Order order)
{
     this.SaveOrderToDataBase(order);
     this.bus.Publish(new OrderPlaced(Order));    
}
Run Code Online (Sandbox Code Playgroud)

将订单保存到数据库后,会将事件发布到消息排队系统,因此同一台计算机或其他计算机上的其他子系统可以处理该事件.

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

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

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

编辑:

Paul Sasik非常接近,我认为我可以达到100%.这就是我的想法:

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

CREATE TABLE Events (EventId int PRIMARY KEY)
Run Code Online (Sandbox Code Playgroud)

您可能希望使用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
Run Code Online (Sandbox Code Playgroud)

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

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

Jes*_*pez 6

在此视频此博客文章中解释了确保事件最终发布到消息队列系统的正确方法

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

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();
    }

}
Run Code Online (Sandbox Code Playgroud)

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

foreach (ev in eventsThatNeedsToBeSent) {
    await PublishEventAsync(ev);
}
Run Code Online (Sandbox Code Playgroud)

  • 对于不支持原子事务的数据库来说,这实际上不起作用。对于文档存储数据库,我会将事件保存在文档内,后台进程会拾取失败的事件并触发它们,并将事件标记为完成。如果将它们标记为完全失败,您可以保留一个对于事件来说足够唯一的幂等性密钥,例如 orderid,以便订阅者可以查找该密钥,如果该密钥已经存在,则不执行任何操作。这样,如果后台进程由于未能将事件标记为完成而尝试两次,则不会产生两次调用该事件的副作用。 (2认同)