我构建了一项服务以支持Azure Service Bus中的多个队列订阅,但是出现了一些奇怪的现象。
我的订阅单例类有一个如下所示的方法:
public void Subscribe<TMessage>(Func<TMessage, Task> execution, int maxDop = 1, int ttl = 60) where TMessage : IServiceBusMessage
{
try
{
var messageLifespan = TimeSpan.FromSeconds(ttl);
var messageType = typeof(TMessage);
if (!_activeSubscriptionClients.TryGetValue(messageType, out var subscriptionClient))
{
subscriptionClient = _subscriptionClientFactory.Create(typeof(TMessage)).GetAwaiter().GetResult();
if (subscriptionClient.OperationTimeout < messageLifespan) subscriptionClient.OperationTimeout = messageLifespan;
if (subscriptionClient.ServiceBusConnection.OperationTimeout < messageLifespan)
subscriptionClient.ServiceBusConnection.OperationTimeout = messageLifespan;
_activeSubscriptionClients.AddOrUpdate(messageType, subscriptionClient, (key, value) => value);
}
var messageHandlerOptions = new MessageHandlerOptions(OnException)
{
MaxConcurrentCalls = maxDop,
AutoComplete = false,
MaxAutoRenewDuration = messageLifespan,
};
subscriptionClient.RegisterMessageHandler(
async (azureMessage, cancellationToken) =>
{
try
{
var textPayload = _encoding.GetString(azureMessage.Body);
var message = JsonConvert.DeserializeObject<TMessage>(textPayload);
if (message == null)
throw new FormatException($"Cannot deserialize the message payload to type '{typeof(TMessage).FullName}'.");
await execution.Invoke(message);
await subscriptionClient.CompleteAsync(azureMessage.SystemProperties.LockToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "ProcessMessagesAsync(Message, CancellationToken)");
await subscriptionClient.AbandonAsync(azureMessage.SystemProperties.LockToken);
}
}
, messageHandlerOptions);
}
catch (Exception ex)
{
_logger.LogError(ex, "Subscribe(Action<TMessage>)");
throw;
}
}
这个想法是,您为特定类型的消息订阅Azure Service Bus,并且该消息直接对应于队列。在您的订阅中,您传递了有关如何处理消息的委托。
这似乎奏效了……一招。
不管我在长时间运行的任何给定消息上为ttl
或MaxAutoRenewDuration
设置了OperationTimeout
还是什么,在一分钟后,该消息都会从排队,另一个订户将其拾起并开始处理。
我的理解是MaxAutoRenewDuration
确实应该阻止...但是似乎并不能阻止任何事情。
任何人都可以告诉我我需要做些什么来确保消费者拥有该消息直到完成吗?
答案 0 :(得分:0)
我可以想到一些选择,您可能想看看。
不是在SubscriptionClient中使用默认的ReceiveMode = PeekLock
,而是将其设置为ReceiveAndDelete,这样,一旦消息被使用,它将被从队列中删除,并且不会被任何其他客户端使用,这确实意味着您必须优雅地处理异常并自己重试;
看看OperationTimeout
,根据doco,它是Duration after which individual operations will timeout
答案 1 :(得分:0)
事实证明,使用者正在运行的远程进程静默失败,并且未返回失败状态代码(或其他任何信息);自动刷新机制挂起等待结果,因此消息最终超时了。
我尚不清楚如何防止这种情况,但是一旦我在远程进程上解决了该问题,该问题就不再可以重现。
故事的寓意:如果一切看起来正确,并且仍在超时,则似乎自动刷新机制与您正在等待的异步操作共享了一些资源。可能是寻找失败的另一个地方。