通过 F# 实现 Azure Blob 存储入门

Azure Blob 存储是将非结构化数据作为对象/blob 存储在云中的服务。 Blob 存储可以存储任何类型的文本或二进制数据,例如文档、媒体文件或应用程序安装程序。 Blob 存储也称为对象存储。

本文介绍如何使用 Blob 存储执行常见任务。 示例是用 F# 编写的并使用了用于 .NET 的 Azure 存储空间客户端库。 涉及的任务包括如何上传、列出、下载和删除 Blob。

有关 Blob 存储的概念性概述,请参阅 Blob 存储的 .NET 指南

先决条件

若要使用此指南,必须先创建 Azure 存储帐户。 你还需要此帐户的存储访问密钥。

创建 F# 脚本并启动 F# 交互

本文中的示例可用于 F# 应用程序或 F# 脚本。 若要创建 F# 脚本,请在 F# 开发环境中创建一个扩展名为 .fsx 的文件,例如 blobs.fsx

如何执行脚本

F# 交互窗口 dotnet fsi 可通过交互方式启动,也可以从命令行启动,以运行脚本。 命令行语法是

> dotnet fsi [options] [ script-file [arguments] ]

在脚本中添加包

接下来,使用 #rnuget:package name 安装 Azure.Storage.Blobs 包和 open 命名空间。例如

> #r "nuget: Azure.Storage.Blobs"
open Azure.Storage.Blobs
open Azure.Storage.Blobs.Models
open Azure.Storage.Blobs.Specialized

添加命名空间声明

将下列 open 语句添加到 blobs.fsx 文件顶部:

open System
open System.IO
open Azure.Storage.Blobs // Namespace for Blob storage types
open Azure.Storage.Blobs.Models
open Azure.Storage.Blobs.Specialized
open System.Text

获取连接字符串

本教程需要一个 Azure 存储连接字符串。 有关连接字符串的详细信息,请参阅配置存储连接字符串

在本教程中,你将在脚本中输入连接字符串,如下所示:

let storageConnString = "..." // fill this in from your storage account

创建一些本地虚拟数据

在开始之前,请在脚本的目录中创建一些虚拟本地数据。 稍后上传此数据。

// Create a dummy file to upload
let localFile = "./myfile.txt"
File.WriteAllText(localFile, "some data")

创建 Blob 服务客户端

通过 BlobContainerClient 类型可创建容器并检索存储在 Blob 存储中的 blob。 下面是创建容器客户端的一种方法:

let container = BlobContainerClient(storageConnString, "myContainer")

现在,已准备好编写从 Blob 存储读取数据并将数据写入 Blob 存储的代码。

创建容器

此示例演示如何创建一个容器(如果该容器不存在):

container.CreateIfNotExists()

默认情况下,新容器是专用容器,意思是必须指定存储访问密钥才能从该容器下载 blob。 如果要让容器中的文件可供所有人使用,则可以使用以下代码将容器设置为公共容器:

let permissions = PublicAccessType.Blob
container.SetAccessPolicy(permissions)

Internet 中的所有人都可以查看公共容器中的 blob,但是,仅在具有相应的帐户访问密钥或共享的访问签名时,才能修改或删除它们。

将 Blob 上传到容器中

Azure Blob 存储支持块 Blob 和页 Blob。 大多数情况下,推荐使用块 blob 类型。

若要将文件上传到块 blob,请获取容器客户端,并使用它获取块 blob 引用。 获取 blob 引用后,可以通过调用 Upload 方法,将任何数据流上载到该 blob。 此操作会覆盖 blob 的内容,如果不存在则创建一个新的块 blob。

// Retrieve reference to a blob named "myblob.txt".
let blockBlob = container.GetBlobClient("myblob.txt")

// Create or overwrite the "myblob.txt" blob with contents from the local file.
use fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read, FileShare.Read)
do blockBlob.Upload(fileStream)

列出容器中的 Blob

若要列出容器中的 Blob,首先需要获取容器引用。 然后,可以使用容器的 GetBlobs 方法来检索其中的 blob 和/或目录。 访问返回的 BlobItem 的丰富属性集和方法。

for item in container.GetBlobsByHierarchy() do
    printfn $"Blob name: {item.Blob.Name}"

例如,考虑名为 photos的容器中包含的下面一组块 Blob:

photo1.jpg
2015/architecture/description.txt
2015/architecture/photo3.jpg
2015/architecture/photo4.jpg
2016/architecture/photo5.jpg
2016/architecture/photo6.jpg
2016/architecture/description.txt
2016/photo7.jpg\

当你在容器上调用 GetBlobsByHierarchy(如上面的示例所示)时,将返回一个层次结构列表。

Directory: https://<accountname>.blob.core.windows.net/photos/2015/
Directory: https://<accountname>.blob.core.windows.net/photos/2016/
Block blob of length 505623: https://<accountname>.blob.core.windows.net/photos/photo1.jpg

下载 Blob

若要下载 blob,首先要检索 blob 引用,然后调用 DownloadTo 方法。 以下示例使用 DownloadTo 方法将 blob 内容传输到一个流对象,然后你可以将该流对象保存到本地文件。

// Retrieve reference to a blob named "myblob.txt".
let blobToDownload = container.GetBlobClient("myblob.txt")

// Save blob contents to a file.
do
    use fileStream = File.OpenWrite("path/download.txt")
    blobToDownload.DownloadTo(fileStream)

也可以使用 DownloadContent 方法以文本字符串形式下载 blob 的内容。

let text = blobToDownload.DownloadContent().Value.Content.ToString()

删除 Blob

若要删除 blob,首先要获取 blob 引用,然后对其调用 Delete 方法。

