【问题标题】:Windows Azure staging <--> production causing conflicts & errors on table storageWindows Azure 暂存 <--> 生产导致表存储冲突和错误
【发布时间】:2012-03-14 00:05:09
【问题描述】:

昨天我们在尝试交换我们的舞台生产角色时遇到了一个可怕的问题/经历。

这是我们的设置:

我们有一个工人角色从队列中提取消息。这些消息在角色上进行处理。 (表存储插入、数据库选择等)。每个队列消息可能需要 1-3 秒,具体取决于他需要创建多少表存储帖子。一切完成后,他会删除该消息。

交换时的问题:

当我们的暂存项目上线时,我们的生产工人角色开始出错。

当角色想要处理队列消息时,它会给出一个恒定的“EntityAlreadyExists”错误流。由于这些错误,队列消息没有被删除。这导致队列消息被放回队列中并返回处理等等......

在查看这些队列消息并分析它们会发生什么时,我们看到它们实际上已被处理但没有被删除。

删除这些错误消息时问题并未结束。新的队列消息也没有处理,而这些还没有处理,也没有添加表存储记录,这听起来很奇怪。

当删除暂存和生产并再次发布到生产时,一切都开始正常工作了。

可能的问题?

我们只有小 2 不知道实际发生了什么。

  • 也许两个角色都收到了相同的消息,一个发了帖子,一个出错了?
  • ...???

可能的解决方案?

我们对如何解决这个“问题”有一些想法。

  • 制作有害消息故障转移系统?当出队计数超过 X 时,我们应该删除该队列消息或将其放入单独的“毒队列”中。
  • 捕获 EntityAlreadyExists 错误并删除该队列消息或将其放入单独的队列中。
  • ...????

多个角色

我想我们在设置多个角色时会遇到同样的问题?

非常感谢。

编辑 24/02/2012 - 额外信息

  • 我们实际上使用 GetMessage()
  • 队列中的每个项目都是唯一的,并且会在表 Storage 中生成唯一的消息。关于这个过程的更多信息:用户发布了一些东西,并且必须分发给某些其他用户。从该用户生成的消息将具有唯一的 Id (guid)。此消息将被发布到队列中并由工作角色拾取。消息分布在其他几个表中(partitionkey -> UserId、rowkey -> 一些时间戳记和唯一消息 ID。因此,在正常情况下几乎不可能发布相同的消息。
  • 隐形超时可能是合乎逻辑的解释,因为某些消息可能会分发到 10-20 个表格。这意味着没有批处理选项的 10-20 插入。你能设置或延长这个隐身时间吗?
  • 由于异常而未删除队列消息可能也是一种解释,因为我们还没有实施任何有害消息故障转移;)。

【问题讨论】:

标签: azure azure-storage azure-worker-roles azure-queues


【解决方案1】:

无论暂存与生产问题如何,拥有处理有害消息的机制都至关重要。我们在 Azure 队列上实现了一个抽象层,一旦尝试处理一些可配置的次数,它就会自动将消息移动到有害队列。

