65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Azure 存储

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (20投票s)

2010年5月24日

CPOL

18分钟阅读

viewsIcon

189668

downloadIcon

2734

简单了解 Windows Azure 存储是什么以及如何使用它

引言

Windows Azure 是微软的云计算产品,提供三种存储类型:Blob、Table 和 Queue。本文将演示这三种类型的半实际用途,以帮助您理解每种类型以及它们如何使用。该应用程序将包含两个角色:一个用于用户界面的 Web 角色,以及一个用于后台处理的辅助角色。Web 角色将有一个简单的 Web 应用程序,用于将图像上传到 Blob 存储,并将有关它们的元数据上传到 Table 存储。然后,它将向队列添加一条消息,该消息将被辅助角色读取,以在上传的图像上添加水印。

运行此应用程序不需要 Windows Azure 帐户,因为它将使用开发环境。

必备组件

要运行本文中的示例代码,您需要

Windows Azure 概述

在开始构建应用程序之前,有必要快速概述一下 Windows Azure 和角色。有许多资源可以描述这些,因此我在这里不会详细介绍。

Windows Azure 是微软的云计算产品,作为 Windows Azure 平台(Platform)的开发、服务托管和管理环境。该平台由三个部分组成:Windows Azure、SQL Azure 和 AppFabric。

  • Windows Azure:基于云的操作系统,提供虚拟化托管环境、计算资源和存储。
  • SQL Azure:基于云的关系数据库管理系统,包括报告和分析。
  • AppFabric:用于连接分布式应用程序的服务总线和访问控制,包括本地和云应用程序。

Windows Azure 角色

与大多数开发人员可能熟悉的安全相关角色不同,Windows Azure 角色用于在应用程序部署时为其配置虚拟环境。下图显示了 Visual Studio 2010 中当前可用的角色。

Roles

除 CGI Web 角色外,这些都应该是自解释的。CGI Web 角色用于为运行非 ASP.NET Web 应用程序(如 PHP)提供环境。这为客户提供了一种将现有应用程序迁移到云的方法,而无需花费与用 .NET 重写它们相关的成本和时间。

构建 Azure 应用程序

当然,第一步是创建用于此演示的 Windows Azure 应用程序。安装并配置好先决条件后,您可以打开 Visual Studio 并按照正常路径创建新项目。在“新建项目”对话框中,展开“Visual C#”树(如果尚未展开),然后单击“云”。您将看到一个可用模板:Windows Azure Cloud Service。

New Project

选择此模板后,将显示“新建云服务项目”对话框,其中列出了可用的 Windows Azure 角色。对于此应用程序,请选择一个 ASP.NET Web 角色和一个辅助角色。将角色添加到“云服务解决方案”列表后,可以通过将鼠标悬停在角色上显示编辑链接来重命名它们。当然,您可以在解决方案创建后添加其他角色。

Cloud Service Project

解决方案创建后,您将在“解决方案资源管理器”中看到三个项目。

Solution Explorer

由于本文是关于 Azure 存储而不是 Windows Azure 本身,因此我将简要介绍一些设置,而将更深入的介绍留给其他文章或资源。

角色文件夹下,您可以看到两个项目,分别对应于上一步中添加的每个角色。无论您双击该项目还是右键单击并从上下文菜单中选择“属性”,都会打开给定角色的“属性”页面。下图是 AzureStorageWeb Role 的属性页面。

Properties

“配置”选项卡中的第一个部分是选择应用程序的信任级别。大多数 .NET 开发人员应该都熟悉这些设置。“实例”部分告诉 Windows Azure 平台要创建多少个此角色的实例以及要配置的虚拟机大小。如果此 Web 角色用于高流量的 Web 应用程序,则选择大量实例将提高其可用性。Windows Azure 将处理所有已创建实例的负载均衡。VM 的大小如下:

