Azure 存储 Blob 重命名

66

是否可以使用Azure Storage API从Web角色重命名Azure存储Blob?目前我唯一的解决方法是将Blob复制到具有正确名称的新Blob中,然后删除旧Blob。


现在,可以使用带有分层命名空间的ADLS Gen 2 - https://learn.microsoft.com/en-us/azure/storage/data-lake-storage/namespace - Saher Ahwal
14个回答

50

更新:

在 @IsaacAbrahams 和 @Viggity 的评论以及答案后,我更新了代码。这个版本应该可以避免你不得不将所有内容加载到 MemoryStream 中,并在复制完成之前等待,然后再删除源 Blob。


对于那些使用 Azure 存储 API V2 的人来说,如果想要快速简洁地实现,这是一个扩展方法(包括异步版本):

public static class BlobContainerExtensions 
{
   public static void Rename(this CloudBlobContainer container, string oldName, string newName)
   {
      //Warning: this Wait() is bad practice and can cause deadlock issues when used from ASP.NET applications
      RenameAsync(container, oldName, newName).Wait();
   }

   public static async Task RenameAsync(this CloudBlobContainer container, string oldName, string newName)
   {
      var source = await container.GetBlobReferenceFromServerAsync(oldName);
      var target = container.GetBlockBlobReference(newName);

      await target.StartCopyFromBlobAsync(source.Uri);

      while (target.CopyState.Status == CopyStatus.Pending)
            await Task.Delay(100);

      if (target.CopyState.Status != CopyStatus.Success)
          throw new Exception("Rename failed: " + target.CopyState.Status);

      await source.DeleteAsync();
    }
}

Azure Storage 7.0更新

    public static async Task RenameAsync(this CloudBlobContainer container, string oldName, string newName)
    {
        CloudBlockBlob source =(CloudBlockBlob)await container.GetBlobReferenceFromServerAsync(oldName);
        CloudBlockBlob target = container.GetBlockBlobReference(newName);


        await target.StartCopyAsync(source);

        while (target.CopyState.Status == CopyStatus.Pending)
            await Task.Delay(100);

        if (target.CopyState.Status != CopyStatus.Success)
            throw new Exception("Rename failed: " + target.CopyState.Status);

        await source.DeleteAsync();            
    }

免责声明:这是一种快速而简单的重命名同步执行方法。它适合我的目的,但正如其他用户所指出的,复制可能需要很长时间(长达数天),因此最好不要像这个答案那样在1个方法中执行此操作,而是:

  • 启动复制过程
  • 轮询复制操作的状态
  • 在复制完成时删除原始 blob。

1
@BrianMacKay提到StartCopyFromBlob可能需要7天才能完成。据您所知,这是否属实? - crthompson
1
嗨@Paqogomez,也许根据某个地方的SLA,但根据我的经验,它很快(在毫秒到秒的范围内)。 - Wiebe Tijsma
1
据我所知,StartCopyFromBlob 函数会在复制操作开始时返回。但它不会在复制完成时返回!要确定复制操作何时完成,您需要轮询 Blob 的最新属性,并查看复制操作何时完成。 - Isaac Abraham
5
在高负载情况下,由于删除操作比复制操作更快,我约丢失了20%正在重命名的文件。操作不排队。解决方法在这里:https://dev59.com/FG865IYBdhLWcg3wl_w3#26549519 - viggity
2
@Vikram 如果我看得很快,你没有在异步操作上使用“await”。我认为你应该先了解C#中async/await结构的基础知识,然后再提出一个新问题(如果你想让我回答,可以在这里放链接),那样会给你一个更好的答案。 - Wiebe Tijsma
显示剩余5条评论

36

尝试使用ADLS Gen 2 API,您可以像在原子操作中重命名文件一样重命名blob。- https://azure.microsoft.com/zh-cn/services/storage/data-lake-storage/ - Saher Ahwal
@SaherAhwal 文件资源不是 Blob 资源。目前还没有 API 可以原子操作重命名 Blob。 - lerthe61
@lerthe61 是的,这就是为什么你需要使用ADLS Gen 2并启用分层命名空间。 - Saher Ahwal
现在你可以这样做,参见 https://stackoverflow.com/revisions/38973244/4 - Erik Erikson
@ErikErikson 这是在重命名一个容器,而不是一个 Blob。 - John C
此外,该评论的后续修订报告了该功能被撤销的情况。 - Erik Erikson