【讨论】:

    【解决方案2】:

    您在处理双重消息方面显然存在错误。您的 ID 是唯一的这一事实并不意味着该消息在某些情况下不会被处理两次,例如:

    1. 角色死亡且工作部分完成,因此消息将重新出现以在队列中进行处理
    2. 角色意外崩溃,因此消息最终回到队列中
    3. FC 正在迁移您的角色,而您没有代码来处理这种情况,因此消息最终回到队列中

    在所有情况下,您都需要处理消息将重新出现这一事实的代码。一种方法是使用DequeueCount 属性并检查消息从队列中删除并接收处理的次数。确保您有处理消息部分处理的代码。

    现在交换过程中可能发生的情况是,当生产环境变成 staging 并且 staging 变成生产环境时,他们都试图接收相同的消息,所以他们基本上是在相互竞争这些消息,这可能还不错,因为无论如何,这是一种已知的工作模式,但是当您终止旧的生产(暂存)时,接收到的每条消息进行处理但尚未完成,最终回到队列中,您的新生产环境再次选择消息进行处理。没有代码逻辑来处理这种情况,并且消息被部分处理,表中存在一些记录,并且它开始导致您注意到的行为。

    【讨论】:

      【解决方案3】:

      有几个可能的原因:

      您如何阅读队列消息?如果您正在执行 Peek Message,则在删除消息之前,该消息仍将可见,以便由另一个角色实例(或您的暂存环境)拾取。您要确保您使用的是“获取消息”,以便在可以删除之前,该消息是不可见的。

      您的第一个角色是否有可能在为消息完成工作后但在删除消息之前崩溃?这将导致消息再次可见并被另一个角色实例拾取。届时,该消息将成为有害消息,会导致您的实例不断崩溃。

      这个问题几乎可以肯定与暂存与生产无关,但很可能是由于多个实例从同一个队列中读取而引起的。您可以通过指定 2 个实例,或者通过将相同的代码部署到 2 个不同的生产服务,或者通过使用 2 个实例在您的开发机器上本地运行代码(仍然指向 Azure 存储)来重现相同的问题。

      一般而言,您确实需要处理有害消息,因此无论如何您都需要实现该逻辑,但我建议您首先找到此问题的根本原因,否则您以后只会遇到更多问题。

      【讨论】:

      • 感谢您的评论。我已经用更多信息编辑了我的帖子:)
      【解决方案4】:

      使用队列时,您需要在编写代码时考虑幂等性,并将“EntityAlreadyExists”作为可行的响应进行处理。

      正如其他人所建议的,原因可能是

      • 队列中有多个具有相同标识符的消息。
      • 正在查看消息,而不是从队列中读取消息,因此不会使它们不可见。
      • 不删除邮件,因为在您删除邮件之前引发了异常。
      • 处理消息的时间过长,因此无法删除(因为隐身已超时)并再次出现

      不看代码,我猜测它是 3 或 4 选项正在发生。

      如果您无法通过代码审查发现问题,您可以考虑添加基于时间的日志记录和 try/catch 包装器以更好地理解。

      在多角色环境中有效地使用队列需要稍微不同的心态,尽早遇到此类问题实际上是因祸得福。

      附加 2/24

      澄清一下,修改隐身超时并不是解决此类问题的通用解决方案。此外,请注意,此功能虽然在 REST API 上可用,但在队列客户端上可能不可用。

      其他选项涉及以异步方式写入表存储以加快处理时间,但这也是权宜之计,并未真正解决使用队列的基本范式。

      所以,底线是幂等的。您可以尝试使用表存储 upsert(更新或插入)功能来避免出现“EntitiyAlreadyExists”错误,如果这适用于您的代码。如果您所做的只是将新实体插入到 azure 表存储中,那么 upsert 应该以最少的代码更改来解决您的问题。

      如果您正在进行更新,那么这完全是一场不同的球赛。一种模式是将更新与具有相同分区键的同一表中的虚拟插入配对,以便在更新之前发生时出错并跳过更新。稍后删除消息后,您可以删除虚拟插入。然而,所有这些都增加了复杂性,因此最好重新审视产品的架构;例如,你真的需要插入/更新这么多表吗?

      【讨论】:

      • 您的 3 和 4 选项“看起来”确实正确。我已经发布了更多信息。
      【解决方案5】:

      在不知道您的辅助角色实际上在做什么的情况下,我在这里进行猜测,但听起来当您运行两个辅助角色的实例时,您在尝试写入 Azure 表时会遇到冲突。这可能是因为您的代码看起来像这样:

      var queueMessage = GetNextMessageFromQueue();    
      
      Foo myFoo = GetFooFromTableStorage(queueMessage.FooId);
      
      if (myFoo == null)
      {
          myFoo = new Foo {
                              PartitionKey = queueMessage.FooId
                          };
      
          AddFooToTableStorage(myFoo);
      }
      
      DeleteMessageFromQueue(queueMessage);
      

      如果队列中有两条相邻的消息具有相同的FooId,则很可能最终两个实例都会检查Foo 是否存在,但没有找到它然后尝试创建它。无论哪个实例是最后尝试保存该项目的实例,都会收到“实体已存在”错误。因为它出错了,所以它永远不会到达代码的删除消息部分,因此它会在一段时间后重新出现在队列中。

      正如其他人所说,处理有害消息是一个非常好的主意。

      27/02 更新 如果不是后续消息(根据您的分区/行键方案,我会说这不太可能),那么我的下一个赌注是在可见性超时后出现在队列中的相同消息。默认情况下,如果您使用.GetMessage(),则超时为 30 秒。它有一个overload,它允许您指定该时间范围有多长。还有.UpdateMessage() function 允许您在处理消息时更新该超时。例如,您可以将初始可见性设置为 1 分钟,然后如果 50 秒后您仍在处理消息,则将其延长一分钟。

      【讨论】:

      • 这些消息是 100% 唯一的 :) 我已经更新了我的帖子,提供了更多信息。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-07-01
      • 1970-01-01
      • 2014-02-05
      • 1970-01-01
      • 2011-07-20
      • 2018-07-31
      • 2013-03-08
      相关资源
      最近更新 更多