// Retrieve reference to a blob named "myblob.txt".
let blobToDelete = container.GetBlobClient("myblob.txt")

// Delete the blob.
blobToDelete.Delete()

以异步方式列出页中的 Blob

如果要列出大量 Blob,或需要控制一个列表操作中返回的结果数,则可以结果页的方式列出 Blob。 此示例演示如何以页的形式返回结果。

此示例使用 BlobClientGetBlobsByHierarchy 方法显示层次结构列表。

let ListBlobsSegmentedInHierarchicalListing(container:BlobContainerClient) =
        // List blobs to the console window, with paging.
        printfn "List blobs in pages:"

        // Call GetBlobsByHierarchy to return an async collection 
        // of blobs in this container. AsPages() method enumerate the values 
        //a Page<T> at a time. This may make multiple service requests.

        for page in container.GetBlobsByHierarchy().AsPages() do
            for blobHierarchyItem in page.Values do 
                printf $"The BlobItem is : {blobHierarchyItem.Blob.Name} "

        printfn ""

我们现在可以使用这个层次结构列表例程,如下所示。 首先,上传一些虚拟数据(使用本教程前面创建的本地文件)。

for i in 1 .. 100 do
    let blob  = container.GetBlobClient($"myblob{i}.txt")
    use fileStream = System.IO.File.OpenRead(localFile)
    blob.Upload(localFile)

现在,调用例程。

ListBlobsSegmentedInHierarchicalListing container

写入追加 Blob

追加 Blob 针对追加操作(例如日志记录)进行了优化。 类似于块 blob,追加 blob 由块组成,但是当你将新的块添加到追加 blob 时,始终追加到该 blob 的末尾。 不能更新或删除追加 Blob 中现有的块。 追加 Blob 的块 ID 不公开,因为它们是用于一个块 Blob 的。

追加 Blob 中的每个块可以有不同的大小,最大为 4 MB,并且追加 Blob 最多可包含 50000 个块。 因此,追加 Blob 的最大大小稍微大于 195 GB(4 MB X 50000 块)。

下面的示例创建一个新的追加 blob 并向其追加某些数据,模拟一个简单的日志记录操作。

// Get a reference to a container.
let appendContainer = BlobContainerClient(storageConnString, "my-append-blobs")

// Create the container if it does not already exist.
appendContainer.CreateIfNotExists() |> ignore

// Get a reference to an append blob.
let appendBlob = appendContainer.GetAppendBlobClient("append-blob.log")

// Create the append blob. Note that if the blob already exists, the 
// CreateOrReplace() method will overwrite it. You can check whether the 
// blob exists to avoid overwriting it by using CloudAppendBlob.Exists().
appendBlob.CreateIfNotExists()

let numBlocks = 10

// Generate an array of random bytes.
let rnd = Random()
let bytesArray = Array.zeroCreate<byte>(numBlocks)
rnd.NextBytes(bytesArray)

// Simulate a logging operation by writing text data and byte data to the 
// end of the append blob.
for i in 0 .. numBlocks - 1 do
    let msg = sprintf $"Timestamp: {DateTime.UtcNow} \tLog Entry: {bytesArray.[i]}\n"
    let array = Encoding.ASCII.GetBytes(msg);
    use stream = new MemoryStream(array)
    appendBlob.AppendBlock(stream)

// Read the append blob to the console window.
let downloadedText = appendBlob.DownloadContent().ToString()
printfn $"{downloadedText}"

请参阅 了解块 Blob、页 Blob 和追加 Blob ,就有关三种 Blob 之间的差异了解详细信息。

并发访问

若要允许从多个客户端或多个进程实例并发访问某个 Blob,可以使用 ETag租约

  • Etag - 用于检测 Blob 或容器是否已被其他进程修改

  • 租约 - 用于在某个时段内获取对 blob 的独占式可续订写入或删除访问

有关详细信息,请参阅在 Microsoft Azure 存储中管理并发

命名容器

Azure 存储中的每个 Blob 必须驻留在一个容器中。 该容器构成 Blob 名称的一部分。 例如,在这些示例 Blob URI 中, mydata 是容器的名称:

  • https://storagesample.blob.core.windows.net/mydata/blob1.txt
  • https://storagesample.blob.core.windows.net/mydata/photos/myphoto.jpg

容器名称必须是有效的 DNS 名称,并符合以下命名规则:

  1. 容器名称必须以字母或数字开头,并且只能包含字母、数字和短划线 (-) 字符。
  2. 每个短划线 (-) 字符的前面和后面都必须是一个字母或数字;在容器名称中不允许连续的短划线 (-)。
  3. 容器名称中的所有字母都必须为小写。
  4. 容器名称必须介于 3 到 63 个字符。

容器的名称必须始终为小写。 如果在容器名称中包括大写字母或以其他方式违反了容器命名规则,则可能会收到 400 错误(错误请求)。

管理 Blob 安全性

默认情况下,Azure 存储会限制拥有帐户访问密钥的帐户所有者的访问权限来保持数据安全。 当需要共享存储帐户中的 Blob 数据时,请注意不可危及帐户访问密钥的安全性。 此外,可以加密 Blob 数据,以确保其在网络中传输时以及在 Azure 存储中时的安全性。

控制对 Blob 数据的访问

默认情况下,存储帐户中的 Blob 数据仅供存储帐户所有者访问。 默认情况下,验证对 Blob 存储的请求需要帐户访问密钥。 不过,你可能想要让特定的 blob 数据可供其他用户使用。

加密 Blob 数据

Azure 存储支持在客户端和服务器上加密 blob 数据。

另请参阅