请求-回复模型与DI下ServiceBusSessionReceiver及会话生命周期处理
我正在编写一个采用异步请求-回复通信模型的ACME Worker Service。在Azure Service Bus中,我创建了acme-requests队列用于接收ACME服务的所有入站请求,同时创建acme-replies队列用于接收ACME服务向其他服务发起请求后的回复消息。
例如,当ACME服务向Foo服务发送请求时,会为该请求-回复消息会话生成一个GUID并存储在本地数据库中,随后将消息发送至foo-requests队列。Foo服务后续会向acme-replies队列回复消息。
在ACME服务中,我通过一个BackgroundService消息收件箱接收acme-replies队列的消息并分发给消息处理程序。具体来说,我希望在BackgroundService的处理循环中调用ServiceBusSessionReceiver replyReceiver = await client.AcceptNextSessionAsync("acme-replies"),处理消息后关闭当前消息会话。但这似乎与官方建议相悖:
与Service Bus交互的对象(如ServiceBusClient、ServiceBusSender、ServiceBusReceiver和ServiceBusProcessor)应注册为依赖注入的单例(或实例化一次并共享)。[...] 建议不要在发送或接收每条消息后关闭或释放这些对象。
(注:ServiceBusSessionReceiver是ServiceBusReceiver的子类。)
我现在的问题是如何将请求-回复模式、消息会话与依赖注入结合起来?具体包含以下问题:
- 如何处理
ServiceBusSessionReceiver和消息会话的生命周期?二者的生命周期似乎一致。 - 那么
ServiceBusSessionReceiver实例应注册为单例还是作用域服务? - 是否可以在不立即连接Service Bus的情况下创建
ServiceBusSessionReceiver实例?目前仅存在ServiceBusClient.AcceptSessionAsync方法,没有ServiceBusClient.CreateSessionReceiver方法。
解答
1. ServiceBusSessionReceiver与消息会话的生命周期处理
ServiceBusSessionReceiver的生命周期确实和它绑定的消息会话完全一致:调用AcceptNextSessionAsync或AcceptSessionAsync时,Receiver会绑定到一个具体会话,直到释放该Receiver,对应的会话锁定才会解除。
在你的请求-回复场景中,每个回复消息都属于特定GUID会话,正确做法是为每个会话创建专属的ServiceBusSessionReceiver,处理完该会话的所有回复后再释放它。这里需要明确:官方建议的“不要频繁创建/释放”针对的是通用的非会话ServiceBusReceiver,而会话Receiver本身就是会话专属的,生命周期匹配会话是合理设计,并不违背最佳实践。
你可以在BackgroundService的循环中,每次接受一个会话Receiver,处理该会话内的所有消息(直到会话无新消息、超时或处理完成),然后释放Receiver,再接受下一个会话。
2. ServiceBusSessionReceiver的DI注册方式
ServiceBusSessionReceiver不适合注册为单例或作用域服务:
- 单例会导致所有会话共用同一个Receiver,完全违背会话隔离的设计目的;
- 作用域服务通常对应HTTP请求或业务事务的生命周期,但会话的生命周期独立于这些场景,无法匹配。
正确的做法是不在DI容器中直接注册ServiceBusSessionReceiver,而是通过注入的单例ServiceBusClient,在BackgroundService的处理循环中按需动态创建会话Receiver。也就是在需要处理会话时调用AcceptNextSessionAsync获取Receiver,处理完成后调用await replyReceiver.DisposeAsync()释放。
3. 不立即连接创建ServiceBusSessionReceiver的可能性
目前Azure Service Bus SDK中确实没有延迟连接的CreateSessionReceiver方法,因为ServiceBusSessionReceiver的创建过程本身就需要和Service Bus服务建立连接并锁定会话。
不过你可以通过配置Receiver的连接超时来控制连接时机,比如设置较短的超时时间,或者在实际需要处理会话时再调用AcceptNextSessionAsync。另外,你可以提前构建ServiceBusSessionReceiverOptions对象,定义好接收模式、自动完成等配置,等到需要创建Receiver时再传入方法中,这样能提前完成配置准备,实际的连接和会话锁定则在调用异步方法时才触发。
内容的提问来源于stack exchange,提问作者Joerg