计算实例大小 CPU 内存 实例存储 I/O 性能
特小 1.0 Ghz 768 MB 20 GB 低功耗
1.6 GHz 1.75 GB 225 GB
媒体 2 x 1.6 GHz 3.5 GB 490 GB
4 x 1.6 GHz 7 GB 1,000 GB
特大 8 x 1.6 GHz 14 GB 2,040 GB

“启动操作”特定于 Web 角色,如您所见,它允许您指定应用程序是通过 HTTP 还是 HTTPS 访问。“设置”选项卡对于 .NET 开发人员来说应该很熟悉,您可以在这里创建应用程序的任何其他设置。在此处添加的任何设置都将放在ServiceConfigurationServiceDefinition 文件中,因为它们适用于服务本身,而不是特定于角色项目。当然,项目也有特定于它们的web.configapp.config 文件。

Settings

“端点”选项卡允许您配置为角色配置和公开的端点。在这种情况下,Web 角色可以配置为使用 HTTP 或 HTTPS,并带有特定的端口和(如果适用)SSL 证书。

EndPoints

Web 角色

由于本文旨在关注 Azure 存储,因此我将保持 UI 简单。但是,通过一些样式,简单的界面也能轻松地看起来不错。

UI

Web 应用程序没有什么特别之处,它与其他您构建过的 Web 应用程序一样。然而,有一个类是 Azure 特有的,那就是 WebRole 类。Windows Azure 中的所有角色都必须有一个派生自 RoleEntryPoint 的类。Windows Azure 使用此类来初始化和控制应用程序。默认实现提供了 OnStart 方法的一个基本重写。如果需要在应用程序启动前执行操作,则应在此处处理。同样,可以重写 RunOnStop 方法,以便在应用程序运行之前和停止之前执行操作。

public override bool OnStart()
{
    return base.OnStart();
}

您在这里想要添加的一件事是,在对配置文件进行更改时能够重新启动角色。令人奇怪的是,这在 Windows Azure Tools for Visual Studio v1.1 中是默认实现,但在 v1.3 中不是。此代码为 RoleEnvironmentChanging 事件分配了一个处理程序,允许在配置更改(例如增加实例数或添加新设置)时重新启动角色。

public override bool OnStart()
{
    // For information on handling configuration changes
    // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
    RoleEnvironment.Changing += RoleEnvironmentChanging;

    return base.OnStart();
}

private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
    // If a configuration setting is changing
    if(e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
    {
        // Set e.Cancel to true to restart this role instance
        e.Cancel = true;
    }
}

Azure 存储

正如我所说,Windows Azure 平台提供三种存储类型:Blob、Table 和 Queue。

Blob 存储

二进制大对象(Binary Large Object),或称 Blob,对于大多数开发人员来说应该很熟悉,它用于存储图像、文档或视频等内容;比名称或 ID 更大的东西。Blob 存储按容器组织,每个容器可以有两种类型的 Blob:块(Block)和页(Page)。所需的 Blob 类型取决于其用途和大小。块 Blob 限制为 200 GB,而页 Blob 可高达 1 TB。但请注意,在开发中,存储 Blob 限制为 2 GB。可以通过 RESTful 方法访问 Blob 存储,URL 格式为:http://myapp.blob.core.windows.net/container_name/blob_name

虽然 Blob 存储不是分层的,但可以通过名称进行模拟。Blob 名称可以使用“/”,因此您可以拥有如下名称:

  • http://myapp.blob.core.windows.net/container_name/2009/10/4/photo1
  • http://myapp.blob.core.windows.net/container_name/2009/10/4/photo2
  • http://myapp.blob.core.windows.net/container_name/2008/6/25/photo1

这里看起来 Blob 是按年、月、日组织的;然而,实际上,Blob 的名称是 2009/10/4/photo12009/10/4/photo22008/6/25/photo1

块 Blob

