多个服务实例 - 如何保证只处理一次对象?

时间:2013-05-22 23:35:54

标签: c# azure

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

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

我将消息存储在Azure MSSQL数据库中,图像存储在blob中,并且它们的URL存储在数据库中。还有 MessageToImage 表,它存储指向消息图像的链接。这是一个简化的DB结构(原谅我的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 for Image”数据库逻辑将是原子的,并将返回消息的“缺失”图像数。这样,我将仅在一个实例上触发进一步处理 - 作为数据库更新的结果接收“0”的实例。但是:如何在MSSQL级别上执行此操作?而且更复杂 - 我怎样才能使用Entity Framework?

  2. 有一个专门的工作人员角色,该工作将选择包含所有图像的消息 - 并将其发送以进行处理。但这不能很好地扩展......看起来有点难看。

  3. 还有其他想法/建议吗?

    谢谢!


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

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

2 个答案:

答案 0 :(得分:2)

创建Azure Service Bus队列,让客户端应用程序将消息发布到队列中。然后,您的辅助角色可以从队列中提取消息并处理消息。

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

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

阅读本文以获取更多信息:

How to use Service Bus Queues

答案 1 :(得分:1)

您应该考虑使用Service Bus Messaging Sessions

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

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

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

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

以下是各种演员应该做些什么来实现你想要的东西:

客户端(一个或多个)

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

网络角色

  • 接收上传的图像时,将图像存储在blob存储中,将一个BrokeredMessage发布到一个Queue(“UploadsQueue”),其中包含SessionId集和Body包含的图像URL
  • 在查看上传的作业“消息”时,将“发布”(“JobsQueue”)发布为“UploadBatch”BrokeredMessage,其中Body包含sessionID和imageCount

工人角色

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