Azure Service Bus问题:AbandonMessageAsync未递增投递计数且日志暴增
Azure Service Bus消息投递计数异常导致函数无限重试问题排查
问题描述
我们使用Azure Functions通过ServiceBusTrigger处理Azure Service Bus队列消息,消息入队时触发函数。单条消息在2.5天内触发了约800万条AppException日志,成本远超预期。怀疑投递计数未正常递增,导致消息无法达到Max Delivery Count进入死信队列,推测AbandonMessageAsync在某些场景下未正常工作。
队列配置详情
- Lock Duration:1分钟
- Max Delivery Count:10
- Message Time to Live:3天
函数简化代码
[Function(nameof(PDFGenFunction))] public async Task Run( [ServiceBusTrigger(SourceQueue, Connection = ConnectionStringKey)] ServiceBusReceivedMessage message, ServiceBusMessageActions messageActions) { try { _logger.LogInformation($"Message ID: {message.MessageId}"); _logger.LogInformation($"PdfGenerationRequest - Received message: {message.Body.ToString()}"); _logger.LogInformation($"Message Content-Type: {message.ContentType}"); var outputJSON = JsonConvert.DeserializeObject<OutputJSON>(message.Body.ToString()); if (outputJSON != null) { var result = await SendPdfGenerationRequest(message.Body.ToString()); if(result.Status == PdfResponseStatus.Queued) { await SendMessageToPdfCompletionQueue(result); await messageActions.CompleteMessageAsync(message); } else { _logger.LogError($"PdfGenerationRequest - request failed: {result.Urn}, application string: {message.Body.ToString()}"); await messageActions.AbandonMessageAsync(message); } } else { _logger.LogError($"PdfGenerationRequest - Invalid JSON string data, application string: {message.Body.ToString()}"); await messageActions.CompleteMessageAsync(message); } } catch (Exception ex) { _logger.LogError(ex, "Error processing application submission: {messageBody}", message.Body.ToString()); await messageActions.AbandonMessageAsync(message); } }
host.json配置
{ "version": "2.0", "logging": { "applicationInsights": { "samplingSettings": { "isEnabled": true, "excludedTypes": "Request" }, "enableLiveMetricsFilters": true } }, "extensions": { "queues": { "visibilityTimeout": "00:03:00", "maxDequeueCount": 5 } } }
排查与解决建议
- 确认投递计数实际变化:通过Azure门户的Service Bus队列监控、Azure CLI或SDK获取目标消息的
DeliveryCount属性,验证每次调用AbandonMessageAsync后计数是否递增。若计数未上涨,说明Abandon操作未生效;若计数正常,则需排查重试间隔或配置冲突。 - 修正host.json配置冲突:当前
extensions.queues节点是针对Azure Storage Queue的配置,对Service Bus完全无效。Service Bus的扩展配置需放在extensions.serviceBus节点下,示例:"extensions": { "serviceBus": { "maxAutoLockRenewalDuration": "00:05:00", "messageHandlerOptions": { "autoComplete": false, "maxConcurrentCalls": 1, "maxAutoRenewDuration": "00:05:00" } } } - 排查
AbandonMessageAsync执行有效性:- 在调用
AbandonMessageAsync前后添加日志,记录message.DeliveryCount,确认该方法是否被执行及执行前的计数。 - 捕获
AbandonMessageAsync自身的异常:若消息锁已过期,调用Abandon会失败,此时Service Bus会自动解锁消息并重新入队,但投递计数不会递增,导致无限重试。 - 检查函数执行超时:若函数执行时间超过Lock Duration(1分钟)且未开启自动锁续期,消息锁会过期,后续Abandon操作无效,消息会被立即重试且计数不涨。
- 在调用
- 验证PDF生成API超时问题:若
SendPdfGenerationRequest调用超时超过1分钟,且未配置锁续期,会导致锁过期后消息立即重试,这正是周末产生海量日志的核心原因——无间隔的无限重试。 - 临时缓解措施:
- 手动将问题消息移入死信队列,终止无限重试。
- 临时降低函数并发数,减少日志生成速度。
- 长期优化方案:
- 配置Service Bus扩展的自动锁续期,确保函数执行时间超过Lock Duration时锁不会过期。
- 在代码中捕获
AbandonMessageAsync的异常,避免因锁过期导致的操作失败。 - 给PDF生成API添加超时控制,或优化函数逻辑确保执行时间在锁有效期内。
- 实现消息幂等性,避免重复处理带来的额外开销。
内容的提问来源于stack exchange,提问作者Ryan Ternier
相关产品推荐
相关产品推荐