虽然块 Blob 可达 200 GB,但如果大于 64 MB,则必须分多个块发送,每个块不超过 4 MB。存储块 Blob 也是一个两步过程;必须先提交块,然后才能使用它。当块 Blob 分多个块发送时,可以按任何顺序发送。Commit 调用发生的顺序决定了 Blob 的组装方式。幸运的是,正如我们稍后将看到的,Azure 存储 API 隐藏了这些细节,因此您不必担心它们,除非您想。

页 Blob

页 Blob 的大小可达 1 TB,并被组织成块内的 512 字节页。这意味着可以通过使用从 Blob 开始的偏移量来访问 Blob 中的任何点以进行读写操作。这是使用页 Blob 而非块 Blob 的优势,块 Blob 只能作为一个整体进行访问。

Table 存储

Azure 表与 RDBMS(如 SQL Server)中的表不同。它们由实体和属性的集合组成,属性进一步包含名称、类型和值的集合。需要认识到的是(这可能会给一些开发人员带来问题),Azure 表不能使用 ADO.NET 方法访问。与所有其他 Azure 存储方法一样,它提供 RESTful 访问:http://myapp.table.core.winodws.net/TableName

稍后在实际代码部分,我将详细介绍表。

Queue 存储

队列用于在应用程序之间传输消息,无论是 Azure 应用程序还是非 Azure 应用程序。可以将其视为云中的 Microsoft 消息队列 (MSMQ)。与其他存储类型一样,也可以通过 RESTful 访问:http://myapp.queue.core.windows.net/Queuename

队列消息的大小只能为 8 KB;请记住,它不用于传输大对象,只用于消息。但是,消息可以是 Blob 或表实体的 URI。Azure 队列与传统队列实现的不同之处在于它不是 FIFO 容器。这意味着消息将保留在队列中,直到被显式删除。如果一个进程读取了一条消息,它将在一定时间内对其他进程不可见,默认时间为 30 秒,最多可达 2 小时;如果消息在此期间未被删除,它将返回到队列并可供再次处理。由于这种行为,也不能保证消息的顺序。

构建存储方法

首先,我将在解决方案中添加另一个项目,一个类库项目。该项目将作为此解决方案中使用的存储方法和实现的容器。创建项目后,您需要添加对 Windows Azure 存储程序集 Microsoft.WindowsAzure.StorageClient.dll 的引用,该程序集可以在 Windows Azure SDK 文件夹中找到。

由于任何访问都需要 CloudStorageAccount,因此我将创建一个基类来包含它的一个属性。

public static CloudStorageAccount Account
{
    get
    {
        // For development this can be used
        //return CloudStorageAccount.DevelopmentStorageAccount;
        // or this so code doesn't need to be changed before deployment
        return CloudStorageAccount.FromConfigurationSetting
	("Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString");
    }
}

您将在此处看到,我们可以使用两种方法来返回 CloudStorageAccount 对象。由于应用程序在开发环境中运行,我们可以使用第一种方法并返回 static 属性 DevelopmentStorageAccount。但是,在部署之前,这需要更新为实际帐户。然而,使用第二种方法,可以从配置文件中检索帐户信息,类似于 app.configweb.config 文件中的数据库连接字符串。但是,在使用 FromConfigurationSetting 方法之前,您必须在每个方法的 OnStart 方法中添加一些代码。Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString 来自设置选项卡,并存储在 ServiceConfiguration.cscfg 文件中。它有点长,所以您可以随意在设置选项卡中重命名它。

// This code is necessary to use CloudStorageAccount.FromConfigurationSetting
CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
    configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
    RoleEnvironment.Changed += (sender, arg) =>
    {
        if(arg.Changes.OfType<roleenvironmentconfigurationsettingchange>()
            .Any((change) => (change.ConfigurationSettingName == configName)))
        {
            if(!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
            {
                RoleEnvironment.RequestRecycle();
            }
        }
    };
});

