如何在rebus中中止交易?

时间:2017-07-13 11:47:40

标签: c# rebus

当我尝试在消息处理程序中中止事务时,我正在努力。我正在使用RabbitMQ

我的目标是产生以下行为:如果收到消息,我会尝试将其内容存储在硬盘上。如果失败了,我想重新排队。通过这样做,我给同一服务的另一个实例尝试相同的机会。 我想要的基本上是控制邮件被ACK编辑或拒绝的可能性。

我仔细查看了源代码RabbitMqTransport.cs,发现当提交事务时,会发送ACK。如果交易中止,则发送NACK。我曾经自己创建了一个围绕RabbitMQ的包装类,因此知道这是正确的。

然而似乎永远不会调用OnAborted。即使我中止了交易,也会调用OnComitted

我使用以下代码中止事务。 context是传递到IMessageContext的{​​{1}}个实例。

Messagehandler

我也尝试了不同的变体,例如获取context.TransactionContext.OnAborted(() => { Console.WriteLine("Abort"); }); context.TransactionContext.OnCommitted(async () => { Console.WriteLine("Commit"); }); context.TransactionContext.Abort(); 或使用AmbientTransactionContext包,但没有效果。

3 个答案:

答案 0 :(得分:0)

您的帖子中不清楚的是您如何中止交易。我们使用与RabbitMq类似的设置,我总是从处理程序内部抛出一个异常。

答案 1 :(得分:0)

Rebus旨在按照您的预期处理ACK / NACK,具体取决于您的消息处理程序是否抛出异常。

换句话说,如果您的消息处理程序没有抛出异常,则认为消息传递成功,并且消息已确认。

另一方面,如果消息处理程序抛出异常,则消息为NACKed(因此在RabbitMQ术语中将其状态重置为“Ready”),从而有效地使另一个实例可以获取消息。 / p>

但是,由于RabbitMQ客户端驱动程序的设计方式,我认为该消息实际上不会返回给服务器以供其他实例接收 - 我认为(这只是猜测)事实是驱动程序预取消息并将它们存储在内存队列中,直到它们被消耗为止,导致消息被简单地在内部标记为重新传送。

因此,我希望相同的服务实例能够执行所有传递尝试,然后 - 正如您正确观察到的那样 - Rebus会将消息移出错误队列,从而安全地存储它以便稍后处理。

我希望这有道理:)

答案 2 :(得分:0)

@ mookid8000: 好的,当消息被ACKNACK编辑时,这表明了这一点。

然而,我能够反驳你的假设。我在绑定到同一输入队列的单独控制台应用程序中创建了两个使用者其中一个总是抛出异常。发布者每10秒发送一条消息。我可以看到,其他消费者在故障抛出异常后处理消息。我明白了,因为Rebus在控制台中记录了消息的ID。

这回答了我的问题,但没有解决我的问题。就我而言,我真正想要的是将消息保留在队列中的可能性,直到其中一个服务实例能够处理它。原因是消息的顺序很重要。这可能是我的方法中的一个根本错误,但是现在我不想改变它。

有没有办法阻止(某些)消息移动到错误队列? Rebus中的二级重试机制是否可能实现此目的?

请参阅以下源代码以获取进一步参考:

普通消费者:

class TimeEventHandler : IHandleMessages<TimeEvent>
{
    public async Task Handle(TimeEvent message)
    {
        Console.WriteLine(message.Time);

        //await Program.Activator.Bus.Reply("Ja danke");
    }
}

class Program
{
    public static BuiltinHandlerActivator Activator;

    static void Main(string[] args)
    {
        using (Activator = new BuiltinHandlerActivator())
        {
            Activator.Register(() => new TimeEventHandler());

            var bus = Configure
                .With(Activator)
                .Transport(t => t.UseRabbitMq("amqp://guest:guest@192.168.3.50",
                    $"ConsumerPrototype").Prefetch(1))
                .Routing(r => r.TypeBased())
                .Start();

            bus.Subscribe<TimeEvent>();

            Console.WriteLine("Press enter to quit");
            Console.ReadLine();

            // Without the unsubscribe we have a durable subscriber, see http://www.enterpriseintegrationpatterns.com/patterns/messaging/DurableSubscription.html
            //bus.Unsubscribe<TimeEvent>();
        }
    }
}

故障消费者:

class FaultedTimeEventHandler : IHandleMessages<TimeEvent>
{
    public async Task Handle(TimeEvent message)
    {
        throw new Exception("That should not have happened");
    }
}

class Program
{
    public static BuiltinHandlerActivator Activator;

    static void Main(string[] args)
    {
        using (Activator = new BuiltinHandlerActivator())
        {
            Activator.Register(() => new FaultedTimeEventHandler());

            var bus = Configure
                .With(Activator)
                .Transport(t => t.UseRabbitMq("amqp://guest:guest@192.168.3.50",
                    $"ConsumerPrototype").Prefetch(1))
                .Routing(r => r.TypeBased())
                .Start();

            bus.Subscribe<TimeEvent>();

            Console.WriteLine("Press enter to quit");
            Console.ReadLine();
        }
    }
}

出版商:

public static class PubSubTest
{
    public static void Start()
    {
        Console.WriteLine("Starting PubSubTest");

        using (var activator = new BuiltinHandlerActivator())
        {
            var bus = Configure
                .With(activator)
                .Transport(t => t.UseRabbitMq("amqp://guest:guest@192.168.3.50", "MessagingTest").Prefetch(1))
                .Routing(r => r.TypeBased())
                .Start();

            Observable
                .Timer(TimeSpan.Zero, TimeSpan.FromSeconds(10))
                .Subscribe(_ => bus.Publish(new TimeEvent(DateTime.Now)).Wait());

            Console.WriteLine("Press enter to quit");
            Console.ReadLine();
        }
    }
}

更新26.07.17: 我的第二级重试结果:激活它们后,我通过延迟并重新发送它们来处理它们。通过这样做,我至少可以确保消息稍后处理而不会丢失。

public async Task Handle(IFailed<TimeEvent> failedMessage)
{
    await bus.Defer(TimeSpan.FromSeconds(30), failedMessage.Message);
}

这不是最佳解决方案:

  1. 我的消息顺序已更改:消息延迟后,将从队列中消耗下一条消息。
  2. Timeoutmanager是In-Memory,我无法访问SQL-Server。
  3. 我读到可以使用RabbitMQ的延迟消息。您可以使用dead-letter-exchangesplugin

    将maxDeliveryAttempts增加到Int32.MaxValue非常脏,但是做了我想要的事情:它保留消息,最重要的是保留队列中消息的顺序。

    我将此问题标记为已解决,因为问题&#34;如何中止Rebus中的交易&#34;得到了答复。