从Azure云表删除时出错 - ResourceNotFound

时间:2011-09-28 22:24:57

标签: linq azure azure-table-storage

我在从azure表中删除对象时出现间歇性问题。它只会影响我尝试的大约1%,如果稍后再次拨打同一个电话,那么它工作正常,但我很想找出它背后的原因!我已经用手指搜索了一下,我发现缺少关于如何创建非常可靠的代码以进行删除,插入和更新的文档非常令人惊讶......这一切似乎都有点受欢迎,“尝试一下,大多数时候它会起作用“

编辑:我正在删除此问题中原来的文字,并将其替换为全新的文字,以考虑我尝试过/已经建议的内容。

Azure表遭遇SQL Azure等间歇性故障。如果是这样,我会尽管“saveChangesWithRetries”会处理那个?这是错的吗?

所以......相当简单的代码,在Azure网络角色上每分钟调用约250次。 azure表用作消息传递系统的一部分。消息由一个用户插入,由另一个用户下载,成功下载后,这些消息被标记为已读。

每个用户都有一个未读邮件分区和一个读取邮件分区。因此,要将消息标记为“已读”,它将从未读分区中删除并移入读取分区。

在每分钟调用此代码的250次中,我将在最终的SaveChangesWithRetries()中收到以下2到10个错误。内在的例外是:

  

  ResourceNotFound   指定的资源不存在。   请求ID:652a3e13-3911-4503-8e49-6fec32a3c044   时间:2011-09-28T22:09:39.0795651Z

我不认为每分钟访问的个别分区超过几次。

这是我的代码:

    public static void Message_MarkAsRead(int uid)
    {
        try
        {
            storageAccount = CloudStorageAccount.Parse(connectionString);
            tableClient = new CloudTableClient(storageAccount.TableEndpoint.AbsoluteUri, storageAccount.Credentials);
            tableClient.RetryPolicy = RetryPolicies.Retry(retryAmount, TimeSpan.FromSeconds(retrySeconds));

            TableServiceContext tableServiceContext = tableClient.GetDataServiceContext();
            tableServiceContext.IgnoreResourceNotFoundException = true;

            //the messageUserJoinerTable let's us join messageId to userFromId and userToId
            //each message is inserted into the tables twice, once into the userFromId partition and also into the userToId partition
            #region get the userToId and userFromId for this message uid
            List<int> userIds = new List<int>();
            var resultsUserIds = from messagesUserJoinerTable in tableServiceContext.CreateQuery<MessageUserJoinerDataEntity>(messageUserJoinerTableName)
                                where messagesUserJoinerTable.PartitionKey == uid.ToString()
                                select messagesUserJoinerTable;

            foreach (MessageUserJoinerDataEntity messageUserJoiner in resultsUserIds)
            {
                userIds.Add(messageUserJoiner.UserId);
            }
            #endregion

            #region then we need to check the partition for each of these users and mark the messages as read
            if (userIds.Count > 0)
            {
                foreach (int userId in userIds)
                {
                    var resultsUnreadMessages = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName)
                                                where messagesTable.PartitionKey == CreatePartitionKey(userId, false)
                                                && messagesTable.RowKey == CreateRowKey(uid)
                                                select messagesTable;

                    //there should only ever be one as duplicate partition/rowkey is not allowed
                    MessageDataEntity messageUnread = resultsUnreadMessages.FirstOrDefault();

                    if (messageUnread != null)
                    {
                        bool isUnreadMessageDeleted = false;

                        //shallow copy the message for re-inserting as read
                        MessageDataEntity messageRead = new MessageDataEntity(messageUnread);

                        //delete the message
                        try
                        {
                            tableServiceContext.Detach(messageUnread);
                            tableServiceContext.AttachTo(messageTableName, messageUnread, "*");
                            tableServiceContext.DeleteObject(messageUnread);
                            //this is where the error occurs
                            tableServiceContext.SaveChangesWithRetries();

                            isUnreadMessageDeleted = true;
                        }
                        catch (Exception ex)
                        {
                            MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.1: MessageID:" + uid + ", UserID:" + userId + ". " + ex.Message + ex.StackTrace + ", " + ex.InnerException.Message + ex.InnerException.StackTrace, "Error. MarkAsRead");

                            //check to see if the message we tried to delete has already been deleted
                            //if so, we just consume this error and continue by inserting the read message
                            //else, we throw the exception outwards
                            var resultsUnreadMessagesLastCheck = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName)
                                                                 where messagesTable.PartitionKey == CreatePartitionKey(userId, false)
                                                                 && messagesTable.RowKey == CreateRowKey(uid)
                                                                 select messagesTable;

                            //there should only ever be one as duplicate partition/rowkey is not allowed
                            MessageDataEntity messageUnreadLastCheck = resultsUnreadMessages.FirstOrDefault();

                            if (messageUnreadLastCheck != null)
                            {
                                MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.2: MessageID:" + uid + ", UserID:" + userId + ". Message WAS deleted.", "Error. MarkAsRead");

                                //the message IS deleted, so although I don't understand why getting error in the first 
                                //place, the result should be the same
                                throw ex;
                            }
                            else
                            {
                                //the message is NOT deleted, so we may as well give up now as I don't understand
                                //what's going on
                                MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error.Stage.2: MessageID:" + uid + ", UserID:" + userId + ". Message was NOT deleted.", "Error. MarkAsRead");
                            }
                        }

                        //mark the new message as read
                        if (isUnreadMessageDeleted)
                        {
                            messageRead.PartitionKey = CreatePartitionKey(userId, true);
                            messageRead.IsRead = true;

                            //check if read message already exists in storage, if not, insert
                            var resultsReadMessages = from messagesTable in tableServiceContext.CreateQuery<MessageDataEntity>(messageTableName)
                                                      where messagesTable.PartitionKey == CreatePartitionKey(userId, true)
                                                      && messagesTable.RowKey == CreateRowKey(uid)
                                                      select messagesTable;

                            //do the insert
                            if (resultsReadMessages.FirstOrDefault() == null)
                            {
                                tableServiceContext.AddObject(messageTableName, messageRead);
                                tableServiceContext.SaveChangesWithRetries();
                            }
                        }
                    }
                }
            }
            #endregion
        }
        catch (Exception ex)
        {
            try
            {
                MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error: " + ex.Message + ex.StackTrace + ", " + ex.InnerException.Message + ex.InnerException.StackTrace, "Error. MarkAsRead");
            }
            catch (Exception)
            {
                MyTrace.Trace("AzureCloudTable_" + Service.versionForTracing + ".Message_MarkAsRead. Error: " + ex.Message + ex.StackTrace, "Error. MarkAsRead");
            }
        }
    }

