我使用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
,其MessageId
,Label
和有效负载与我们当前正在处理的相同,但它将来会包含ScheduledEnqueueTimeUtc
。
我们的想法是,我们快速完成当前消息的处理,并等待将来安排新消息重试。
有时,特别是当队列中的消息多于SDK peek-locks时,我看到ServiceBus队列中的消息重复。它们具有相同的MessageId
,Label
和有效负载,但具有不同的SequenceNumber
,EnqueuedTimeUtc
和ScheduledEnqueueTimeUtc
。它们的交货数均为1。
查看我的处理程序代码,这种情况发生的唯一方法是,如果我多次收到相同的消息,请确定我需要等待并创建一条新消息以便将来处理。处理程序成功完成,因此原始消息已完成。
初始消息是唯一的。此外,我将SingletonAttribute
放在消息处理程序上,以便不同处理程序不能使用相同MessageId
的消息。
为什么使用相同的消息触发多个处理程序,如何防止这种情况发生?
我使用Microsoft.Azure.WebJobs
版本是v2.1.0
我的处理程序的持续时间最长为17秒,平均为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);
}
}
这样我就不会在重启方案中将消息恢复到队列中。