将大型 Page Blob 的页范围进行分段

Windows Azure 存储支持一种 Blob 类型,即 Page Blob。Page Blob 通过仅将已写入但未清除的页存入物理存储, 来有效存储稀疏数据。每页大小为 512 字节。Get Page Ranges REST 服务调用将返回包含有效数据的所有连续页范围的列表。在 Windows Azure 存储客户端库中,GetPageRanges 方法提供此功能。

如果服务处理请求的时间过长,Get Page Ranges 在某些情况下可能会失败。与所有 Blob REST API 类似,Get Page Ranges 使用超时参数,此参数用于指定允许请求的时间,包括通过网络进行读/写。但是,仅给服务器一段固定时间来处理请求和开始发送响应。如果此服务器超时已过,则即使还未到 API 超时参数指定的时间,请求也会失败。

在一个高度分散但具有大量写入的 Page Blob 中,填充 Get Page Ranges 返回的列表可能要比服务器超时的时间更长,所以请求将失败。因此,如果应用程序使用的模式中含有大量 Page Blob写入,并且您想要调用 GetPageRanges,建议应用程序应一次检索一部分页范围。

例如,假设 500 GB 的 Page Blob 通过 500,000 次写入来填充整个 Blob。默认情况下,存储客户端指定 Get Page Ranges 操作的超时时间为 90 秒。如果 Get Page Ranges 未在服务器超时间隔内完成,调用将失败。这一问题可以通过分组提取范围(比如 每组50 GB)的方式来解决。这会将工作拆分成 10 个请求。其中每个请求都在各自的服务器超时间隔内单独完成,确保能够成功检索全部范围。

为了确保所有请求都可以在服务器超时间隔内完成,请分段提取范围,每段 150 MB。这样,即使对于最散分布的 Page Blob 来说也很安全。如果 Page Blob 分布较集中,可以使用较大分段。

客户端库扩展

下面我们提供了一个简单的扩展方法,使存储客户端可以通过提供 rangeSize 参数将请求拆分成给定大小的范围来解决此问题。生成的 IEnumerable 对象在需要时才循环访问所有页范围,从而根据需要进行服务调用。

由于将请求拆分成了多个范围,任何超出 rangeSize 边界的页范围都会在结果中拆分成多个页范围。因此对于大小为 10 GB 的分段范围,以下 40 GB 的范围

[0 – 42949672959]

将拆分成四个 10 GB 的范围:

[0 – 10737418239]

[10737418240 – 21474836479]

[21474836480 – 32212254719]

[32212254720 – 42949672959].

若分段范围大小为 20 GB,则上述范围将仅拆分成两个范围。

请注意,通过将 BlobRequestOptions 对象指定为参数,可以使用自定义超时,但下面的方法不使用任何重试策略。对每个服务调用单独应用指定的超时。如果由于某种原因导致服务调用失败,GetPageRanges 将抛出异常。

namespace Microsoft.WindowsAzure.StorageClient
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using Microsoft.WindowsAzure.StorageClient.Protocol;

    /// <summary>
/// Class containing an extension method for the <see cref="CloudPageBlob"/> class.
    /// </summary>
    public static class CloudPageBlobExtensions
    {
        /// <summary>
/// Enumerates the page ranges of a page blob, sending one service call as needed for each
        /// <paramref name="rangeSize"/> bytes.
        /// </summary>
/// <param name="pageBlob">The page blob to read.</param>
/// <param name="rangeSize">The range, in bytes, that each service call will cover. This must be a multiple of
        ///     512 bytes.</param>
/// <param name="options">The request options, optionally specifying a timeout for the requests.</param>
/// <returns>An <see cref="IEnumerable"/> object that enumerates the page ranges.</returns>
        public static IEnumerable<PageRange> GetPageRanges(
            this CloudPageBlob pageBlob,
            long rangeSize,
            BlobRequestOptions options)
        {
            int timeout;

            if (options == null || !options.Timeout.HasValue)
            {
                timeout = (int)pageBlob.ServiceClient.Timeout.TotalSeconds;
            }
            else
            {
                timeout = (int)options.Timeout.Value.TotalSeconds;
            }

            if ((rangeSize % 512) != 0)
            {
                throw new ArgumentOutOfRangeException("rangeSize", "The range size must be a multiple of 512 bytes.");
            }

            long startOffset = 0;
            long blobSize;

            do
            {
                // Generate a web request for getting page ranges
                HttpWebRequest webRequest = BlobRequest.GetPageRanges(
                    pageBlob.Uri,
                    timeout,
                    pageBlob.SnapshotTime,
                    null /* lease ID */);

                // Specify a range of bytes to search
                webRequest.Headers["x-ms-range"] = string.Format(
                    "bytes={0}-{1}",
                    startOffset,
                    startOffset + rangeSize - 1);

                // Sign the request
                pageBlob.ServiceClient.Credentials.SignRequest(webRequest);

                List<PageRange> pageRanges;

                using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
                {
                    // Refresh the size of the blob
                    blobSize = long.Parse(webResponse.Headers["x-ms-blob-content-length"]);

                    GetPageRangesResponse getPageRangesResponse = BlobResponse.GetPageRanges(webResponse);

                    // Materialize response so we can close the webResponse
                    pageRanges = getPageRangesResponse.PageRanges.ToList();
                }

                // Lazily return each page range in this result segment.
                foreach (PageRange range in pageRanges)
                {
                    yield return range;
                }

                startOffset += rangeSize;
            }
            while (startOffset < blobSize);
        }
    }
}

用法示例:

pageBlob.GetPageRanges(10 * 1024 * 1024 * 1024 /* 10 GB */, null);

pageBlob.GetPageRanges(150 * 1024 * 1024 /* 150 MB */, options /* custom timeout in options */);

总结

对于某些分段的 Page Blob,GetPageRanges API 调用可能不会在最大服务器超时间隔内完成。为解决这一问题,一次可以提取一部分页范围, 完成后再提取下一部分,这样就减少了单个服务调用花费的时间。我们提出一种扩展方法,能够在 Windows Azure 存储客户端库中实施此技术。

Michael Roberson

本文翻译自:

https://blogs.msdn.com/b/windowsazurestorage/archive/2012/03/26/getting-the-page-ranges-of-a-large-page-blob-in-segments.aspx