此代码基本上告诉运行时使用配置文件获取设置信息,并为 RoleEnvironment.Changed 事件设置一个事件处理程序,以检测对配置文件的任何更改。如果检测到更改,将重新启动角色,以便这些更改生效。此代码也使得默认的 RoleEnvironment.Changing 事件处理程序不必要,因为它们都执行相同的操作,即在配置更改时重新启动角色。

设置环境

在使用 Azure 存储元素之前,必须先创建它们,并且因为它们只需要执行一次,所以一个好的地方是在每个角色的 OnStart 方法中执行此操作。我将在那里添加一个方法调用,该方法将根据需要创建队列、Blob 和表。如前所述,CreateIfNotExists 方法在元素不存在并被创建时返回 true。但对于此示例,这并不重要,所以我将忽略它。

public static void CreateContainersQueuesTables()
{
    // These methods return true if the container did not exist and was created
    bool didExist = Blob.CreateIfNotExist();
    didExist = Queue.CreateIfNotExist();
    didExist = Table.CreateTableIfNotExist(TABLE_NAME);
}

实现 Blob 存储

您需要的第一样东西是 CloudBlobClient 对象的引用,以访问其方法。如您所见,有两种方法可以做到这一点。两者都产生相同的结果;一种只是打字少,而另一种提供了更多创建控制。

private static CloudBlobClient Client
{
    get
    {
        //return new CloudBlobClient(Account.BlobEndpoint.AbsoluteUri,
        //                           Account.Credentials);

        // More direct method
        return Account.CreateCloudBlobClient();
    }
}

上传 Blob 是相对容易的任务。

protected static CloudBlobContainer Blob
{
    get { return BlobClient.GetContainerReference(BLOB_CONTAINER_NAME); }
}

public static void PutBlobBlock(Stream stream, string fileName)
{
    CloudBlob blobRef = Blob.GetBlobReference(fileName);
    blobRef.UploadFromStream(stream);

    return blobRef.Uri.ToString();
}

如您所见,第一步是检索对容器的引用。有了这个容器,下一步是获取对 CloudBlob 的引用,以便上传文件。如果指定名称的 CloudBlob 已存在,它将被覆盖。获得 CloudBlob 对象引用后,只需调用相应的方法上传 Blob 即可。在这种情况下,我将使用 UploadFromStream 方法,因为文件来自 ASP.NET Upload 控件,作为 stream;但是,根据环境和用途,还有其他方法,例如 UploadFile,它使用物理文件的路径。所有上传和下载方法都有异步对应项。

这里要注意的一点是,容器名称必须是小写。如果尝试使用带大写字母的名称,您将收到一个相当神秘且无信息量的 StorageClientException,消息为“请求输入的一个超出范围。”。此外,InnerException 将是一个 WebException,消息为“远程服务器返回错误:(400) 错误请求。”。

实现 Table 存储

在这三种存储类型中,Azure Table Storage 需要最多的设置。首先需要为将在表中存储的数据创建模型。

public class MetaData : TableServiceEntity
{
    public MetaData()
    {
        PartitionKey = "MetaData";
        RowKey = "Not Set";
    }

    public string Description { get; set; }
    public DateTime Date { get; set; }
    public string ImageURL { get; set; }
}

对于此演示,模型非常简单,但最重要的是,它派生自 TableServiceEntity,这告诉 Azure 该类代表一个表实体。虽然 Azure Table Storage 不是关系型数据库,但必须有一些机制来唯一标识存储在表中的行。TableServiceEntity 类中的 PartitionKeyRowKey 属性用于此目的。PartitionKey 本身用于在虚拟环境中将表数据分区到多个存储节点,并且,虽然应用程序可以使用一个分区存储所有表数据,但这可能不是可伸缩性和性能的最佳解决方案。

Windows Azure Table Storage 基于 WCF Data Services(以前称为 ADO.NET Data Services),因此需要一些表的上下文。TableServiceContext 类代表此上下文,因此我将派生一个类来继承它。

