读取Azure Service Bus延迟消息时会话锁过期问题排查
我有两个应用,应用A向应用B发送复杂对象数据集,要求每个数据集的对象必须按特定顺序处理,因此我使用了启用会话的Azure Service Bus主题,每条消息包含代表数据集的会话ID和代表顺序的序列ID。
我使用ServiceBusSessionProcessor接收总线消息,初始化代码如下:
SessionOptions = new ServiceBusSessionProcessorOptions() { MaxAutoLockRenewalDuration = TimeSpan.FromMinutes(5), AutoCompleteMessages = false, MaxConcurrentSessions = 4, ReceiveMode = ServiceBusReceiveMode.PeekLock, SessionIdleTimeout = TimeSpan.FromMinutes(10), MaxConcurrentCallsPerSession = 1, }; SubscriptionSessionProcessor = SubscriptionClient.CreateSessionProcessor("TestTopic", "TestSubscription", SessionOptions);
收到消息后,我会检查该消息是否为序列中的下一个待处理消息:如果是则处理并完成消息,否则将消息延迟回总线,代码如下:
protected async Task HandleSessionMessageAsync<R>(SessionInformationBase sessionInformation, ServiceBusReceivedMessage message) { var stateData = await sessionInformation.Message.GetSessionStateAsync(); var sessionState = SessionStateManager.CreateSessionStateManager(stateData); if (sessionState.IsNextMessage(sessionInformation.SessionData.Sequence)) { try { sessionInformation.ProcessorCall.Invoke(); } catch (Exception e) { ExceptionTracer.TraceException(e); } await sessionInformation.Message.CompleteMessageAsync(message); await HandleSessionStateAsync<R>(sessionInformation, sessionState); } else { sessionState.DeferredMessages.Add(sessionInformation.SessionData.Sequence, message.SequenceNumber); //defer message directly on the message object (the message that was received on the ServiceBusSessionProcessor object await sessionInformation.Message.DeferMessageAsync(message); await SetSessionStateAsync(sessionInformation.Message, sessionState.Serialize()); } }
处理完序列中的消息后,我会检查下一条消息是否已被延迟,如果是则尝试从总线读取,代码如下:
if (!sessionState.NextMessageIsDeferred()) return; try { var deferredMessage = await Receiver.ReceiveDeferredMessageAsync(sessionState.GetNextMessageSequenceId()); ... //process deferred message }
但调用ReceiveDeferredMessageAsync方法时,我收到了会话锁过期异常。会话锁超时设置为5分钟,且延迟消息与读取之间仅间隔数秒。
我怀疑问题在于使用ServiceBusReceiver对象读取延迟消息,该对象既不是接收消息的原始处理器,也不是消息本身——但据我所知,这些对象无法读取延迟消息。
我尝试过通过接收器对象延迟消息、用接收器对象替换处理器对象,但都遇到了不同的问题。
我使用的是Azure.Messaging.ServiceBus 7.10.0库,请问我忽略了什么?为什么会话锁会过期?
核心原因:跨会话客户端操作导致锁失效
你遇到的会话锁过期问题,根源在于你用独立的ServiceBusReceiver去读取同一会话的延迟消息,但这个接收器并没有持有该会话的锁。
Azure Service Bus的会话锁是绑定到具体的会话客户端(ServiceBusSessionProcessor的会话实例或ServiceBusReceiver的会话实例)的,不同客户端无法共享同一会话的锁。当你用未持有锁的Receiver去访问该会话的延迟消息时,Service Bus会判定当前客户端没有会话权限,抛出锁过期异常。
正确的处理方式
在使用ServiceBusSessionProcessor时,应该直接使用处理器提供的会话上下文来操作延迟消息,而不是创建独立的ServiceBusReceiver:
复用当前会话的客户端
在HandleSessionMessageAsync方法里,sessionInformation.Message是当前会话对应的ServiceBusReceiver实例(ServiceBusSessionProcessor会为每个会话分配专属的接收器),你可以直接用这个客户端来接收延迟消息,无需使用外部独立的Receiver。修改延迟消息读取逻辑
把读取延迟消息的代码改成使用当前会话的客户端:// 替换原有的Receiver,用当前会话的客户端操作 var deferredMessage = await sessionInformation.Message.ReceiveDeferredMessageAsync(sessionState.GetNextMessageSequenceId()); ... //process deferred message确保会话锁自动续约生效
你的MaxAutoLockRenewalDuration设置为5分钟,处理器会自动续约会话锁直到超过该时长,但要注意:- 只有当前会话的客户端处于活跃状态时,续约逻辑才会正常运行。如果会话处理过程中存在长时间空闲,可能触发
SessionIdleTimeout(10分钟)导致会话被释放。 - 不要在会话处理流程中切换客户端,否则原有客户端的续约会停止,会话锁会在超时后过期。
- 只有当前会话的客户端处于活跃状态时,续约逻辑才会正常运行。如果会话处理过程中存在长时间空闲,可能触发
额外注意事项
- 延迟消息本身不会持有会话锁,但读取延迟消息的操作必须由持有该会话锁的客户端执行。
- 如果需要在会话处理流程之外访问延迟消息,必须先通过
ServiceBusClient.AcceptSessionAsync获取该会话的锁,再创建对应的ServiceBusReceiver进行操作。
内容的提问来源于stack exchange,提问作者Bojan B

