在Azure存储Blob中添加缓存控制和过期标头

40
我正在使用Azure存储来提供静态文件blob,但我想在提供文件/ blob时添加Cache-Control和Expires标头以减少带宽成本。
CloudXplorer和Cerebrata的Cloud Storage Studio这样的应用程序提供了在容器和blob上设置元数据属性的选项,但在尝试添加Cache-Control时会出现问题。
有人知道是否可以为文件设置这些标头吗?

我后来发现可以在每个 Blob 上设置 Cache-Control,但是我有超过 500,000 个文件/ Blob 分布在数千个容器中,我希望为其设置缓存头。有人知道一种有效的方法来为所有 Blob 设置此标头吗? - Gavin
我曾经以为在CloudBerry Explorer for Windows Azure中找到了解决方案,但尽管它看起来可以批量更新标头,但实际上并不起作用。似乎这是一个已知的错误,但自2009年3月以来仍然存在,所以我不会抱着等待修复的希望而浪费时间!http://cloudberrylab.com/forum/default.aspx?g=posts&t=3047 - Gavin
我也尝试了使用CloudBerry。我可以设置缓存控制头。但是保存后,它会丢失设置。也许这是因为它来自“用户定义”类型而不是“系统”类型? - ownking
Gavin,你是如何使用Cloudberry Explorer为单个文件设置缓存控制头的?我尝试过了,但好像没有起作用。 - TMC
9个回答

26

我需要在大约 600k 个 Blob 上运行批处理作业,发现了两个真正有用的方法:

  1. 从同一数据中心的工作角色中运行操作。只要它们在同一关联组中,Azure 服务之间的速度就非常快,而且没有数据传输费用。
  2. 并行运行操作。.NET v4 中的任务并行库(TPL)使这变得非常容易。以下是设置容器中每个 Blob 的缓存控制标头的并行代码:

  3. // get the info for every blob in the container
    var blobInfos = cloudBlobContainer.ListBlobs(
        new BlobRequestOptions() { UseFlatBlobListing = true });
    Parallel.ForEach(blobInfos, (blobInfo) =>
    {
        // get the blob properties
        CloudBlob blob = container.GetBlobReference(blobInfo.Uri.ToString());
        blob.FetchAttributes();
    
        // set cache-control header if necessary
        if (blob.Properties.CacheControl != YOUR_CACHE_CONTROL_HEADER)
        {
            blob.Properties.CacheControl = YOUR_CACHE_CONTROL_HEADER;
            blob.SetProperties();
        }
    });
    

显然,您不能再将完整的URI传递给GetBlobReference。我已经编辑了答案,并附上了我为其中一个容器编写的最新代码。 - Shiroy
如果有人编写一个这样的实用程序,那就太棒了,因为人们想要做这件事情是非常普遍的! - Shiroy
我对同一容器中的约 200,000 个 blob 运行此脚本,结果得到了 409 服务器响应。有人知道这是什么意思吗? - Guy Assaf
太棒了,Azure SDK for Java非常相似 :smile: - Ruslan López

12
这里是 Joel Fillmore 的答案的更新版本,使用 Net 5 和 Azure.Storage.Blobs 的 V12。(顺带说一句:如果可以在父容器上设置默认标题属性,那不是太好了吗?)
Azure 不需要创建网站并使用 WorkerRole,而是具有运行“WebJobs”的功能。您可以在位于与存储帐户相同数据中心的网站上按需运行任何可执行文件,以设置缓存标头或任何其他标头字段。
1. 创建一个丢弃的临时网站,与您的存储账户在同一个数据中心。不用担心亲和性组;创建一个空的 ASP.NET 网站或任何其他简单的网站。内容不重要。我需要使用至少 B1 服务计划,否则 WebJob 在 5 分钟后会中止。
2. 使用下面的代码创建控制台程序,并为发布编译它,然后将可执行文件和所有必需的 DLL 打包成 .zip 文件,或者直接从 VisualStudio 发布并跳过第 3 步。
3. 创建 WebJob 并上传步骤 #2 中的 .zip 文件。
4. 运行 WebJob。控制台中写入的所有内容都可在创建的日志文件中查看,并可从 WebJob 控制页面访问。 enter image description here 5. 删除临时网站,或将其更改为免费版(在“扩展”下)。
下面的代码为每个容器运行单独的任务,我每分钟更新了多达 100K 的标题(取决于一天中的时间?)。没有出站费用。
using Azure;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AzureHeaders
{
    class Program
    {
        private static string connectionString = "DefaultEndpointsProtocol=https;AccountName=REPLACE_WITH_YOUR_CONNECTION_STRING";
        private static string newCacheControl = "public, max-age=7776001"; // 3 months
        private static string[] containersToProcess = { "container1", "container2" };

        static async Task Main(string[] args)
        {
            BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);

            var tasks = new List<Task>();
            foreach (var container in containersToProcess)
            {
                BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(container);
                tasks.Add(Task.Run(() => UpdateHeaders(containerClient, 1000)));  // I have no idea what segmentSize should be!
            }
            Task.WaitAll(tasks.ToArray());
        }

        private static async Task UpdateHeaders(BlobContainerClient blobContainerClient, int? segmentSize)
        {
            int processed = 0;
            int failed = 0;
            try
            {
                // Call the listing operation and return pages of the specified size.
                var resultSegment = blobContainerClient.GetBlobsAsync()
                    .AsPages(default, segmentSize);

                // Enumerate the blobs returned for each page.
                await foreach (Azure.Page<BlobItem> blobPage in resultSegment)
                {
                    var tasks = new List<Task>();

                    foreach (BlobItem blobItem in blobPage.Values)
                    {
                        BlobClient blobClient = blobContainerClient.GetBlobClient(blobItem.Name);
                        tasks.Add(UpdateOneBlob(blobClient));
                        processed++;
                    }
                    Task.WaitAll(tasks.ToArray());
                    Console.WriteLine($"Container {blobContainerClient.Name} processed: {processed}");
                }
            }
            catch (RequestFailedException e)
            {
                Console.WriteLine(e.Message);
                failed++;
            }
            Console.WriteLine($"Container {blobContainerClient.Name} processed: {processed}, failed: {failed}");
        }

        private static async Task UpdateOneBlob(BlobClient blobClient) {
            Response<BlobProperties> propertiesResponse = await blobClient.GetPropertiesAsync();
            BlobHttpHeaders httpHeaders = new BlobHttpHeaders
            {
                // copy any existing headers you wish to preserve
                ContentType = propertiesResponse.Value.ContentType,
                ContentHash = propertiesResponse.Value.ContentHash,
                ContentEncoding = propertiesResponse.Value.ContentEncoding,
                ContentDisposition = propertiesResponse.Value.ContentDisposition,
                // update CacheControl
                CacheControl = newCacheControl  
            };
            await blobClient.SetHttpHeadersAsync(httpHeaders);
        }
    }
}