我不明白资源如何可能不存在,当它作为查询的一部分返回给我时,然后我对它进行了!= null检查。

根据之前的回复,我添加了代码,在try中进行额外的检查,看看对象是否已经以某种方式被删除。 尚未删除

我的跟踪在错误时返回此信息:

  

AzureCloudTable_3_0_5.Message_MarkAsRead。 Error.Stage.1:MessageID:146751012,BoyID:477296。处理此请求时发生错误。在Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.get_Result() at Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.ExecuteAndWait()在BenderRestfulService_3_0_5.AzureCloudTable.Message_MarkAsRead(Int32 uid)in ... ResourceNotFound指定的资源不存在。 RequestId:583c59df-fdac-47e4-a03c-7a4bc7d004c9时间:2011-10-05T16:37:36.7940530Z在System.Data.Services.Client.DataServiceContext.SaveResult.d__1e.MoveNext()

     

AzureCloudTable_3_0_5.Message_MarkAsRead。 Error.Stage.2:MessageID:146751012,BoyID:477296。消息未被删除。

我完全不知所措。任何建议都非常感谢!!!

史蒂芬

3 个答案:

答案 0 :(得分:2)

此代码是在多个角色实例中运行还是在多个应用中运行? (也许在您从表中读取实体和尝试删除它的时间之间,另一个进程正在删除该实体。)

答案 1 :(得分:1)

问题解决了,我将解释我发现的内容,以防其他人受益。

这是由于线程化(正如Smarx所证实的),我正在考虑像SQL开发人员这样的事实,以及我会考虑Azure代码的一些奇怪/意外行为以及真正缺乏深度例子!

因此,为了解决这个问题,我尽可能地简化了问题。

我创建了一个包含一个实体的表,PartitionKey'a',RowKey'1'。

我创建了一个控制台应用程序,从表中选择“a”,删除它,将其更改为“b”并重新插入。

我在一个循环上运行代码,数千次,而且一切正常。

然后我将代码移动到一个线程中,并启动了两个线程。我立刻遇到了错误,并在删除和插入之间丢失了实体。

问题1:两个线程都可以同时选择实体,两者都可以检查它是否存在,然后两者都可以尝试删除它。删除时只有一个会成功。因此,您需要捕获错误,检查错误是否包含“ResourceNotFound”,如果是这样,请相信对象确实已经消失并继续正常运行。

问题2:tableContext会记住上次失败的操作,因此删除失败的线程将在调用AddObject后在SaveChangesWithRetries上引发另一个错误。因此,您需要为AddObject使用新的tableContext

