You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

读取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:

  1. 复用当前会话的客户端
    在HandleSessionMessageAsync方法里,sessionInformation.Message是当前会话对应的ServiceBusReceiver实例(ServiceBusSessionProcessor会为每个会话分配专属的接收器),你可以直接用这个客户端来接收延迟消息,无需使用外部独立的Receiver。

  2. 修改延迟消息读取逻辑
    把读取延迟消息的代码改成使用当前会话的客户端:

    // 替换原有的Receiver,用当前会话的客户端操作
    var deferredMessage = await sessionInformation.Message.ReceiveDeferredMessageAsync(sessionState.GetNextMessageSequenceId());
    ... //process deferred message
    
  3. 确保会话锁自动续约生效
    你的MaxAutoLockRenewalDuration设置为5分钟,处理器会自动续约会话锁直到超过该时长,但要注意:

    • 只有当前会话的客户端处于活跃状态时,续约逻辑才会正常运行。如果会话处理过程中存在长时间空闲,可能触发SessionIdleTimeout(10分钟)导致会话被释放。
    • 不要在会话处理流程中切换客户端,否则原有客户端的续约会停止,会话锁会在超时后过期。

额外注意事项

  • 延迟消息本身不会持有会话锁,但读取延迟消息的操作必须由持有该会话锁的客户端执行。
  • 如果需要在会话处理流程之外访问延迟消息,必须先通过ServiceBusClient.AcceptSessionAsync获取该会话的锁,再创建对应的ServiceBusReceiver进行操作。

内容的提问来源于stack exchange,提问作者Bojan B

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 06:25:22