谢谢,这段代码节省了我一些时间。他们已经更改了WebJobs。显然你不能再指定按需运行。我只是将其创建为连续作业,然后观察日志以确保它完成并手动停止作业。 - jrichview

5
最新版本的Cerebrata Cloud Storage Studio,v2011.04.23.00,支持在个别Blob对象上设置缓存控制。右键单击Blob对象,选择“查看/编辑Blob属性”,然后为Cache-Control属性设置值(例如public,max-age=2592000)。
如果使用curl检查Blob对象的HTTP标头,则会返回所设置的缓存控制标头的值。

1
Azure Explorer也具有此功能,并且无需试用即可免费使用。 http://www.cerebrata.com/products/azure-explorer/introduction - TWilly

3
有时,最简单的答案是最好的答案。如果您只想管理少量的 blob,则可以使用 Azure Management 来更改 blob 的标题/元数据。
  1. 点击 存储,然后点击存储帐户名称。
  2. 点击 容器 选项卡,然后单击一个容器。
  3. 单击一个 blob,然后在屏幕底部单击 编辑
在编辑窗口中,您可以自定义 缓存控制内容编码内容语言 等内容。 注意: 您目前无法从 Azure Portal 编辑此数据。

1
不幸的是,他们已经从我们这里拿走了那个功能。当使用旧版Azure管理时,你现在会看到“存储的新家园。访问我们的新门户网站”。但是该功能仍未在门户网站中实现。 - Malyngo
现在,该功能已经回到了门户,并且可以使用“存储资源管理器”来完成,该工具目前处于预览状态。 - Stephen McDowell
@StephenMcDowell,我刚刚在我们的帐户中检查了一个blob,但是我没有看到任何地方可以编辑Cache Control属性或其他类似的属性。也许我正在错误的位置寻找。您在门户中是在哪里找到这个的? - John Washam
2
通过“存储资源管理器(预览版)”,您可以访问 Blob 容器。在 Blob 容器中,如果您选择一个项目并右键单击,我会看到“属性..”作为最后一个菜单项。这将打开另一个窗口,如果您滚动浏览顶部部分,我可以看到文本框,您可以在其中输入缓存控制。 - Stephen McDowell
这是有关此内容的官方文档:https://learn.microsoft.com/zh-cn/azure/cdn/cdn-manage-expiration-of-blob-content#azure-storage-explorer - equivalent8


2
这是Joel Fillmore的回答的更新版本,涉及WindowsAzure.Storage v9.3.3。请注意,ListBlobsSegmentedAsync返回一个页面大小为5,000,因此需要使用BlobContinuationToken。"Original Answer"翻译成"最初的回答"。
    public async Task BackfillCacheControlAsync()
    {
        var container = await GetCloudBlobContainerAsync();
        BlobContinuationToken continuationToken = null;

        do
        {
            var blobInfos = await container.ListBlobsSegmentedAsync(string.Empty, true, BlobListingDetails.None, null, continuationToken, null, null);
            continuationToken = blobInfos.ContinuationToken;
            foreach (var blobInfo in blobInfos.Results)
            {
                var blockBlob = (CloudBlockBlob)blobInfo;
                var blob = await container.GetBlobReferenceFromServerAsync(blockBlob.Name);
                if (blob.Properties.CacheControl != "public, max-age=31536000")
                {
                    blob.Properties.CacheControl = "public, max-age=31536000";
                    await blob.SetPropertiesAsync();
                }
            }               
        }
        while (continuationToken != null);
    }

    private async Task<CloudBlobContainer> GetCloudBlobContainerAsync()
    {
        var storageAccount = CloudStorageAccount.Parse(_appSettings.AzureStorageConnectionString);
        var blobClient = storageAccount.CreateCloudBlobClient();
        var container = blobClient.GetContainerReference("uploads");
        return container;
    }

