【问题标题】:Multiple instances of the service - how to guarantee that an object will be processed only once?服务的多个实例——如何保证一个对象只被处理一次?
【发布时间】:2013-05-23 13:40:08
【问题描述】:

我有以下设置:部署了 N 个 Azure Worker Role 实例。我们的桌面应用程序将一条消息上传到 Azure,然后上传一组与该消息相关的图像。 Message 知道它需要什么图像。

这 2 个活动(消息上传和图片上传)是独立的 - 甚至可以在用户生成消息之前上传图片(称为缓存 - 但它更复杂),或者几秒/分钟之后消息已上传到 Azure。

我将消息存储在 Azure MSSQL 数据库中,图像存储在 blob 中,它们的 URL 存储在数据库中。还有一个 MessageToImage 表,用于存储消息图像的链接。这是一个简化的数据库结构(请原谅我的 C#):

class Message
{
    public int Id;
    public string Text;
}

class Image
{
    public int Id;
    public string Name;
    public string BlobUrl;   // Null if image was not received by the service yet
}

class MessageToImage
{
    public int MessageId;
    public List<int> ImageIds;
}

当我们准备好所有图片的消息(即所有图片都已上传)时,我们需要对其进行其他操作(例如,发布到 Facebook)。 问题来了:我如何保证该消息只会被处理一次?在最坏的情况下,我将有 N 个实例同时接收 N 个消息的图像 - 哪个实例将“选择”它应该将消息发送到进一步处理?我怎么能保证它只会发生一次?

到目前为止,我想出了以下想法:

  1. 确保“为图像更新 BlobUrl”数据库逻辑将是原子的,并将返回消息的“丢失”图像的数量。这样,我将仅在一个实例上触发进一步处理 - 作为数据库更新结果接收“0”的实例。但是:我怎样才能在 MSSQL 级别上做到这一点?更复杂 - 我如何使用实体框架来做到这一点?

  2. 有一个专门的工作角色,该工作将选择包含所有图像的消息 - 并将它们发送给处理。但这并不能很好地扩展……而且看起来有点难看。

还有其他想法/建议吗?

谢谢!


更新1 @Richard 和 @Rob 建议使用服务总线队列。我确实调查过了。我仍然没有答案的部分是 WORKER ROLE 中决定何时将消息发送到队列进行处理的代码应该是什么样的?仅当数据库/blob 中存在所有图像(即上传到 Azure 云)时,消息才会发送到队列。在这里,我仍然想指出我的极端案例——我有 10 个图像同时由 10 个工作角色处理。对于所有实例,处理同时结束。每个角色都使用上传的图像 URL 更新数据库。然后我应该以某种方式触发最终的消息处理——这意味着其中一个实例应该获得优先级。而且我不清楚我应该如何做到这一点。

希望这能让我的问题更清晰。

【问题讨论】:

  • "-1" - 敢解释为什么?

标签: c# azure


【解决方案1】:

创建一个 Azure 服务总线队列,让您的客户端应用程序将消息发布到队列。然后,您的工作角色可以从队列中提取消息并处理消息。

服务总线队列的优点在于,它们保证消息只能从队列中取出一次,然后将消息标记为“已获取”。如果事务未在(可配置的)时间段内标记为完成,则消息将返回到队列,准备好被下一个工作请求拉取。

这意味着,如果您的工作人员角色在处理过程中失败,消息最终将重新出现在队列中,以便下一个工作人员接手并(希望)完成所需的工作。

阅读本文了解更多信息:

How to use Service Bus Queues

【讨论】:

  • 感谢您的回答。我确实查看了服务总线队列,但是我的担忧仍然相同。我更新了我的问题 - 请看一下。
  • 从您上面的更新中,尚不清楚谁在上传图片 - 客户或工人角色?据我了解您的情况,您的客户端上传了一堆图像,然后向队列发送一条消息,请求处理图像。然后,您的工作角色可以从队列中提取消息并处理消息中列出的图像。根本不需要 SQL!
  • 客户端对队列等一无所知。客户端(桌面应用程序)将图像和消息上传到工作角色。消息到达后,worker 角色需要等待所有图片,然后发送消息进行进一步处理。
  • @avs。可以修改客户端以将消息发布到 Azure 队列吗?客户端也可以修改为在所有图像上传后发布消息(我建议将图像直接上传到 Blob 存储)。这样,worker 角色就不需要检查图像是否已经上传,而可以从队列中拉取消息并进行相应的处理。
【解决方案2】:

你应该考虑使用Service Bus Messaging Sessions

在您的客户端上为上传会话生成一个唯一的批量上传 ID(使用 GUID 而不是 int)。上传的每张图片都应附有此 ID(上传批次中所有图片的 ID 相同)。当您的服务收到图像时,它会将“上传图像” BrokeredMessage 发布到服务总线队列或主题,并将 SessionId 设置为客户端提供的唯一 ID。

当您的消息从客户端发送到服务时,发送批量上传 ID。当从服务收到此消息时,会在另一个队列/主题上发布“处理所有上传”BrokedMessage。这个 BrokeredMessage 的接收者将读取批量上传 Id,然后开始侦听与相关 sessionId 关联的所有“上传图像”消息。它可以继续执行此操作,直到全部收到(可能需要发送上传的总计数,以便知道何时停止)。一旦收到它们,它就可以生成一个新的 BrokeredMessage 来触发处理图像上传(例如发布到 facebook)。

您希望为最终处理阶段(发布到 facebook)生成单独的 BrokeredMessage,以便如果您的角色在处理消息时失败,稍后将重新传递消息。

要记住的是,“至少一次”接收到消息,如果您的角色在处理消息中途失败,则消息将在超时后返回到队列/主题。因此,您的处理逻辑需要能够处理它正在处理之前已经部分处理的消息的情况。

以下是各个参与者为实现您想要的大致应该做的事情:

客户

  • 生成唯一的 sessionId(或从您的 Web 服务请求一个)
  • 将每个图像上传到 Web 角色,并带有关联的 sessionId
  • 将作业“消息”上传到 Web 角色,并在上传批处理中关联 sessionId 和 imageCount(可以在图像上传之前/期间/之后完成)

网络角色

  • 当接收到上传的图片时,将图片存储在 blob 存储中,向队列 ("UploadsQueue") 发送带有 SessionId 集和包含图片 URL 的正文的 BrokeredMessage
  • 在接收上传的作业“消息”时,向队列 (“JobsQueue”) 发布一个“UploadBatch” BrokeredMessage,其正文包含 sessionID 和 imageCount

工人角色

  • 当接收到来自“JobsQueue”的“UploadBatch”消息时,从消息正文中获取 sessionId 和 imageCount,在“UploadsQueue”上的 AcceptMessageSession(sessionId) 并继续轮询该会话中的消息,直到收到所有上传消息.一次 (receivedCount == imageCount) 将“SendToFacebook”消息发布到“JobsQueue”,并在正文中包含要处理的图像列表等
  • 当从“JobsQueue”接收到“SendToFacebook”消息时,从消息正文中获取要处理的图像列表并将它们发送到 facebook 等

【讨论】:

  • 感谢您的回答。我更新了我的问题,请看一下。
  • 诀窍是在上传每张图片时为每张图片发布一个 BrokeredMessage,而不是在所有图片都完成上传后尝试发布一条消息。我将更新我的答案,详细说明 workerRole/webRole/client 完成了哪些位
  • hmmm...所以基本上我需要另一个工作角色实例来监听队列并进行最终处理 - 我是否正确?老实说,我希望在 10 个实例之间“传播”最终处理。
  • 您可以让现有的图像处理工作者角色也监听第二个队列并进行最终处理,您可能希望启动一些线程来监听每个队列,而不是使用单个循环在 WorkerRole.Run 方法中的一个队列上
猜你喜欢
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
  • 2019-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
  • 2012-04-17
相关资源
最近更新 更多