ServiceBusRetryOptions与SessionProcessor重试策略失效问题
问题描述
使用.NET 7 + Azure.Messaging.ServiceBus SDK实现连接Azure Service Bus Topic的会话处理器,尝试通过ServiceBusClientOptions.RetryOptions设置MaxRetries=2来控制消息重试次数,但测试时发现实际重试次数由Azure门户中订阅设置的MaxDeliveryCount(10)决定,抛出异常后消息的DeliveryCount会一直涨到10才停止。需求是遇到内部服务器错误时,无需重新发布消息即可完成指定次数的重试。
相关代码如下:
using Azure.Messaging.ServiceBus; namespace ServiceBusProcessor; public class ServiceBusProcessorHandler : BackgroundService { private static readonly string ConnectionString = ""; private static readonly string TopicName = ""; private static readonly string SubscriptionName = ""; private readonly ServiceBusClient serviceBusClient; private readonly ServiceBusSessionProcessor serviceBusSessionProcessor; public ServiceBusProcessorHandler() { this.serviceBusClient = new ServiceBusClient(ConnectionString, new ServiceBusClientOptions() { RetryOptions = new ServiceBusRetryOptions() { Mode = ServiceBusRetryMode.Exponential, MaxRetries = 2, MaxDelay = TimeSpan.FromMilliseconds(10000), Delay = TimeSpan.FromMilliseconds(1000) } }); this.serviceBusSessionProcessor = this.serviceBusClient.CreateSessionProcessor(TopicName, SubscriptionName, new ServiceBusSessionProcessorOptions() { MaxConcurrentSessions = 100, PrefetchCount = 100, SessionIdleTimeout = TimeSpan.FromSeconds(1), }); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { this.serviceBusSessionProcessor.ProcessMessageAsync += (args) => { Console.WriteLine($"DeliveryCount: {args.Message.DeliveryCount}"); Console.WriteLine(args.Message.Body); throw new NotImplementedException("Not Implemented"); }; this.serviceBusSessionProcessor.ProcessErrorAsync += (args) => { Console.WriteLine(args.Exception); return Task.CompletedTask; }; await this.serviceBusSessionProcessor.StartProcessingAsync(); } public override async Task StopAsync(CancellationToken cancellationToken) { Console.WriteLine("Service is stopping."); await base.StopAsync(cancellationToken); } }
原因分析
ServiceBusClientOptions.RetryOptions配置的是客户端与Service Bus服务端交互时的重试策略,比如连接建立失败、发送/接收请求超时、服务端返回临时错误(如5xx)等场景的重试,并不控制消息处理失败后的重试次数。
当你在ProcessMessageAsync事件处理程序中抛出异常时,SessionProcessor会自动将消息标记为Abandon,Service Bus服务端会将消息重新放回队列,直到消息的DeliveryCount达到订阅设置的MaxDeliveryCount,之后消息会被转入死信队列。这就是为什么实际重试次数由门户中的MaxDeliveryCount决定。
解决方案
要实现消息处理失败时的本地指定次数重试,需要在消息处理逻辑内部实现重试逻辑,而不是依赖Service Bus服务端的重投递。推荐使用Polly库来简化重试策略的实现,步骤如下:
- 安装Polly NuGet包:
Install-Package Polly
- 修改消息处理逻辑,添加本地重试:
this.serviceBusSessionProcessor.ProcessMessageAsync += async (args) => { Console.WriteLine($"DeliveryCount: {args.Message.DeliveryCount}"); Console.WriteLine(args.Message.Body); // 定义重试策略:最多重试2次,仅针对内部服务器错误类异常 var retryPolicy = Policy .Handle<Exception>(ex => ex is NotImplementedException || (ex.InnerException is HttpRequestException httpEx && httpEx.StatusCode == System.Net.HttpStatusCode.InternalServerError)) .WaitAndRetryAsync(2, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); try { await retryPolicy.ExecuteAsync(async () => { // 这里是你的实际业务处理逻辑 throw new NotImplementedException("Not Implemented"); }); // 处理成功,完成消息 await args.CompleteMessageAsync(args.Message); } catch (Exception ex) { Console.WriteLine($"处理失败,已用完本地重试次数: {ex.Message}"); // 可以选择放弃消息(让服务端重投)或直接死信 // await args.AbandonMessageAsync(args.Message); await args.DeadLetterMessageAsync(args.Message, "处理失败", "本地重试次数已用完"); } };
关键说明
- 本地重试不会增加消息的
DeliveryCount,因为重试过程中消息一直被处理器持有,没有调用Abandon或DeadLetter。 - 当本地重试次数用完后,根据业务需求选择:
- 调用
AbandonMessageAsync:让服务端重新投递消息,此时DeliveryCount会+1,受门户MaxDeliveryCount限制。 - 调用
DeadLetterMessageAsync:直接将消息转入死信队列,不再重试。 - 调用
CompleteMessageAsync:标记消息为完成(不推荐,除非确认无需重试)。
- 调用
内容的提问来源于stack exchange,提问作者usernotdefined

