Azure CloudAppendBlob并发访问错误

本文关键字:错误 访问 并发 CloudAppendBlob Azure | 更新日期: 2023-09-27 18:12:18

我的理解是,Azure CloudAppendBlob是安全的,从并发问题,因为你只能追加到这个blob存储,它不需要比较电子标签。正如这篇文章所述:

http://blogs.msdn.com/b/windowsazurestorage/archive/2015/04/13/introducing-azure-storage-append-blob.aspx

专:

此外,Append Blob支持多个客户端写入同一个Blob而不需要任何同步(不像块Blob和页Blob)

但是下面的单元测试会引发:

412指定的追加位置条件不满足。

堆栈跟踪
Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Flush()
Microsoft.WindowsAzure.Storage.Blob.BlobWriteStream.Commit()
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.UploadFromStreamHelper
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendFromStream
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendFromByteArray
Microsoft.WindowsAzure.Storage.Blob.CloudAppendBlob.AppendText

这是单元测试。也许服务将处理来自不同上下文的请求,但不是像这样并行处理?

    [TestMethod]
    public void test_append_text_concurrency()
    {
        AppendBlobStorage abs = new AppendBlobStorage(new    TestConnectConfig(), "testappendblob");
        string filename = "test-concurrent-blob";
        abs.Delete(filename);                       
        Parallel.Invoke(
            () => { abs.AppendText(filename, "message1'r'n"); },
            () => { abs.AppendText(filename, "message2'r'n"); }
        );
        string text = abs.ReadText(filename);
        Assert.IsTrue(text.Contains("message1"));
        Assert.IsTrue(text.Contains("message2"));
    }
AppendBlobStorage 中的

方法

    public void AppendText(string filename, string text)
    {
        CloudAppendBlob cab = m_BlobStorage.BlobContainer.GetAppendBlobReference(filename);
        // Create if it doesn't exist
        if (!cab.Exists())
        {
            try
            {
                cab.CreateOrReplace(AccessCondition.GenerateIfNotExistsCondition(), null, null);
            }
            catch { }
        }
        // Append the text
        cab.AppendText(text);      
    }

也许我错过了什么。我之所以这样做是因为我有多个web作业都可以写入这个append blob我想这就是它的设计目的?

Azure CloudAppendBlob并发访问错误

再搜索一下,看起来这是一个实际问题。

我猜AppendBlobStorage是相当新的。(目前还有其他问题与AppendBlobStorage。看到

http://blogs.msdn.com/b/windowsazurestorage/archive/2015/09/02/issue-in-azure-storage-client-library-5-0-0-and-5-0-1-preview-in-appendblob-functionality.aspx)

无论如何,我通过使用AppendBlock变体而不是AppendText修复了这个问题,如这里所建议的:

https://azurekan.wordpress.com/2015/09/08/issues-with-adding-text-to-azure-storage-append-blob/

通过上面定义的单元测试的appendtext方法的更改

    public void AppendText(string filename, string text)
    {
        if (string.IsNullOrWhiteSpace(filename))
            throw new ArgumentException("filename cannot be null or empty");
        if (!string.IsNullOrEmpty(text))
        {
            CloudAppendBlob cab = m_BlobStorage.BlobContainer.GetAppendBlobReference(filename);
            // Create if it doesn't exist
            if (!cab.Exists())
            {
                try
                {
                    cab.CreateOrReplace(AccessCondition.GenerateIfNotExistsCondition(), null, null);
                }
                catch (StorageException) { }
            }
            // use append block as append text seems to have an error at the moment.
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))
            {
                cab.AppendBlock(ms);
            }
        }
    }

类CloudAppendBlob的追加方法,包括

AppendBlock/AppendFromByteArray AppendFromFile AppendFromStream/追加文本

本质上它们都将使用相同的rest API端点。阅读文件:https://learn.microsoft.com/en-us/rest/api/storageservices/append-block

但是只有AppendBlock应该在多写入场景中使用,其他的应该在单写入场景中使用。原因是:AppendBlock不会在PUT HTTP请求中发送报头x-ms-blob- append_offset