29

我最初使用了@Zidad的代码,在低负载情况下通常可以工作(我几乎总是重命名小文件,约10kb)。

不要StartCopyFromBlob然后Delete!!!!!!!

在高负载场景下,我丢失了约20%的正在重命名的文件(数千个文件)。正如他的答案评论中所提到的那样,StartCopyFromBlob只是开始复制。 你无法等待复制完成。

唯一能够保证复制完成的方法是下载并重新上传。这是我的更新代码:

public void Rename(string containerName, string oldFilename, string newFilename)
{
    var oldBlob = GetBlobReference(containerName, oldFilename);
    var newBlob = GetBlobReference(containerName, newFilename);

    using (var stream = new MemoryStream())
    {
        oldBlob.DownloadToStream(stream);
        stream.Seek(0, SeekOrigin.Begin);
        newBlob.UploadFromStream(stream);

        //copy metadata here if you need it too

        oldBlob.Delete();
    }
}

1
嗨Viggity,谢谢,看起来@IsaacAbraham是正确的,很抱歉。我已经更新了我的答案并加上了一个警告。 - Wiebe Tijsma
2
显然还有一个状态可以检查,这样您就可以重命名 blob 而无需像您现在做的那样将其全部下载到内存中,我会更新我的答案... - Wiebe Tijsma
@zidad,有趣的方法。在我的情况下,我不想将其设置为异步,因为这会影响其他几个事情。感谢您的更新。 - viggity
3
为什么人们要这样滥用MemoryStreams?太可怕了。你可以使用一个小的byte缓冲区,直接将oldBlob流传输到newBlob流中。这完全违背了Stream的目的,而且完全不可扩展。我曾看到这段代码被复制到生产环境中并引发了OOM问题。非常糟糕。 - makhdumi
所有我的文件都很小,这没关系。抱歉。 - viggity
@Al-Muhandis请发布另一种答案。 - davidcarr

26

你可以复制,然后再删除。


10
如果你要复制,请确保复制实际的数据和元数据,然后再删除。不要使用StartCopyFromBlob,然后再删除。我因为复制未完成就被删除了,而丢失了20%的文件。这是我在重命名Azure存储块时遇到的问题。 https://dev59.com/FG865IYBdhLWcg3wl_w3#26549519 - viggity

12

虽然这篇文章有些年头了,但也许这篇博客文章能够向其他人展示如何快速重命名已上传的 Blob。

以下是要点:

//set the azure container
string blobContainer = "myContainer";
//azure connection string
string dataCenterSettingKey = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", "xxxx",
                                            "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
//setup the container object
CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(dataCenterSettingKey);
CloudBlobClient blobClient = cloudStorageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(blobContainer);

// Set permissions on the container.
BlobContainerPermissions permissions = new BlobContainerPermissions();
permissions.PublicAccess = BlobContainerPublicAccessType.Blob;
container.SetPermissions(permissions);

//grab the blob
CloudBlob existBlob = container.GetBlobReference("myBlobName");
CloudBlob newBlob = container.GetBlobReference("myNewBlobName");
//create a new blob
newBlob.CopyFromBlob(existBlob);
//delete the old
existBlob.Delete();

1
考虑更新您的答案,附上相关要点的简要总结。这样即使链接失效,它仍将对他人有用。 - Leigh
4
据我所知,这段话已经过时,因为在 Azure 存储 API 2... 中,CopyFromBlob 已经变成了 StartCopyFromBlob。我听说这会将你的 Blob 放入一个重命名队列中,最多需要7天时间。 - Brian MacKay
那不是我的经验。虽然SLA上可能会说类似的话,但创建和复制非常快。 - crthompson
2
v2 API将CopyFromBlob转换为StartCopyFromBlob。在高负载情况下,我丢失了20%正在重命名的文件。不好。 https://dev59.com/FG865IYBdhLWcg3wl_w3#26549519 - viggity
@paqogomez - 实际上性能会因其他Azure客户而异。我已经与微软联系过,他们确认有时复制需要数天时间。 - Nitramk

