正确的删除并重新创建Windows Azure存储表的方法 = 错误409冲突 - 代码:TableBeingDeleted

18

我对Windows Azure开发非常陌生,但有一个将一些数据存储在Windows Azure存储表中的要求。

这个表实际上只存在于提供快速查找机制并位于Azure存储驱动器上的某些文件的目的。

因此,我计划在应用程序启动时(即在Web应用程序全局应用程序启动时)填充此表。

与在应用程序不运行时可能发生的驱动器更改相比,维护此表会比较麻烦。或者,由于该驱动器只是资源的虚拟硬盘,我们可能会偶尔上传新的虚拟硬盘。

因此,与其费心去维护它,每次应用程序启动时重新构建此表就足够了。

我开始编写一些代码来检查该表是否已存在,如果存在,则删除它,然后重新创建一个新表。

var storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["AzureStorage"].ConnectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var rmsTable = tableClient.GetTableReference("ResourceManagerStorage");
rmsTable.DeleteIfExists();
rmsTable.Create();

我本以为这不会管用。结果我得到了以下错误:

The remote server returned an error: (409) Conflict. 

HTTP/1.1 409 Conflict
Cache-Control: no-cache
Transfer-Encoding: chunked
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: c6baf92e-de47-4a6d-82b3-4faec637a98c
x-ms-version: 2012-02-12
Date: Tue, 19 Mar 2013 17:26:25 GMT

166
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
  <code>TableBeingDeleted</code>
  <message xml:lang="en-US">The specified table is being deleted. Try operation later.
RequestId:c6baf92e-de47-4a6d-82b3-4faec637a98c
Time:2013-03-19T17:26:26.2612698Z</message>
</error>
0

怎样才是正确的做法?是否有事件可供订阅,以便在表格被删除时得到通知?对于实现这一点的最佳方法,您有其他建议吗?

2个回答

30

来自MSDN的提示: 注意,删除表可能需要至少40秒才能完成。 如果在删除表时尝试对该表执行操作,则服务将返回状态码409(冲突),并显示附加的错误信息,指示正在删除该表。

唯一的解决方法是使用不同名称创建一个表。 这可以简单地将时间戳或GUID添加到您的名称中。 只需小心清理您的垃圾即可。


或者,您可以等待并重试,直到Create命令成功。 - Igorek
3
根据桌子的尺寸和许多其他因素,这可能需要很长时间。 - IngisKahn
这是我发现的最佳实现重试逻辑的方法:https://dev59.com/h3I_5IYBdhLWcg3wBuNs#1563234 - douglaslps
1
2015年,同样的问题。我的解决方案是:我在周围保留一个元数据表,以跟踪每个表的版本。所有查询首先访问元数据表,以查看要先加载哪个表(X1、X2...Xn)。在删除/重新创建时,我加载到新表,更新元数据表,然后删除旧表。 - David Betz
3
对于自动化环境脚本来说,这是一个痛点,因为它们需要拆除和重新创建数据库等。可能只需要不断尝试,并在尝试之间等待一段时间直到成功即可。 - richard
1
值得注意的是,我在删除队列(而不是表)后遇到了这个问题。 - Fijjit

15
如果您需要使用相同的表名,您可以使用扩展方法:
public static class StorageExtensions
{
    #region Non-async

    public static bool SafeCreateIfNotExists(this CloudTable table, TimeSpan? timeout, TableRequestOptions requestOptions = null, OperationContext operationContext = null)
    {
        Stopwatch sw = Stopwatch.StartNew();
        if (timeout == null) timeout = TimeSpan.FromSeconds(41); // Assuming 40 seconds max time, and then some.
        do
        {
            if (sw.Elapsed > timeout.Value) throw new TimeoutException("Table was not deleted within the timeout.");

            try
            {
                return table.CreateIfNotExists(requestOptions, operationContext);
            }
            catch (StorageException e) when(IsTableBeingDeleted(e))
            {
                Thread.Sleep(1000);
            }
        } while (true);
    }

    #endregion

    #region Async

    public static async Task<bool> SafeCreateIfNotExistsAsync(this CloudTable table, TimeSpan? timeout, TableRequestOptions requestOptions = null, OperationContext operationContext = null, CancellationToken cancellationToken = default)
    {
        Stopwatch sw = Stopwatch.StartNew();
        if (timeout == null) timeout = TimeSpan.FromSeconds(41); // Assuming 40 seconds max time, and then some.
        do
        {
            if (sw.Elapsed > timeout.Value) throw new TimeoutException("Table was not deleted within the timeout.");

            try
            {
                return await table.CreateIfNotExistsAsync(requestOptions, operationContext, cancellationToken).ConfigureAwait(false);
            }
            catch (StorageException e) when(IsTableBeingDeleted(e))
            {
                // The table is currently being deleted. Try again until it works.
                await Task.Delay(1000);
            }
        } while (true);
    }

    #endregion

    private static bool IsTableBeingDeleted(StorageException e)
    {
        return
            e.RequestInformation.HttpStatusCode == 409
            &&
            e.RequestInformation.ExtendedErrorInformation.ErrorCode.Equals( TableErrorCodeStrings.TableBeingDeleted );
    }
}

警告!使用此方法时要小心,因为它会阻塞线程。如果第三方服务(Azure)不断生成这些错误,它可能会进入死循环。造成这种情况的原因可能是表锁定、订阅过期、服务不可用等。


5
哇,这不是一个好主意。通常当逻辑依赖于第三方服务时,我会尽量避免使用while(true)。在这里使用while(retryCnt>0)会更好。 - Roman Pushkin
1
当然,你可以传递一些超时参数或硬编码一些超时。但是你会建议什么值?1分钟、2分钟、20分钟?IngisKahn说:“...可能需要至少40秒才能完成。”所以你需要一些高值,而且你永远不知道如果你等待多一秒,这个过程是否已经完成。如果服务不可用或存在其他问题,我无法想象微软会返回相同的错误TableErrorCodeStrings.TableBeingDeleted。因此,如果有人添加了一个超时,我建议设一个相当高的值。 - huha
你可以将该方法设置为异步,并用 await Task.Delay(2000) 替换 Thread.Sleep(2000) - 这样线程就不会被阻塞了。 - UserControl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接