public class MetaDataContext : TableServiceContext
{
    public MetaDataContext(string baseAddress, StorageCredentials credentials)
        : base(baseAddress, credentials)
    {
        // Alternative method of creating table
        //CloudTableClient.CreateTablesFromModel(typeof(MetaDataContext),
        //                                       baseAddress, credentials);
    }
}

在构造函数中,您可以看到创建表的替代方法,但是,由于它已在 RoleEntryPoint OnStart 中创建,我将其保留仅供参考并将其注释掉。

向表中添加数据应该与处理过 LINQ to SQL 或 Entity Framework 的任何人非常熟悉。将对象添加到数据上下文,然后保存所有更改。请注意这里的 RowKey。由于我使用了日期和文件名,因此需要进行一些小修改,因为 RowKey 不能包含“/”字符。我还想先检查记录是否已存在,如果存在则更新。

public void Add(MetaData data)
{
    // RowKey can't have / so replace it
    data.RowKey = data.RowKey.Replace("/", "_");

    MetaData original = (from e in MetaData
                        where e.RowKey == data.RowKey
                        && e.PartitionKey == Table.PARTITION_KEY
                        select e).FirstOrDefault();

    // Check if the object already exists
    // and update if so
    if(original != null)
    {
        Update(original, data);
    }
    else
    {
        AddObject(StorageBase.TABLE_NAME, data);
    }

    SaveChanges();
}

如果您熟悉 Linq,那么您会期望 FirstOrDefault 在找不到记录时返回 null。但是,对于 WCF Data Services 和 Azure Table Storage,如果查询同时使用 RowKeyPartitionKey,它将返回一个 DataServiceQueryException,其 InnerException404 Resource not found。要解决此问题,必须将 IgnoreResourceNotFoundException = true 设置为 true。构造函数是一个很好的设置位置 。

public MetaDataContext(string baseAddress, StorageCredentials credentials)
    : base(baseAddress, credentials)
{
    // Alternative method of creating table
    //CloudTableClient.CreateTablesFromModel(typeof(MetaDataContext),
    //                           baseAddress, credentials);

    // Prevent DataServiceQueryException when no records
    // match a query
    IgnoreResourceNotFoundException = true;
}

实现 Queue 存储

队列存储可能是最容易实现的部分。与 Table 存储不同,不需要设置模型和上下文,与 Blob 存储不同,不需要担心块和页。队列存储仅用于存储 8 KB 或更小的短消息。向队列添加消息遵循与其他存储机制相同的模式。首先,获取对队列的引用,如果需要则创建它,然后添加消息。

public void Add(CloudQueueMessage msg)
{
    Queue.AddMessage(msg);
}

从队列检索消息也一样简单。

public CloudQueueMessage GetNextMessage()
{
    return Queue.PeekMessage() != null ? Queue.GetMessage() : null;
}

关于 Azure 队列需要理解的一点是,它们不像您可能熟悉的其他类型的队列(例如 MSMQ)。当从队列中检索消息时(如上),它不会从队列中移除。消息将保留在队列中,但 Windows Azure 会设置一个标志,使其对其他请求不可见。如果在一段时间内(默认为 30 秒)未从队列中删除消息,它将再次对 GetMessage 调用可见。这种行为也使得 Windows Azure 队列不像其他实现那样是 FIFO 的。

在某个时候,您可能想要获取队列中的所有消息。尽管 GetMessages 方法可以用于此目的,但肯定有一些问题需要注意。GetMessages 接受一个参数来指定要返回的消息数。指定的消息数大于队列中的消息数将产生一个 Exception,因此您需要知道队列中的消息数才能检索。由于如上所述队列的动态性质,Windows Azure 最多只能给出近似计数。CloudQueue.ApproximateMessageCount 属性似乎非常适合此目的。不幸的是,此属性似乎始终返回 null。要获取计数,您需要使用 Queue.RetrieveApproximateMessageCount() 方法。