问题3:两个线程都有机会添加实体,但只有一个会成功。即使两个线程在添加之前检查对象是否存在,它们都可以认为它不存在并且都试图添加它。因此,为简单起见,让两个线程尝试添加它,一个将成功,一个将抛出“EntityAlreadyExists”错误。只是抓住这个错误并继续。

这是我这个简单示例的工作代码,我在原始问题中为我更复杂的示例修改了它,现在根本没有收到任何错误。

    //method for shifting an entity backwards and forwards between two partitions, a and b
    private static void Shift(int threadNumber)
    {
        Console.WriteLine("Launching shift thread " + threadNumber);

        //set up access to the tables
        _storageAccount = CloudStorageAccount.Parse(_connectionString);
        _tableClient = new CloudTableClient(_storageAccount.TableEndpoint.AbsoluteUri, _storageAccount.Credentials);
        _tableClient.RetryPolicy = RetryPolicies.Retry(_retryAmount, TimeSpan.FromSeconds(_retrySeconds));

        int lowerLimit = threadNumber * _limit;
        int upperLimit = (threadNumber + 1) * _limit;

        for (int i = lowerLimit; i < upperLimit; i++)
        {
            try
            {
                TableServiceContext tableServiceContextDelete = _tableClient.GetDataServiceContext();
                tableServiceContextDelete.IgnoreResourceNotFoundException = true;

                string partitionKey = "a";

                if (i % 2 == 1)
                {
                    partitionKey = "b";
                }

                //find the object with this partition key
                var results = from table in tableServiceContextDelete.CreateQuery<TableEntity>(_tableName)
                              where table.PartitionKey == partitionKey
                                && table.RowKey == "1"
                              select table;

                TableEntity tableEntity = results.FirstOrDefault();

                //shallow copy it
                if (tableEntity != null)
                {
                    TableEntity tableEntityShallowCopy = new TableEntity(tableEntity);

                    if (tableEntityShallowCopy.PartitionKey == "a")
                    {
                        tableEntityShallowCopy.PartitionKey = "b";
                    }
                    else
                    {
                        tableEntityShallowCopy.PartitionKey = "a";
                    }

                    //delete original
                    try
                    {
                        tableServiceContextDelete.Detach(tableEntity);
                        tableServiceContextDelete.AttachTo(_tableName, tableEntity, "*");
                        tableServiceContextDelete.DeleteObject(tableEntity);
                        tableServiceContextDelete.SaveChangesWithRetries();
                        Console.WriteLine("Thread " + threadNumber + ". Successfully deleted. PK: " + tableEntity.PartitionKey);
                    }
                    catch (Exception ex1)
                    {
                        if (ex1.InnerException.Message.Contains("ResourceNotFound"))
                        {
                            //trying to delete an object that's already been deleted so just continue
                        }
                        else
                        {
                            Console.WriteLine("Thread " + threadNumber + ". WTF?! Unexpected error during delete. Code: " + ex1.InnerException.Message);
                        }
                    }

                    //move into new partition (a or b depending on where it was taken from)
                    TableServiceContext tableServiceContextAdd = _tableClient.GetDataServiceContext();
                    tableServiceContextAdd.IgnoreResourceNotFoundException = true;

                    try
                    {
                        tableServiceContextAdd.AddObject(_tableName, tableEntityShallowCopy);
                        tableServiceContextAdd.SaveChangesWithRetries();
                        Console.WriteLine("Thread " + threadNumber + ". Successfully inserted. PK: " + tableEntityShallowCopy.PartitionKey);
                    }
                    catch (Exception ex1)
                    {
                        if (ex1.InnerException.Message.Contains("EntityAlreadyExists"))
                        {
                            //trying to add an object that already exists, so continue as normal
                        }
                        else
                        {
                            Console.WriteLine("Thread " + threadNumber + ". WTF?! Unexpected error during add. Code: " + ex1.InnerException.Message);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error shifting: " + i + ". Error: " + ex.Message + ". " + ex.InnerException.Message + ". " + ex.StackTrace);
            }
        }

        Console.WriteLine("Done shifting");
    }

我确信有更好的方法可以做到这一点,但由于缺乏好的例子,我只是想找一些对我有用的东西!

由于

答案 2 :(得分:0)

默认情况下MergeOption.AppendOnly使用tableServiceContext.MergeOption,这意味着Azure表存储将跟踪您的表项。您应该在删除项目之前将其分离,如下所示:

tableServiceContext.Detach(messageUnread);
tableServiceContext.AttachTo(messageTableName, messageUnread, "*");
tableServiceContext.DeleteObject(messageUnread);
tableServiceContext.SaveChangesWithRetries();

这应该摆脱任何物品追踪问题。