Windows Azure登台< - >生产导致冲突&表存储错误

时间:2012-02-23 15:01:59

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

我们昨天尝试交换我们的升级时遇到了一个可怕的问题/经历< - >生产角色。

以下是我们的设置:

我们有一个workerrole从队列中获取消息。这些消息在角色上处理。 (表存储插入,db选择等)。每个队列消息可能需要1-3秒,具体取决于他需要制作多少个表存储帖子。一切都完成后,他将删除该消息。

交换时出现问题:

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

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

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

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

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

可能出现的问题?

我们已经知道实际上发生了什么。

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

可能的解决方案?

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

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

多个角色

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

非常感谢。

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

  • 我们实际使用 GetMessage()
  • 队列中的每个项目都是唯一的,并将在表存储中生成唯一的消息。关于该过程的更多信息:用户发布某些内容并且必须分发给某些其他用户。从该用户生成的消息将具有唯一的Id(guid)。此消息将发布到队列中并由worker角色获取。该消息分布在其他几个表中(partitionkey - > UserId,rowkey - >一些时间戳记在刻度和唯一的消息ID中。所以几乎没有机会在正常情况下发布相同的消息。
  • 隐身超时可能是一个逻辑解释,因为有些消息可以分发到10-20个表。这意味着10-20插入没有批处理选项。您可以设置或扩展此隐身时间吗?
  • 由于异常 COULD 而不删除队列消息也是一个解释,因为我们没有实现任何有害消息故障转移YET;)。

5 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

有几个可能的原因:

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

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

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

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

答案 2 :(得分:1)

在不知道你的工作者角色实际在做什么的情况下,我在这里猜测,但听起来好像你有两个工作者角色运行实例,你在尝试写入Azure表时遇到了冲突。这很可能是因为你的代码看起来像这样:

var queueMessage = GetNextMessageFromQueue();    

Foo myFoo = GetFooFromTableStorage(queueMessage.FooId);

if (myFoo == null)
{
    myFoo = new Foo {
                        PartitionKey = queueMessage.FooId
                    };

    AddFooToTableStorage(myFoo);
}

DeleteMessageFromQueue(queueMessage);

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

正如其他人所说,处理毒药信息是一个非常好的主意。

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

答案 3 :(得分:1)

对于队列,您需要考虑到幂等性并期望并处理“EntityAlreadyExists”作为可行的响应。

正如其他人所说,原因可能是

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

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

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

在多角色环境中有效地使用队列需要稍微不同的心态,并且尽早遇到这些问题实际上是伪装的祝福。

附加2/24

只是澄清一下,修改隐形时间并不是解决此类问题的通用方法。另请注意,虽然REST API上提供此功能,但可能在队列客户端上不可用。

其他选项涉及以异步方式写入表存储以加快处理时间,但这又是一个停止间隙度量,它并没有真正解决使用队列的基本范例。

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

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

答案 4 :(得分:1)

您在处理双重讯息时显然有问题。您的ID是唯一的这一事实并不意味着在某些情况下消息不会被处理两次,如:

  1. 角色死亡和部分完成的工作,因此消息将重新出现以便在队列中进行处理
  2. 角色崩溃意外,因此消息最终返回队列
  3. FC迁移移动您的角色而您没有代码来处理这种情况,因此消息最终会回到队列中
  4. 在所有情况下,您都需要能够处理消息重新出现的代码。一种方法是使用DequeueCount属性并检查消息从队列中删除并接收进行处理的次数。确保您拥有处理消息部分处理的代码。

    现在在交换过程中可能发生的事情是,当生产环境成为升级和升级生产时,他们都试图接收相同的消息,因此他们基本上互相竞争这些消息,这可能并不坏,因为无论如何,这是一种已知的模式,但是当你杀死旧的生产(暂存)收到的每个处理消息但尚未完成的消息时,最终回到队列中,你的新生产环境又选择了消息进行处理。没有代码逻辑来处理这种情况,并且消息是部分处理的,表中的一些记录存在并且它开始引起您注意到的行为。