public static List<CloudQueueMessage> GetAllMessages()
{
    // If the number requested is greater than the actual
    // number of messages in the queue this will fail.
    // So get the approximate number of messages available

    // Although this would seem to a good property to use
    // it will always return null
    //int? count = Queue.ApproximateMessageCount;

    // To get the count use this method
    int count = Queue.RetrieveApproximateMessageCount();

    return Queue.GetMessages(count).ToList();
}

Worker Role (辅助角色)

现在我们可以最后来看辅助角色了。为了演示如何将辅助角色合并到项目中,我将使用它来为已上传的图像添加水印。之前创建的队列将用于通知此辅助角色何时需要处理图像以及处理哪个图像。

与 Web 角色一样,OnStart 方法用于设置和配置环境。辅助角色有一个额外的方法 Run,它创建一个循环并无限期地继续。没有退出条件有些奇怪;相反,当为此角色调用 Stop 时,它会强制终止循环,这可能会导致其中运行的任何代码出现问题。

public override void Run()
{
    // This is a sample worker implementation. Replace with your logic.
    Trace.WriteLine("AzureStorageWorker entry point called", "Information");

    while(true)
    {
        PhotoProcessing.Run();

        Thread.Sleep(5000);
        Trace.WriteLine("Working", "Information");
    }
}

您可以查看本文的示例代码,以了解 PhotoProcessing.Run 的详细信息。它只是获取 QueueMessage 中指示的 Blob,添加水印,然后更新 Blob 存储。

整合

现在一切都已实现,只需将它们放在一起即可。使用 ASPX 页面上的上传按钮的 Click 事件,我将获取正在上传的文件和其他相关详细信息。第一步是上传 Blob,以便我们获得指向它的 URI,并将其与描述和日期一起添加到 Table 存储。最后一步是向队列添加一条消息以触发辅助进程。

protected void OnUpload(object sender, EventArgs e)
{
    if(FileUpload.HasFile)
    {
        DateTime dt = DateTime.Parse(Date.Text);
        string fileName = string.Format("{0}_{1}",
		dt.ToString("yyyy/MM/dd"), FileUpload.FileName);

        // Upload the blob
        string blobURI = Storage.Blob.PutBlob
			(FileUpload.PostedFile.InputStream, fileName);


        // Add entry to table
        Storage.Table.Add(new Storage.MetaData
        {
            Description = Description.Text,
            Date = dt,
            ImageURL = blobURI,
            RowKey = fileName
        }
        );

        // Add message to queue
        Storage.Queue.Add(new CloudQueueMessage(blobURI + "$" + fileName));

        // Reset fields
        Description.Text = "";
        Date.Text = "";
    }
}

结论

希望本文能帮助您了解 Windows Azure 存储是什么以及如何使用它。当然,关于这个主题还有很多内容可以涵盖,这可能会在后续文章中介绍。但是,这里有一些资源可以为您提供有关 Windows Azure 和 Windows Azure 存储的额外信息和见解。

关注点

表、Blob 容器和队列的名称似乎混合支持大写名称。最好的方法似乎是始终使用小写。

SDK 和 Visual Studio 工具的每个版本之间存在一些细微差别和差异。例如,本文的第一个版本是用版本 1.1 编写的,设置 SetConfigurationSettingPublisher 不是问题。遇到的另一个 bug 是当 web.config 为只读时(例如在使用源代码控制时)出现的奇怪的 System.ServiceModel.CommunicationObjectFailedException。有关更多信息,请参阅此链接

历史

  • 首次发布:2010 年 5 月 24 日
  • 更新:2011 年 1 月 1 日
    • Windows Azure SDK 和 Visual Studio v1.3 的 Windows Azure 工具,以及 .NET Framework 4.0
© . All rights reserved.