使用 Azure) 生成Real-World云应用 (非结构化 Blob 存储

作者 :Rick AndersonTom Dykstra

下载修复项目下载电子书

使用 Azure 构建真实世界云应用 电子书基于 Scott Guthrie 开发的演示文稿。 其中介绍了 13 种模式和做法,可帮助你成功开发适用于云的 Web 应用。 有关电子书的信息,请参阅 第一章

在上一章中,我们了解了分区方案,并解释了修复它应用如何将映像存储在 Azure 存储 Blob 服务中,并将其他任务数据存储在 Azure SQL 数据库中。 在本章中,我们将更深入地介绍 Blob 服务,并在修复它项目代码中说明它是如何实现的。

什么是 Blob 存储?

Azure 存储 Blob 服务提供了一种在云中存储文件的方法。 Blob 服务比在本地网络文件系统中存储文件具有许多优势:

  • 它高度可缩放。 单个存储帐户可以存储 数百 TB 的容量,并且可以有多个存储帐户。 一些最大的 Azure 客户存储了数百 PB。 Microsoft SkyDrive 使用 Blob 存储。
  • 它很耐用。 存储在 Blob 服务中的每个文件都会自动备份。
  • 它提供高可用性。 存储 SLA 承诺 99.9% 或 99.99% 的运行时间,具体取决于所选的异地冗余选项。
  • 它是 Azure (PaaS) 功能的平台即服务,这意味着只需存储和检索文件,只需为实际使用的存储量付费,Azure 会自动设置和管理服务所需的所有 VM 和磁盘驱动器。
  • 可以使用 REST API 或编程语言 API 访问 Blob 服务。 SDK 适用于 .NET、Java、Ruby 等。
  • 将文件存储在 Blob 服务中时,可以轻松地通过 Internet 公开提供该文件。
  • 可以保护 Blob 服务中的文件,使其只能由授权用户访问,也可以提供临时访问令牌,使其仅在有限的时间内可供某人使用。

每当要生成适用于 Azure 的应用,并且想要存储本地环境中会进入文件(例如图像、视频、PDF、电子表格等)的大量数据。-- 考虑 Blob 服务。

创建存储帐户

若要开始使用 Blob 服务,请在 Azure 中创建存储帐户。 在门户中,单击“ 新建 -- Data Services -- 存储 -- 快速创建”,然后输入 URL 和数据中心位置。 数据中心位置应与 Web 应用相同。

创建存储帐户

选择要在其中存储内容的主要区域,如果选择 异地复制 选项,Azure 将在国家/地区另一部分的不同数据中心创建所有数据的副本。 例如,如果选择“美国西部”数据中心,则存储文件时,该文件会转到“美国西部”数据中心,但在后台,Azure 也会将其复制到其他美国数据中心之一。 如果国家/地区的某个地区发生灾难,则数据仍然安全。

Azure 不会跨地缘政治边界复制数据:如果主要位置位于美国,则文件仅复制到美国境内的另一个区域;如果主要位置是澳大利亚,则文件仅复制到澳大利亚的另一个数据中心。

当然,还可以通过执行脚本中的命令来创建存储帐户,如前所述。 下面是用于创建存储帐户的Windows PowerShell命令:

# Create a new storage account
New-AzureStorageAccount -StorageAccountName $Name -Location $Location -Verbose

拥有存储帐户后,可以立即开始在 Blob 服务中存储文件。

在 Fix It 应用中使用 Blob 存储

“修复它”应用使你能够上传照片。

创建“修复”任务

单击“ 创建 FixIt”时,应用程序将上传指定的图像文件并将其存储在 Blob 服务中。

设置 Blob 容器

若要将文件存储在 Blob 服务中,需要一个 容器 来存储该文件。 Blob 服务容器对应于文件系统文件夹。 我们在 “自动化一切”一章 中查看的环境创建脚本会创建存储帐户,但它们不会创建容器。 因此, 类的 CreateAndConfigure 方法 PhotoService 的目的是创建容器(如果尚不存在)。 此方法是从 Application_StartGlobal.asax 中的 方法调用的。

public async void CreateAndConfigureAsync()
{
    try
    {
        CloudStorageAccount storageAccount = StorageUtils.StorageAccount;

        // Create a blob client and retrieve reference to images container
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference("images");

        // Create the "images" container if it doesn't already exist.
        if (await container.CreateIfNotExistsAsync())
        {
            // Enable public access on the newly created "images" container
            await container.SetPermissionsAsync(
                new BlobContainerPermissions
                {
                    PublicAccess =
                        BlobContainerPublicAccessType.Blob
                });

            log.Information("Successfully created Blob Storage Images Container and made it public");
        }
    }
    catch (Exception ex)
    {
        log.Error(ex, "Failure to Create or Configure images container in Blob Storage Service");
    }
}

存储帐户名称和访问密钥存储在 appSettingsWeb.config 文件的集合中,方法中的 StorageUtils.StorageAccount 代码使用这些值来生成连接字符串并建立连接:

string account = CloudConfigurationManager.GetSetting("StorageAccountName");
string key = CloudConfigurationManager.GetSetting("StorageAccountAccessKey");
string connectionString = String.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", account, key);
return CloudStorageAccount.Parse(connectionString);

然后, CreateAndConfigureAsync 方法创建一个表示 Blob 服务的对象,以及一个对象,该对象表示 Blob 服务中名为“images”的容器 (文件夹) :

CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference("images");

如果名为“images”的容器尚不存在(首次针对新存储帐户运行应用时为 true),则代码将创建容器并设置将其公开的权限。 (默认情况下,新的 Blob 容器是专用的,只有有权访问存储帐户的用户才能访问。)

if (await container.CreateIfNotExistsAsync())
{
    // Enable public access on the newly created "images" container
    await container.SetPermissionsAsync(
        new BlobContainerPermissions
        {
            PublicAccess =
                BlobContainerPublicAccessType.Blob
        });

    log.Information("Successfully created Blob Storage Images Container and made it public");
}

将上传的照片存储在 Blob 存储中

为了上传和保存图像文件,应用在 类中使用 IPhotoServicePhotoService 接口和 接口的实现。 PhotoService.cs 文件包含 Fix It 应用中与 Blob 服务通信的所有代码。

当用户单击“ 创建 FixIt”时,将调用以下 MVC 控制器方法。 在此代码中, photoService 引用 类的 PhotoService 实例,并 fixittask 引用 FixItTask 存储新任务数据的实体类的实例。

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "FixItTaskId,CreatedBy,Owner,Title,Notes,PhotoUrl,IsDone")]FixItTask fixittask, HttpPostedFileBase photo)
{
    if (ModelState.IsValid)
    {
        fixittask.CreatedBy = User.Identity.Name;
        fixittask.PhotoUrl = await photoService.UploadPhotoAsync(photo);
        await fixItRepository.CreateAsync(fixittask);
        return RedirectToAction("Success");
    }

    return View(fixittask);
}