1
以下是一份批处理/Unix脚本,适用于没有PowerShell的Windows机器。该脚本循环遍历所有Blob,并在每个Blob上单独设置Content-Cache属性(Cache-Control http header)。
不幸的是,没有好的方法可以同时设置多个Blob的属性,因此这是一个耗时的任务。每个Blob通常需要1-2秒钟。但是,正如Jay Borseth指出的那样,如果从与您的存储帐户位于同一数据中心的服务器运行它,则该过程将显着加速。
# Update Azure Blob Storage blob's cache-control headers
# /content-cache properties
# 
# Quite slow, since there is no `az storage blob update-batch`
#
# Created by Jon Tingvold, March 2021
#
#
# If you want progress, you need to install pv:
# >>> brew install pv  # Mac
# >>> sudo apt install pv  # Ubuntu
#

set -e  # exit when any command fails

AZURE_BLOB_CONNECTION_STRING='DefaultEndpointsProtocol=https;EndpointSuffix=core.windows.net;AccountName=XXXXXXXXXXXX;AccountKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX=='
CONTAINER_NAME=main

BLOB_PREFIX='admin/'
CONTENT_CACHE='max-age=3600'
NUM_RESULTS=10000000  # Defaults to 5000

BLOB_NAMES=$(az storage blob list --connection-string $AZURE_BLOB_CONNECTION_STRING --container-name $CONTAINER_NAME --query '[].name' --output tsv --num-results $NUM_RESULTS --prefix $BLOB_PREFIX)
NUMBER_OF_BLOBS=$(echo $BLOB_NAMES | wc -w)

echo "Ask Azure for files in Blob Storage ..."
echo "Set content-cache on $NUMBER_OF_BLOBS blobs ..."

for BLOB_NAME in $BLOB_NAMES
do
  az storage blob update --connection-string $AZURE_BLOB_CONNECTION_STRING --container-name $CONTAINER_NAME --name $BLOB_NAME --content-cache $CONTENT_CACHE > /dev/null;
  echo "$BLOB_NAME"

# If you don't have pv install, uncomment  everything after done
done | cat | pv -pte --line-mode --size $NUMBER_OF_BLOBS > /dev/null

1
这可能回答得太晚了,但最近我想以不同的方式做同样的事情,我有一系列图像需要使用PowerShell脚本(当然需要使用Azure存储程序集)来应用。 希望将来有人会发现这个有用。 完整的解释在使用PowerShell脚本设置Azure Blob缓存控制中给出。
Add-Type -Path "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\v2.3\ref\Microsoft.WindowsAzure.StorageClient.dll"

$accountName = "[azureaccountname]"
$accountKey = "[azureaccountkey]"
$blobContainerName = "images"

$storageCredentials = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey -ArgumentList $accountName,$accountKey
$storageAccount = New-Object Microsoft.WindowsAzure.CloudStorageAccount -ArgumentList $storageCredentials,$true
#$blobClient = $storageAccount.CreateCloudBlobClient()
$blobClient =  [Microsoft.WindowsAzure.StorageClient.CloudStorageAccountStorageClientExtensions]::CreateCloudBlobClient($storageAccount)

$cacheControlValue = "public, max-age=604800"

echo "Setting cache control: $cacheControlValue"

Get-Content "imagelist.txt" | foreach {     
    $blobName = "$blobContainerName/$_".Trim()
    echo $blobName
    $blob = $blobClient.GetBlobReference($blobName)
    $blob.Properties.CacheControl = $cacheControlValue
    $blob.SetProperties()
}

0

使用 PowerShell 脚本设置存储 Blob 缓存控制属性

https://gallery.technet.microsoft.com/How-to-set-storage-blob-4774aca5

#creat CloudBlobClient 
Add-Type -Path "C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\v2.3\ref\Microsoft.WindowsAzure.StorageClient.dll" 
$storageCredentials = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey -ArgumentList $StorageName,$StorageKey 
$blobClient =   New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($BlobUri,$storageCredentials) 
#set Properties and Metadata 
$cacheControlValue = "public, max-age=60480" 
foreach ($blob in $blobs) 
{ 
  #set Metadata 
  $blobRef = $blobClient.GetBlobReference($blob.Name) 
  $blobRef.Metadata.Add("abcd","abcd") 
  $blobRef.SetMetadata() 

  #set Properties 
  $blobRef.Properties.CacheControl = $cacheControlValue 
  $blobRef.SetProperties() 
}

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