头文件x-ms-blob- append_offset 基本上表示必须在blob的这个偏移位置追加这个块数据。

所以对于AppendBlock, http请求看起来像这样:

PUT https://test.blob.core.windows.net/test/20180323.log?comp=appendblock HTTP/1.1 User-Agent: Azure-Storage/9.1.0 (.NET CLR 4.0.30319.42000; Win32NT 6.2.9200.0) x-ms-version: 2017-07-29 x-ms-client-request-id: bb7f5a93-191d-40f9-8b92-4ec0476be920 x-ms-date: Fri, 23 Mar 2018 20:21:29 GMT Authorization: SharedKey XXXXX Host: test.blob.core.windows.net Content-Length: 99

对于所有其他的append方法,它将发送头文件x-ms-blob-append-offset。这个头的值应该是在追加之前blob的当前长度。那么库是如何知道这个值的呢?它会发送一个HEAD http请求来获取信息

HEAD http://test.blob.core.windows.net/test/20180323.log HTTP/1.1 User-Agent: Azure-Storage/9.1.0 (.NET CLR 4.0.30319.42000; Win32NT 6.2.9200.0) x-ms-version: 2017-07-29 x-ms-client-request-id: 1cdb3731-9d72-41ab-afee-d4f462e9b0c2 x-ms-date: Fri, 23 Mar 2018 20:29:19 GMT Authorization: SharedKey XXXX Host: test.blob.core.windows.net

响应头Content-Length的值将是以下PUT http请求中头x-ms-blob-append-offset的值:

PUT http://test.blob.core.windows.net/test/20180323.log?comp=appendblock HTTP/1.1 User-Agent: Azure-Storage/9.1.0 (.NET CLR 4.0.30319.42000; Win32NT 6.2.9200.0) x-ms-version: 2017-07-29 x-ms-blob-condition-appendpos: 1287 x-ms-client-request-id: 1cdb3731-9d72-41ab-afee-d4f462e9b0c2 x-ms-date: Fri, 23 Mar 2018 20:29:20 GMT Authorization: SharedKey XXXXX Host: test.blob.core.windows.net Content-Length: 99

所以原来的问题,当两个并行任务同时调用AppendText时,最有可能的是,这两个任务将发送HEAD http请求以获得blob的当前长度,这将是相同的。然后,首先发送PUT http请求的任务将成功,但稍后发送PUT http请求的任务将失败,因为blob的长度已经更改,并且该偏移量已经被第一个PUT http请求占用。

所以如果你有一个多写入器的场景,AppendBlock是现在工作的方法。但是你必须知道

  • 将无法控制块在blob
  • 中的位置。
  • blob块有一个大小限制(我认为是4M)
  • 如果您使用AppendBlock上传超过4M的数据,请求将失败,并返回响应:HTTP/1.1 413请求正文太大,超过最大允许限制
  • 如果您使用除AppendBlock以外的其他方法上传大数据,它将发送一个HEAD http请求以获取blob长度,然后自动将数据拆分为多个PUT http请求。块大小可以通过CloudAppendBlob.StreamWriteSizeInBytes控制。如果不设置,则默认为4M。
  • 因此,正如名称AppendBlock所提示的那样,它只能追加一个块,不能超过一个块。所以如果你想上传一个大的blob,你必须自己拆分数据。但是,如果您有一个多写入器场景,您不能保证拆分的块将在blob中一起。

对于需要更通用的解决方案的人,我创建了一个扩展方法:

public static async Task AppendTextConcurrentAsync(this CloudAppendBlob appendBlob, string content)
{
    using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
    {
        await appendBlob.AppendBlockAsync(stream);
    }
}

此解决方案与您如何在CloudAppendBlob上使用其他Append*方法更一致。

你可以试试AppendTextAsync。在类似的情况下,这似乎对我很有效。使用lock关键字也可以。

public void Log(string message)
{
    lock (this.appendBlob)
    {
        appendBlob.AppendText(string.Format("[{0:s}] {1}{2}", DateTime.Now, message, Environment.NewLine));
    }
}