2011-09-28 43 views
4

我有间歇性的问题,从一个蓝色的表中删除的对象。它只影响我的1%的尝试,如果稍后再次进行同样的调用,那么它可以正常工作,但我很想知道它背后的原因!我已经使用了我的手指,我发现缺乏关于如何创建非常可靠的删除,插入和更新代码的文档,这是非常令人惊讶的......这一切似乎都有点失误,“尝试它,大部分时间它会工作“错误从碧霞表删除 - ResourceNotFound

编辑:我删除原来在这个问题中的文本,并用全新的文本取代它,以考虑我已经尝试/已经建议的事情。

待办事项表天青从像SQL Azure的间歇性故障困扰。如果是这样,我会尽管“saveChangesWithRetries”会处理这个问题吗?这是错的吗?

所以......相当简单的代码,被称为大约250倍的Azure的网络角色一分钟。天蓝色表格被用作消息传递系统的一部分。消息由一个用户插入,由另一个用户下载,成功下载时,这些消息被标记为已读。

每个用户具有用于未读消息的分区,和用于读消息的分区。因此,要将消息标记为“读取”,它会从未读分区中删除并移入读取分区。

250时该代码每分钟叫,我将在最后SaveChangesWithRetries收到以下错误2和10之间()。内部的例外是:

ResourceNotFound The specified resource does not exist. RequestId:652a3e13-3911-4503-8e49-6fec32a3c044 Time: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检查一下。

根据以往的回应,我添加代码来执行额外的检查,在尝试看看,如果该对象已被不小心删除。 它并没有被删除的错误

我跟踪返回此:

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.1: MessageID:146751012, BoyID:477296. An error occurred while processing this request. at Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.get_Result() at Microsoft.WindowsAzure.StorageClient.Tasks.Task 1.ExecuteAndWait() at BenderRestfulService_3_0_5.AzureCloudTable.Message_MarkAsRead(Int32 uid) in ... ResourceNotFound The specified resource does not exist. RequestId:583c59df-fdac-47e4-a03c-7a4bc7d004c9 Time:2011-10-05T16:37:36.7940530Z at System.Data.Services.Client.DataServiceContext.SaveResult.d__1e.MoveNext()

AzureCloudTable_3_0_5.Message_MarkAsRead. Error.Stage.2: MessageID:146751012, BoyID:477296. Message was NOT deleted.

我完全莫名其妙。任何建议非常感谢!

Steven

回答

1

问题解决了,我来解释一下我发现的情况下,它有利于任何人。

这是由于线程化(正如Smarx所迷惑的),我像一个SQL开发人员那样思考的事实,以及我会考虑一些Azure代码中相当奇怪/意外的行为,深入的例子!

因此,要解决它,我简化,尽可能的问题。

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

我创建的选择“A”,从该表中,删除了,它改变到“b”,并重新插入它一个控制台应用程序。

我跑的代码放在一个循环,数千次,而这一切工作的罚款。

我然后移动代码到一个线程,并推出两个线程。我立即被错误地击中,并在删除和插入之间丢失了实体。

问题1:两个线程既可以选择在同一实体,既可以检查,看它是否存在,那么既可以尝试将其删除。删除它时只有一个会成功。因此,您需要捕获错误,检查错误是否包含“ResourceNotFound”,如果是,则相信该对象确实消失并继续正常。

问题2:tableContext记住上次失败的动作,所以在删除失败的线程将抛出另一个错误上SaveChangesWithRetries你打电话ADDOBJECT后。所以,你需要使用一个新的tableContext为ADDOBJECT

问题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

有两种情况,但是当一个客户端(iPhone应用程序)调用的URL响应下载的消息......所以我想,如果它在被删除的机会的代码只执行一分之一的秒数真的很低。可能可能。但太低以至于不能删除1%的删除 –

+2

还有一些想法:尝试捕获错误并实际测试消息存在。它确实存在吗?另一个要尝试的是为每个逻辑操作使用一个新的tableServiceContext。否则,如果在一次SaveChanges过程中出现故障,下次可能会重试。 (上下文实际上保留了一些关于正在进行的更改的状态。) – smarx

+0

嘿Smarx。我尝试了你的建议,检查是否存在对象*错误说明没有。它*确实存在。我已经重写了我的问题,以包括迄今为止所了解的变化和所有内容。谢谢 –

0

默认情况下,MergeOption.AppendOnly被您的tableServiceContext.MergeOption使用,这意味着Azure表存储将跟踪您的表格项目。你应该在删除它之前取消它,像这样:

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

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

+0

谢谢,我现在就试试。你能向我解释为什么你会分离它,只是为了立即重新连接它吗?谢谢!Steven –

+0

我做了更改,但没有任何效果,我仍然每分钟收到2到10次的错误。我会尝试smarx的建议,即在我得到错误之后检查消息是否已经被删除,即使我在调用deleteObject之前检查过两次,以确定它是否存在,如果* *被删除,那么它可能是一个线索导致某处 –

+0

我很抱歉它没有工作。 Azure默认跟踪最后一个CRUD操作(即锁定)行。通过分离和重新附加物品,您可以摆脱此锁定。 – 2011-10-04 19:46:32