UploadPhotoAsync类中的 PhotoService 方法将上传的文件存储在 Blob 服务中,并返回指向新 Blob 的 URL。

public async Task<string> UploadPhotoAsync(HttpPostedFileBase photoToUpload)
{            
    if (photoToUpload == null || photoToUpload.ContentLength == 0)
    {
        return null;
    }

    string fullPath = null;
    Stopwatch timespan = Stopwatch.StartNew();

    try
    {
        CloudStorageAccount storageAccount = StorageUtils.StorageAccount;

        // Create the blob client and reference the container
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference("images");

        // Create a unique name for the images we are about to upload
        string imageName = String.Format("task-photo-{0}{1}",
            Guid.NewGuid().ToString(),
            Path.GetExtension(photoToUpload.FileName));

        // Upload image to Blob Storage
        CloudBlockBlob blockBlob = container.GetBlockBlobReference(imageName);
        blockBlob.Properties.ContentType = photoToUpload.ContentType;
        await blockBlob.UploadFromStreamAsync(photoToUpload.InputStream);

        // Convert to be HTTP based URI (default storage path is HTTPS)
        var uriBuilder = new UriBuilder(blockBlob.Uri);
        uriBuilder.Scheme = "http";
        fullPath = uriBuilder.ToString();

        timespan.Stop();
        log.TraceApi("Blob Service", "PhotoService.UploadPhoto", timespan.Elapsed, "imagepath={0}", fullPath);
    }
    catch (Exception ex)
    {
        log.Error(ex, "Error upload photo blob to storage");
    }

    return fullPath;
}

与 在 方法中 CreateAndConfigure 一样,代码连接到存储帐户并创建一个表示“images”blob 容器的对象,但此处假定容器已存在。

然后,它通过将新的 GUID 值与文件扩展名连接起来,为即将上传的图像创建唯一标识符:

string imageName = String.Format("task-photo-{0}{1}",
    Guid.NewGuid().ToString(),
    Path.GetExtension(photoToUpload.FileName));

然后,代码使用 Blob 容器对象和新的唯一标识符创建 blob 对象,在该对象上设置一个属性来指示它是哪种类型的文件,然后使用 blob 对象将文件存储在 Blob 存储中。

