在重试邮件之前,Azure Service Bus是否可以延迟?

时间:2014-02-03 20:09:32

标签: azure azureservicebus

Azure Service Bus支持内置的重试机制,该机制使被放弃的消息立即可见以进行另一次读取尝试。我正在尝试使用此机制来处理一些瞬态错误,但该消息在被放弃后立即可用。

我想要做的是让信息在被放弃后的一段时间内不可见,最好是基于指数递增的政策。

我在放弃邮件时尝试设置ScheduledEnqueueTimeUtc属性,但似乎没有效果:

var messagingFactory = MessagingFactory.CreateFromConnectionString(...);

var receiver = messagingFactory.CreateMessageReceiver("test-queue");

receiver.OnMessageAsync(async brokeredMessage =>
{
    await brokeredMessage.AbandonAsync(
        new Dictionary<string, object>
        {
            { "ScheduledEnqueueTimeUtc", DateTime.UtcNow.AddSeconds(30) }
        });
    }
});

我已经考虑过根本不放弃邮件而只是让锁过期,但是这需要一些方法来影响MessageReceiver如何指定邮件的锁定持续时间,而我找不到API中的任何内容都可以让我更改此值。此外,在已经需要锁定之前,无法读取消息的传递计数(因此决定等待下一次重试的时间)。

消息总线中的重试策略是否可以以某种方式受到影响,还是可以通过其他方式人为引入延迟?

3 个答案:

答案 0 :(得分:13)

请注意,因为我认为您将重试功能与Complete事件驱动的邮件处理的Abandon / OnMessage机制相混淆。当对服务总线的调用失败时,内置重试机制开始起作用。例如,如果您调用将消息设置为完成且失败,则重试机制将启动。如果您正在处理消息,则您自己的代码中会发生异常,不会通过重试功能触发重试。如果错误来自您的代码或尝试联系服务总线,您的问题不会明确。

如果确实在修改了尝试与服务总线通信时发生错误时发生的重试策略,则可以修改RetryPolicy本身设置的MessageReciver。默认情况下使用RetryExponitial,以及您可以创建自己的抽象RetryPolicy

我认为你所追求的是更多地控制当你处理异常时会发生什么,并且你想要推迟处理该消息。有几个选择:

创建消息处理程序时,您可以设置OnMessageOptions。其中一个属性是“自动完成”。默认情况下,此值设置为true,这意味着只要完成消息处理,就会自动调用Complete方法。如果发生异常,则会自动调用放弃,这就是您所看到的。通过将AutoComplete设置为false,您需要在消息处理程序中自行调用Complete。如果不这样做,将导致消息锁定最终耗尽,这是您正在寻找的行为之一。

因此,您可以编写处理程序,以便在处理过程中发生异常时,您只需调用Complete即可。然后该消息将保留在队列中,直到它的锁定用完,然后再次可用。标准的死亡刻字机制适用,在x次尝试后,它将自动放入死信队列。

处理这种方式的一个注意事项是任何类型的异常都将以这种方式处理。你真的需要考虑什么类型的异常正在做这个以及你真的想要推迟处理。例如,如果您在处理期间呼叫第三方系统并且它给您一个例外,您知道这是一个短暂的,很棒的。但是,如果它给你一个错误,你知道这将是一个大问题,那么你可能决定在系统中做一些其他事情,除了对消息哄骗。

您还可以查看“Defer”方法。实际上,该方法不允许从队列中处理该消息,除非它的序列号特别提取。您的代码必须记住序列号值并将其拉出。这不是你所描述的。

另一个选择是你可以放弃OnMessage,事件驱动的处理消息的方式。虽然这非常有用,但您无法对事物进行大量控制。而是连接自己的处理循环并自己处理放弃/完成。您还需要处理OnMessage模式为您提供的一些线程/并发调用管理。这可能是更多的工作,但你有最大的灵活性。

最后,我认为您对AbandonAsync传递要修改的属性的调用不起作用的原因是这些属性指的是方法上的Metadata properties,而不是标准属性BrokeredMessage。

答案 1 :(得分:11)

我实际上asked this same question last year(实现除了)我可以考虑使用三种方法来查看API。在SB团队工作的@ClemensVasters回应说,使用Defer进行某种重新接收实际上是准确控制这种情况的唯一方法。

您可以阅读我对其答案的回答,以了解具体方法,我建议使用辅助队列来存储指示哪些主要消息已被延迟并需要从主队列重新接收的消息。然后,您可以通过在这些辅助消息上设置ScheduledEnqueueTimeUtc来控制完全您在重试之前等待的时间,从而控制等待的时间。

答案 2 :(得分:1)

我遇到了一个类似的问题,即我们的订单拣选系统是旧系统,每天晚上都进入维护模式。

使用本文中的思想(https://markheath.net/post/defer-processing-azure-service-bus-message),我创建了一个自定义属性来跟踪邮件已重新提交多少次,并在尝试10次后手动使邮件失效。如果邮件的重试次数少于10次,它将克隆该邮件,并递增custom属性并设置新邮件的队列。

using Microsoft.Azure.ServiceBus;
public PickQueue()
{
    queueClient = new QueueClient(QUEUE_CONN_STRING, QUEUE_NAME); 
}
public async Task QueueMessageAsync(int OrderId)
{
    string body = JsonConvert.SerializeObject(OrderId);
    var message = new Message(Encoding.UTF8.GetBytes(body));
    await queueClient.SendAsync(message);
}

public async Task ReQueueMessageAsync(Message message, DateTime utcEnqueueTime)
{
    int resubmitCount = (int)(message.UserProperties["ResubmitCount"] ?? 0) + 1;
    if (resubmitCount > 10)
    {
        await queueClient.DeadLetterAsync(message.SystemProperties.LockToken);
    }
    else
    {
        Message clone = message.Clone();
        clone.UserProperties["ResubmitCount"] = ++resubmitCount;
        await queueClient.ScheduleMessageAsync(message, utcEnqueueTime);
    }
}