Azure Blob对象存储实践 | 青训营

325 阅读4分钟

对象存储实战课程的要求是实现一个对象存储客户端,发现手头上只有Azure的服务可以用,所以就选择用Azure Blob存储服务来完成本次作业。

Blob存储

Azure Blob与课上提到的亚马逊S3、阿里云OSS、火山TOS类似,只不过把Bucket转换为了Container,而存储的对象改称为Blob,存储结构如下所示。

image.png

依据官方文档,Azure支持三种类型的Blob:

  • 块 Blob 存储文本和二进制数据。 块 Blob 由可以分别管理的数据块构成。 块 blob 最多可存储约 190.7 TiB。
  • 与块 Blob 一样,追加 Blob 也由块构成,但针对追加操作进行了优化。 追加 Blob 非常适用于诸如记录来自虚拟机的数据之类的场景。
  • 页 Blob 用于存储最大 8 TiB 的随机访问文件。 页 blob 存储虚拟硬盘 (VHD) 文件并作为 Azure 虚拟机的磁盘。

Blob使用

Blob的申请过程就不在这里展开了,具体可以看Blob快速入门,下面采用Go SDK对Blob进行操作。官方示例参考

安装包

要使用Blob,需要安装azblob包以及Azure身份验证工具azidentity包。

go get github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
go get github.com/Azure/azure-sdk-for-go/sdk/azidentity

Azure身份验证

首先需要在Azure门户中为个人账户分配权限,在存储账户的访问控制选项卡中,为当前用户分配存储Blob数据参与者的角色。

接着需要进行身份验证,Azure的身份验证方法有很多,这里采用比较方便的Azure CLI进行验证登录,其他方法可以查看Azure for Go 身份验证

安装好Azure CLI后,在Power Shell中输入az login命令。

image.png

接着在Go中使用DefaultAzureCredential来验证权限,并创建一个Blob客户端

// TODO: replace <storage-account-name> with your actual storage account name
url := "https://<storage-account-name>.blob.core.windows.net/"
ctx := context.Background()

credential, _ := azidentity.NewDefaultAzureCredential(nil)
handleError(err)

client, err := azblob.NewClient(url, credential, nil)
handleError(err)

创建容器

containerName := "test-container"
client.CreateContainer(ctx, containerName, nil)

image.png

上传Blob至容器

SDK提供了三种上传方式字节数组、文件、输入流,对应着三个方法。

func (c *Client) UploadBuffer(ctx context.Context, containerName string, blobName string, buffer []byte, o *UploadBufferOptions) (UploadBufferResponse, error)
func (c *Client) UploadFile(ctx context.Context, containerName string, blobName string, file *os.File, o *UploadFileOptions) (UploadFileResponse, error)
func (c *Client) UploadStream(ctx context.Context, containerName string, blobName string, body io.Reader, o *UploadStreamOptions) (UploadStreamResponse, error)

这里选择上传一个本地文件到容器中。

file, _ := os.Open("test.txt")
client.UploadFile(ctx, containerName, "test-blob.txt", file, &azblob.UploadFileOptions{})

运行结果

image.png

image.png

分块上传

块 Blob 由块组成,每个块可以是不同的大小,最大为 100MB,块 Blob 最多可以包含 50,000 块。因此,块 Blob 的最大大小约为 4.75 TB (100MB X 50,000 块)。在上传文件到Azure Blob存储时,支持两种方式,整体上传分块上传

要想使用分块上传,需要使用azblob中的blockblob包。大致思路是,使用想要使用的blob-name创建blockblob客户端,维护一个以Base64编码的块ID队列,将准备上传的文件分块读取(例如每次只读10MB)并分配一个Base64编码的块ID,接着使用StageBlock上传,每一块上传结束后将块ID加入队列,等待全部块上传结束后,使用CommitBlockList提交所有队列中的块。

// NOTE: The blockID must be <= 64 bytes and ALL blockIDs for the block must be the same length  
blockIDBinaryToBase64 := func(blockID []byte) string { return base64.StdEncoding.EncodeToString(blockID) }  
blockIDBase64ToBinary := func(blockID string) []byte { _binary, _ := base64.StdEncoding.DecodeString(blockID); return _binary }  
  
// These helper functions convert an int block ID to a base-64 string and vice versa  
blockIDIntToBase64 := func(blockID int) string {  
    binaryBlockID := &[4]byte{} // All block IDs are 4 bytes long  
    binary.LittleEndian.PutUint32(binaryBlockID[:], uint32(blockID))  
    return blockIDBinaryToBase64(binaryBlockID[:])  
}  
blockIDBase64ToInt := func(blockID string) int {  
    blockIDBase64ToBinary(blockID)  
    return int(binary.LittleEndian.Uint32(blockIDBase64ToBinary(blockID)))  
}  
bbClient, err := blockblob.NewClient(blobURL, cred, nil)  
if err != nil {  
    return err  
}  
base64BlockIDs := make([]string, 0, 1024)  
blockID := 0  
// Block Size is 10MB
block := make([]byte, 1024*1024*10)  
for n, _ := file.Read(block); n > 0; n, _ = file.Read(block) {  
    fmt.Printf("Put %v bytes\n", n)  
    base64BlockIDs = append(base64BlockIDs, blockIDIntToBase64(blockID))  
    _, err = bbClient.StageBlock(ctx, base64BlockIDs[blockID], streaming.NopCloser(bytes.NewReader(block[:n])), nil)  
    if err != nil {  
        return err  
    }  
    blockID++  
}  
_, err = bbClient.CommitBlockList(ctx, base64BlockIDs, nil)  
if err != nil {  
    return err  
}  
// For the blob, show each block (ID and size) that is a committed part of it.  
getBlock, err := bbClient.GetBlockList(ctx, blockblob.BlockListTypeAll, nil)  
for _, block := range getBlock.BlockList.CommittedBlocks {  
    fmt.Printf("Block ID=%d, Size=%d\n", blockIDBase64ToInt(*block.Name), block.Size)  
}  
return err

下载Blob至本地

下载和上传一样提供了三种方式,这里同样选择用文件的形式下载。

file, _ := os.OpenFile("test-local.txt", os.O_WRONLY|os.O_CREATE, 0755)  
client.DownloadFile(ctx, containerName, "test-blob.txt", file, &azblob.DownloadFileOptions{})

运行结果

image.png

删除Blob

client.DeleteBlob(ctx, containerName, "test-blob.txt", nil)

列出容器内所有Blob

pager := client.NewListBlobsFlatPager(containerName, &azblob.ListBlobsFlatOptions{
    Include: azblob.ListBlobsInclude{Snapshots: true, Versions: true},
})
for pager.More() {
    resp, err := pager.NextPage(ctx)
    if err != nil {
            return err
    }

    for _, blob := range resp.Segment.BlobItems {
            fmt.Println(*blob.Name)
    }
}

判断Blob是否存在

如果只想判断Blob是否存在,显然不需要获取整个Blob的数据内容,只需要采取HEAD的方式获取Blob的属性即可,为此需要使用azblob中的blob包,其专门针对指定容器中的blob进行操作。想要使用blob包同样需要创建对应的客户端,其url格式为https://<storage-account-name>.blob.core.windows.net/<container-name>/<blob-name>

blobClient, _ := blob.NewClient(blobURL, cred, nil)  
_, err = blobClient.GetProperties(ctx, &blob.GetPropertiesOptions{})
if err != nil {
// 不存在
}
// 存在

完整代码

完整代码