CloudBlockBlob blockBlob = container.GetBlockBlobReference(imageName);
blockBlob.Properties.ContentType = photoToUpload.ContentType;
blockBlob.UploadFromStream(photoToUpload.InputStream);

最后,它获取引用 Blob 的 URL。 此 URL 将存储在数据库中,并可用于修复它网页以显示上传的图像。

fullPath = String.Format("http://{0}{1}", blockBlob.Uri.DnsSafeHost, blockBlob.Uri.AbsolutePath);

此 URL 作为 FixItTask 表的列之一存储在数据库中。

public class FixItTask
{
    public int FixItTaskId  { get; set; }
    public string CreatedBy { get; set; }
    [Required]
    public string Owner     { get; set; }
    [Required]
    public string Title     { get; set; }
    public string Notes     { get; set; }
    public string PhotoUrl  { get; set; }
    public bool IsDone      { get; set; } 
}

由于只有数据库中的 URL 和 Blob 存储中的图像,Fix It 应用使数据库保持小、可缩放且成本低廉,而图像则存储在存储成本低廉且能够处理 TB 或 PB 量级的位置。 一个存储帐户可以存储数百 TB 的“修复它”照片,只需为使用的内容付费。 因此,你可以从少量支付 9 美分的第一千兆字节开始,并添加更多图像,每额外的千兆字节。

显示上传的文件

Fix It 应用程序在显示任务的详细信息时显示上传的图像文件。

使用照片修复任务详细信息

若要显示图像,MVC 视图只需 PhotoUrl 在发送到浏览器的 HTML 中包含 值。 Web 服务器和数据库不使用周期来显示图像,它们只向图像 URL 提供几个字节。 在以下 Razor 代码中, Model 引用实体类的 FixItTask 实例。

<fieldset>
<legend>@Html.DisplayFor(model => model.Title)</legend>
<dl>
    <dt>Opened By</dt>
    <dd>@Html.DisplayFor(model => model.CreatedBy)</dd>
                <br />
    <dt>@Html.DisplayNameFor(model => model.Notes)</dt>
    <dd>@Html.DisplayFor(model => model.Notes)</dd>
                <br />
                @if(Model.PhotoUrl != null) {
        <dd><img src="@Model.PhotoUrl" title="@Model.Title" /></dd>
                }
</dl>
</fieldset>

如果查看所显示页面的 HTML,会看到 URL 直接指向 Blob 存储中的图像,如下所示:

<fieldset>
<legend>Brush the dog again</legend>
<dl>
    <dt>Opened By</dt>
    <dd>Tom</dd>
                <br />
    <dt>Notes</dt>
    <dd>Another dog brushing task</dd>
                <br />
    <dd>
<img src="http://storageaccountname.blob.core.windows.net/images/task-photo-312dd635-ba87-4542-8b15-767032c55f4e.jpg" 
           title="Brush the dog again" />
    </dd>
</dl>
</fieldset>

摘要

你已了解 Fix It 应用如何将图像存储在 Blob 服务中,并且仅在 SQL 数据库中存储图像 URL。 使用 Blob 服务使 SQL 数据库比本来要小得多,因此可以纵向扩展到几乎无限数量的任务,并且无需编写大量代码即可完成。

一个存储帐户中可以有数百 TB 的容量,并且存储成本比SQL 数据库存储便宜得多,每月大约每千兆字节 3 美分,外加少量事务费用。 请记住,你不会为最大容量付费,而只需为实际存储的容量付费,因此你的应用已准备好进行缩放,但你无需为所有这些额外容量付费。

在下一章中,我们将讨论使云应用能够正常处理故障的重要性。

资源

有关详细信息,请参阅以下资源:

  • 如何在 .NET 中使用 Azure Blob 存储 服务。 MicrosoftAzure.com 网站上的官方文档。 简要介绍 Blob 存储,然后是演示如何连接到 Blob 存储、创建容器、上传和下载 Blob 等的代码示例。
  • FailSafe:生成可缩放、可复原云服务。 由乌尔里希·霍曼、马克·莫库里和马克·西姆斯组成的九部分视频系列。 以一种易于访问且有趣的方式呈现高级概念和体系结构原则,其中从 Microsoft 客户咨询团队 (CAT) 实际客户的体验中提取的故事。 有关 Azure 存储服务和 Blob 的讨论,请参阅从 35:13 开始的情节 5。
  • Microsoft 模式和做法 - Azure 指南。 请参阅附属密钥模式。