我在从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。消息未被删除。
我完全不知所措。任何建议都非常感谢!!!
史蒂芬
答案 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();
这应该摆脱任何物品追踪问题。