如何避免在使用WebJobs SDK时从ServcieBus队列多次接收消息

时间:2018-05-07 07:56:33

标签: azure-webjobs azure-webjobssdk

我使用WebJobs SDK获得了一个带有以下ServiceBus处理程序的WebJob:

[Singleton("{MessageId}")]
public static async Task HandleMessagesAsync([ServiceBusTrigger("%QueueName%")] BrokeredMessage message, [ServiceBus("%QueueName%")]ICollector<BrokeredMessage> queue, TextWriter logger)
{
    using (var scope = Program.Container.BeginLifetimeScope())
    {
        var handler = scope.Resolve<MessageHandlers>();
        logger.WriteLine(AsInvariant($"Handling message with label {message.Label}"));

        // To avoid coupling Microsoft.Azure.WebJobs the return type is IEnumerable<T>
        var outputMessages = await handler.OnMessageAsync(message).ConfigureAwait(false);

        foreach (var outputMessage in outputMessages)
        {
            queue.Add(outputMessage);
        }
    }
}

如果未满足处理程序的先决条件,则outputMessages包含BrokeredMessage,其MessageIdLabel和有效负载与我们当前正在处理的相同,但它将来会包含ScheduledEnqueueTimeUtc

我们的想法是,我们快速完成当前消息的处理,并等待将来安排新消息重试。

有时,特别是当队列中的消息多于SDK peek-locks时,我看到ServiceBus队列中的消息重复。它们具有相同的MessageIdLabel和有效负载,但具有不同的SequenceNumberEnqueuedTimeUtcScheduledEnqueueTimeUtc。它们的交货数均为1。

查看我的处理程序代码,这种情况发生的唯一方法是,如果我多次收到相同的消息,请确定我需要等待并创建一条新消息以便将来处理。处理程序成功完成,因此原始消息已完成。

初始消息是唯一的。此外,我将SingletonAttribute放在消息处理程序上,以便不同处理程序不能使用相同MessageId的消息。

为什么使用相同的消息触发多个处理程序,如何防止这种情况发生?

我使用Microsoft.Azure.WebJobs版本是v2.1.0

我的处理程序的持续时间最长为17秒,平均为1秒。锁定持续时间为1米。我最好的理论仍然是带有消息(重新)锁定的东西不起作用,所以当我处理处理程序时,锁会丢失,消息会返回到队列并再次消耗。如果两个处理程序都会看到关键资源仍然被占用,那么它们都会将新消息排入队列。

1 个答案:

答案 0 :(得分:0)

经过一些实验后,我找出了根本原因并找到了解决方法。

如果ServiceBus消息已完成,但未放弃查看锁定,则在锁定到期后它将返回处于活动状态的队列。

ServiceBus QueueClient显然在收到下一条消息(或一批消息)后放弃了锁定。

因此,如果WebJobs SDK使用的QueueClient意外终止(例如由于进程已结束或Web App正在重新启动),则已锁定的所有消息都会显示在队列中,即使它们已完成。 / p>

在我的处理程序中,我现在手动完成消息并放弃锁定,如下所示:

public static async Task ProcessQueueMessageAsync([ServiceBusTrigger("%QueueName%")] BrokeredMessage message, [ServiceBus("%QueueName%")]ICollector<BrokeredMessage> queue, TextWriter logger)
{
    using (var scope = Program.Container.BeginLifetimeScope())
    {
        var handler = scope.Resolve<MessageHandlers>();
        logger.WriteLine(AsInvariant($"Handling message with label {message.Label}"));

        // To avoid coupling Microsoft.Azure.WebJobs the return type is IEnumerable<T>
        var outputMessages = await handler.OnMessageAsync(message).ConfigureAwait(false);

        foreach (var outputMessage in outputMessages)
        {
            queue.Add(outputMessage);
        }

        await message.CompleteAsync().ConfigureAwait(false);
        await message.AbandonAsync().ConfigureAwait(false);
    }
}

这样我就不会在重启方案中将消息恢复到队列中。