10

我的理解是 Azure CloudAppendBlob 不会出现并发问题,因为您只能附加到此 blob 存储,并且不需要比较 E-tags。正如这篇文章所述:

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

具体来说:

此外,Append 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 作业都可以写入这个附加 blob,我认为这就是它的设计目的?

4

4 回答 4

11

经过一番搜索后,看起来这是一个实际问题。

我猜 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);
            }
        }

    }
于 2015-09-12T15:27:29.323 回答
3

CloudAppendBlob 类的附加方法,包括

AppendBlock/AppendFromByteArray/AppendFromFile/AppendFromStream/AppendText

本质上,他们都将使用相同的 rest api 端点。阅读文档: https ://docs.microsoft.com/en-us/rest/api/storageservices/append-block

但是只有AppendBlock应该在 multi-writer 场景中使用,所有其他应该在 single-writer 场景中使用。原因是: AppendBlock不会将标头x-ms-blob-append-offset与 PUT HTTP 请求一起发送。

标头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

对于所有其他附加方法,它将发送标头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 The request body is too large and超出最大允许限制
  • 如果使用AppendBlock以外的其他方式上传大数据,它会发送一个 HEAD http 请求获取 blob 长度,然后自动将数据拆分为多个 PUT http 请求。块大小可以由 CloudAppendBlob.StreamWriteSizeInBytes 控制。如果不设置,则默认为4M。
  • 所以正如名称AppendBlock 所暗示的,它只能附加一个块,不能超过一个块。所以如果你想上传一个大的 blob,你已经自己拆分了数据。但是如果你有一个多写入器的场景,你不能保证分割的块会在 blob 中一起出现。
于 2018-03-23T21:29:54.507 回答
0

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

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

此解决方案更符合您Append*CloudAppendBlob.

于 2017-01-19T19:21:54.667 回答
-1

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

public void Log(string message)
{
    lock (this.appendBlob)
    {
        appendBlob.AppendText(string.Format("[{0:s}] {1}{2}", DateTime.Now, message, Environment.NewLine));
    }
}
于 2017-02-22T22:27:53.597 回答