从Azure云表中删除时出错-ResourceNotFound



从azure表中删除对象时遇到间歇性问题。它只影响我大约1%的尝试,如果稍后再次拨打同一个电话,效果会很好,但我很想找出背后的原因!我在谷歌上搜索了一下,发现缺乏关于如何创建用于删除、插入和更新的非常可靠的代码的文档,这真是令人惊讶。。。这一切似乎都有点偶然,"试试看,大多数时候都会奏效">

编辑:我将删除本问题中最初的文本,并将其替换为全新的文本,以考虑到我尝试过/建议过的内容

Azure表是否会像SQL Azure一样出现间歇性故障。如果是这样的话,我会认为"saveChangesWithRetries"会处理这个问题吗?这错了吗?

所以。。。相当简单的代码,在Azure web角色上每分钟调用约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");
            }
        }
    }

我不明白当资源作为查询的一部分返回给我,然后我做了一个!=对其进行空检查。

基于之前的响应,我添加了代码来在中进行额外的检查,以查看对象是否已经以某种方式被删除尚未删除

我的跟踪返回错误:

AzureCloudTable_3_0_5。消息MarkAsRead。错误。阶段1:消息ID:146751012,男孩ID: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(中。。。ResourceNotFound指定的资源不存在。请求ID:583c59df-fdac-47e4-a03c-7a4bc7d004c9时间:2011-10-05T16:37:36.794030Z,位于System.Data.Services.Client.DataServiceContext.SaveResult.d_1e.MoveNext((

AzureCloudTable_3_0_5。消息MarkAsRead。错误。阶段2:消息ID:146751012,男孩ID:477296。邮件未被删除。

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

Steven

这段代码是在多个角色实例中运行还是在多个应用程序中运行?(可能在您从表中读取实体和尝试删除实体之间,另一个进程正在删除该实体。(

问题已经解决,我将解释我的发现,以防对其他人有利。

这是由于线程化(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");
    }

我相信有更好的方法可以做到这一点,但由于缺乏好的例子,我只选择对我有用的方法!

感谢

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

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

这应该可以消除任何项目跟踪问题。

相关内容

  • 没有找到相关文章

最新更新