从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();
这应该可以消除任何项目跟踪问题。