同一订阅者的 Azure 服务总线多个实例
Azure service bus multiple instances for the same subscriber
我有一个 asp.net 核心应用程序,它在启动时将订阅客户端注册到主题 (IHostedService
),这个订阅客户端本质上有一个回调字典,需要每当它在主题中检测到带有 id 的新消息时触发(此 id 存储在消息属性中)。这个字典在应用程序的整个生命周期中都存在,并且在内存中。
在 Azure 上 asp.net 核心应用服务的单个实例上一切正常,一旦我扩展到 2,我注意到有时订阅中的回调没有触发。这是有道理的,因为我们现在有两个实例,每个实例都有自己的回调字典存储。
所以我更新了代码来检查订阅的id是否存在,如果不存在,则放弃消息,如果存在,则获取回调并调用它。
public async Task HandleMessage(Microsoft.Azure.ServiceBus.Message message, CancellationToken cancellationToken)
{
var queueItem = this.converter.DeserializeItem(message);
var sessionId = // get the session id from the message
if (string.IsNullOrEmpty(sessionId))
{
await this.subscriptionClient.AbandonAsync(message.SystemProperties.LockToken);
return;
}
if (!this.subscriptions.TryGetValue(sessionId, out var subscription))
{
await this.subscriptionClient.AbandonAsync(message.SystemProperties.LockToken);
return;
}
await subscription.Call(queueItem);
// subscription was found and executed. Complete message
await this.subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
}
但是,问题还是出现了。我唯一的猜测是,当调用 AbandonAsync
时,同一个实例再次接收消息?
我想我真正想问的是,如果我有一个主题订阅客户端的多个实例都指向该主题的同一个订阅者,是否有可能所有实例都获得消息的副本?还是不能保证。
if I have multiple instances of a topic subscription client all pointing to the same subscriber for the topic, is it possible for all the instances to get a copy of the message? Or is that not guaranteed.
没有。如果所有客户端都指向 相同 订阅,则只有一个会收到该消息。
您 运行 遇到了与竞争消费者进行横向扩展的问题。如果你要横向扩展,你永远不知道哪个实例会选择消息。并且由于您的状态是本地的(在每个实例的内存中),因此有时会失败。额外的缺点是成本。通过在 "wrong" 实例上获取消息并放弃,您将在消息传递方面支付更高的成本。
要解决此问题,您需要 shared/centralized 或围绕此更改架构。
我设法通过使用服务总线会话解决了这个问题。无论如何,我试图用回调字典做的基本上是一个会话管理器!
服务总线会话允许我拥有一个会话客户端的多个实例,它们都指向同一个订阅。但是,每个实例只会知道或关心它当前正在处理的会话。
我有一个 asp.net 核心应用程序,它在启动时将订阅客户端注册到主题 (IHostedService
),这个订阅客户端本质上有一个回调字典,需要每当它在主题中检测到带有 id 的新消息时触发(此 id 存储在消息属性中)。这个字典在应用程序的整个生命周期中都存在,并且在内存中。
在 Azure 上 asp.net 核心应用服务的单个实例上一切正常,一旦我扩展到 2,我注意到有时订阅中的回调没有触发。这是有道理的,因为我们现在有两个实例,每个实例都有自己的回调字典存储。
所以我更新了代码来检查订阅的id是否存在,如果不存在,则放弃消息,如果存在,则获取回调并调用它。
public async Task HandleMessage(Microsoft.Azure.ServiceBus.Message message, CancellationToken cancellationToken)
{
var queueItem = this.converter.DeserializeItem(message);
var sessionId = // get the session id from the message
if (string.IsNullOrEmpty(sessionId))
{
await this.subscriptionClient.AbandonAsync(message.SystemProperties.LockToken);
return;
}
if (!this.subscriptions.TryGetValue(sessionId, out var subscription))
{
await this.subscriptionClient.AbandonAsync(message.SystemProperties.LockToken);
return;
}
await subscription.Call(queueItem);
// subscription was found and executed. Complete message
await this.subscriptionClient.CompleteAsync(message.SystemProperties.LockToken);
}
但是,问题还是出现了。我唯一的猜测是,当调用 AbandonAsync
时,同一个实例再次接收消息?
我想我真正想问的是,如果我有一个主题订阅客户端的多个实例都指向该主题的同一个订阅者,是否有可能所有实例都获得消息的副本?还是不能保证。
if I have multiple instances of a topic subscription client all pointing to the same subscriber for the topic, is it possible for all the instances to get a copy of the message? Or is that not guaranteed.
没有。如果所有客户端都指向 相同 订阅,则只有一个会收到该消息。
您 运行 遇到了与竞争消费者进行横向扩展的问题。如果你要横向扩展,你永远不知道哪个实例会选择消息。并且由于您的状态是本地的(在每个实例的内存中),因此有时会失败。额外的缺点是成本。通过在 "wrong" 实例上获取消息并放弃,您将在消息传递方面支付更高的成本。
要解决此问题,您需要 shared/centralized 或围绕此更改架构。
我设法通过使用服务总线会话解决了这个问题。无论如何,我试图用回调字典做的基本上是一个会话管理器!
服务总线会话允许我拥有一个会话客户端的多个实例,它们都指向同一个订阅。但是,每个实例只会知道或关心它当前正在处理的会话。