7

无法重命名。以下是使用Azure SDK for .NET v12的解决方法:

BlobClient sourceBlob = container.GetBlobClient(sourceBlobName);
BlobClient destBlob = container.GetBlobClient(destBlobName);
CopyFromUriOperation ops = await destBlob.StartCopyFromUriAsync(sourceBlob.Uri);

long copiedContentLength = 0;
while (ops.HasCompleted == false)
{
    copiedContentLength = await ops.WaitForCompletionAsync();
    await Task.Delay(100);
}
await sourceBlob.DeleteAsync();

5

复制 Blob,然后删除它。

已测试 1G 大小的文件,表现良好。

有关更多信息,请参见 MSDN 上的示例

StorageCredentials cred = new StorageCredentials("[Your?storage?account?name]", "[Your?storage?account?key]");  
CloudBlobContainer container = new CloudBlobContainer(new Uri("http://[Your?storage?account?name].blob.core.windows.net/[Your container name] /"), cred);  

string fileName = "OldFileName";  
string newFileName = "NewFileName";  
await container.CreateIfNotExistsAsync();  

CloudBlockBlob blobCopy = container.GetBlockBlobReference(newFileName);  

if (!await blobCopy.ExistsAsync())  
{  
    CloudBlockBlob blob = container.GetBlockBlobReference(fileName);  

    if (await blob.ExistsAsync())  
    {  
           // copy
           await blobCopy.StartCopyAsync(blob);                               
           // then delete
           await blob.DeleteIfExistsAsync();  
    } 
} 

1

现在,您可以使用ADLS Gen 2Azure Data Lake Storage Gen 2)的公共预览版进行操作。

分层命名空间功能使您能够对目录和文件执行原子操作,包括重命名操作。

但是,请注意以下内容: “使用预览版时,如果启用了分层命名空间,则 Blob 和 Data Lake Storage Gen2 REST API 之间不存在数据或操作的互通性。此功能将在预览期间添加。”

您需要确保使用 ADLS Gen 2 创建 blob(文件)以重命名它们。否则,请等待在预览期间添加 Blob API 和 ADLS Gen 2 之间的互通性。


1
使用Monza Cloud的Azure Explorer,我可以在不到一秒钟的时间内重命名一个18千兆字节的块。微软的Azure Storage Explorer需要29秒来克隆相同的块,因此Monza没有进行复制。我知道它很快,因为在Monza重命名后立即单击Microsoft Azure Storage Explorer中的容器,就会显示具有新名称的块。

0

目前唯一的方法是将源Blob移动到新的目标位置/名称。以下是我的代码:

 public async Task<CloudBlockBlob> RenameAsync(CloudBlockBlob srcBlob, CloudBlobContainer destContainer,string name)
    {
        CloudBlockBlob destBlob;

        if (srcBlob == null && srcBlob.Exists())
        {
            throw new Exception("Source blob cannot be null and should exist.");
        }

        if (!destContainer.Exists())
        {
            throw new Exception("Destination container does not exist.");
        }

        //Copy source blob to destination container            
        destBlob = destContainer.GetBlockBlobReference(name);
        await destBlob.StartCopyAsync(srcBlob);
        //remove source blob after copy is done.
        srcBlob.Delete();
        return destBlob;
    }

如果您想将Blob查找作为方法的一部分,请参考以下代码示例:

    public CloudBlockBlob RenameBlob(string oldName, string newName, CloudBlobContainer container)
    {
        if (!container.Exists())
        {
            throw new Exception("Destination container does not exist.");
        }
        //Get blob reference
        CloudBlockBlob sourceBlob = container.GetBlockBlobReference(oldName);

        if (sourceBlob == null && sourceBlob.Exists())
        {
            throw new Exception("Source blob cannot be null and should exist.");
        }

        // Get blob reference to which the new blob must be copied
        CloudBlockBlob destBlob = container.GetBlockBlobReference(newName);
        destBlob.StartCopyAsync(sourceBlob);

        //Delete source blob
        sourceBlob.Delete();
        return destBlob;
    }

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