Windows Azure 存储的冗余存储选项和只读访问跨地域冗余存储

我们很高兴地宣布,现在我们使客户可以获得对数据更高的读取可用性。该预览功能称为“只读访问- 跨地域冗余存储(RA-GRS)”,使客户可以在存储帐户主要区域无法读取数据时,通过跨地域冗余的辅助区域读取相关数据, 并保证这两个副本一致。

在介绍这一新功能的具体情况之前,我们先简要概括一下Windows Azure 存储提供的冗余存储选项。然后我们将详细介绍包括新的只读访问跨地域冗余存储(RA-GRS) 在内的各个可用选项,并详细说明如何注册获取RA-GRS 的有限预览版。我们还将探讨存储客户端库的一些变动,客户可利用这些变动通过使用RA-GRS 获得更高的读取可用性。

Windows Azure 存储的冗余存储选项

Windows Azure 存储提供以下针对Blob、表和队列的冗余存储选项:

1. 本地冗余存储(LRS):通过将事务同步复制到同一区域内的三个不同存储节点上,将存储帐户中的所有数据持久化。以下章节会介绍有关LRS 的更多详情,包括如何选择LRS。

2. 跨地域冗余存储(GRS):这是创建存储帐户时的默认冗余存储选项。与LRS 一样,也是将事务同步存储到三个存储节点上,这三个节点均位于创建存储帐户时所选择的主要区域内。不同的是,GRS 还会将事务异步复制到另一个辅助区域(距离主要区域数百英里外),然后再复制到辅助区域的另外三个存储节点,实现数据持久性。以下章节将深入介绍异步复制过程、区域配对信息以及故障转移过程。

3. 只读访问跨地域冗余存储(RA-GRS):对于GRS 存储帐户,我们现已在有限预览版中推出一项新功能,使客户可以获取对辅助区域内存储帐户数据的只读访问权。因为复制到辅助区域为异步完成,因此可提供读取数据的最终一致性版本。以下章节将介绍有关RA-GRS 的更多详情,说明如何在预览模式中启用该功能,并讲解存储分析的细节。

本地冗余存储(LRS)

LRS 是什么?

本地冗余存储将数据的多个副本同步存储在一个区域内,以获得数据持久性。为确保持久性,我们将事务同步复制到跨不同故障域和升级域的三个不同存储节点上。故障域(FD) 是代表物理故障单元的一组节点,可视为归属于同一物理机架的节点。升级域(UD) 是将在服务升级(推出)过程中一起升级的一组节点。三个副本跨不同UD 和FD,确保了数据的可用性,即便出现硬件故障影响到某一机架或在推出期间升级节点时也不受影响。

除仅当三个副本全部留存才能返回成功消息外,我们还存储了数据的CRC 以确保正确性,并定期阅读和验证CRC 以检测位衰减(一段时间内发生在磁盘媒体上的随机错误)。此外,WindowsAzure存储擦除可对数据进行编码,这进一步提升了数据的持久性。有关如何使数据持久的更多信息,请参阅我们的SOSP 文档。

使用LRS的场景

LRS 成本低于GRS。根据当前的价格结构,LRS的价格要比GRS 低23% 到34% 左右,具体取决于存储的数据量。以下是选择使用LRS 而非GRS 的一些原因:

1. 可轻松重建的数据存储应用程序不会选择跨地域冗余复制数据,这不仅仅是出于成本考虑,也是因为它们的存储帐户的吞吐量更高。LRS帐户的输入和输出限额分别为10 GB 和15 GB,GRS 帐户则分别为5 GB 和10 GB。

2. 按照应用程序的数据管理要求,一些客户希望仅在某一区域内复制他们的数据。

3. 一些应用程序可能已经制定自己的跨地域冗余战略,不需要通过Windows Azure 存储服务管理跨地域冗余。

如何配置LRS

GRS 是创建存储帐户时的默认冗余存储选项,包含在Azure 存储的当前定价中。要通过Windows Azure 门户配置LRS,需要在所选存储帐户的“配置”页面选择“本地冗余”,此时折扣定价才会适用。选择LRS 后,辅助位置中的数据将被删除。需要注意的是,选择LRS 之后,再回到GRS(即跨地域冗余)模式将会产生额外费用,主要是涉及将现有数据从主要位置复制到辅助位置的输出成本。而对于GRS 而言,初始数据复制完之后,从主要位置到辅助位置跨地域复制数据无需更多额外输出费用。您可以在这里找到有关带宽费用的详细信息。

跨地域冗余存储(GRS):

GRS 是什么?

跨地域冗余存储帐户将其Blob、表和队列数据复制到距离主要区域数百英里远的辅助区域。因此,即便发生整个区域停电或区域性灾难导致主要位置无法恢复,数据仍能保持持久性。在上述LRS 部分中已经介绍过,存储帐户的更新将同步复制到主要区域中的三个存储节点,只有三个副本都留存成功时才会返回成功消息。对于GRS 而言,更新数据在主要区域完成存储后,还会异步复制到辅助区域。在辅助区域中,更新数据又将复制到三个复制集,之后才会向主要区域返回成功信息。

我们的目标是保持数据在主要位置和辅助位置的完全持久性。这意味着我们同时在两个位置保持三个副本(即,总共6 个副本),确保每个位置都可以从常见的故障(例如,磁盘、节点、机架、TOR 故障)自行恢复,而无需与其他位置进行通信。这两个位置仅在将最近的更新跨地域冗余复制到存储帐户时才需要相互通话。这一点很重要,因为它意味着,如果我们需要将存储帐户从主要位置故障转移到辅助位置,则所有已通过跨地域冗余提交到辅助位置的数据将变得持久。由于对事务执行了异步复制,因此需要注意的是,选择GRS 不会对主要位置的事务延迟产生影响。但是,由于跨地域冗余存在延迟,因此在发生区域性灾难的情况下,如果数据无法从主要区域中恢复,则尚未复制到辅助区域的增量更改可能会丢失。

辅助位置是什么?

创建存储帐户时,客户需要为存储帐户选择主要位置。存储帐户的辅助位置则是固定的,客户无法更改。下表显示了当前的主要位置和辅助位置配对:

主要位置

辅助位置

美国中北部

美国中南部

美国中南部

美国中北部

美国东部

美国西部

美国西部

美国东部

北欧

西欧

西欧

北欧

东南亚

东亚

东亚

东南亚

中国东部

中国北部

中国北部

中国东部

对跨地域冗余的事务一致性有何期望?

要了解跨地域冗余的事务一致性,需要了解 Windows Azure存储使用基于范围的分区系统,其中每个对象都有一个称为分区键的属性,这是一个伸缩单元。所有具有相同分区键值的对象将由同一个分区服务器进行处理(有关详细信息,请参见 SOSP 文档)。这些对象的分区键如下:

  • Blob:帐户名称、容器名称和 Blob名称

  • 表实体:帐户名称、表名称和应用程序定义的 PartitionKey

  • 队列消息:帐户名称和队列名称

有关存储帐户和这些对象的可伸缩性目标的详细信息,请访问此处

跨地域冗余可以确保具有相同分区键值的对象事务在辅助位置中也按与主要位置相同的顺序提交。这就是说,还需要注意的是,我们并不保证分区键值不同的对象之间的跨地域冗余排序。这意味着不同的分区可以不同的速度执行跨地域冗余。在对所有更新执行跨地域冗余复制并在辅助位置提交后,辅助位置将具有与主要位置完全相同的状态。

例如,假设在我们的存储帐户中具有两个 BlobfoobarBlob的全名做为分区键)。现在假设我们在 Blob foo上执行事务 A B,然后在 Blob bar上执行事务 X Y。我们可以保证在事务 B 前先对事务 A执行跨地域冗余,在事务 Y前先对事务 X执行跨地域冗余。但是,无法保证 foo bar事务各自执行跨地域冗余的具体时间。如果发生灾难并导致未对最近事务执行跨地域冗余,则可能事务 A X已经执行跨地域冗余,但丢失了事务 B Y。也可能事务 A B已执行跨地域冗余,但未对辅助位置执行事务 X Y。这对于涉及表和队列的操作也适用:对于表来说,分区由应用程序定义的实体分区键决定,而不是由 Blob 名称决定;对于队列来说,队列名称即分区键。

因此,要充分利用跨地域冗余,一种最佳实践是尽可能避免跨分区键关系。这意味着您应尝试将表关系限制在具有相同分区键值的实体中。单个分区键值中的所有事务在辅助位置中也按与主要位置相同的顺序提交。但是,在大规模的方案中,由于单个分区的可伸缩性目标大大低于单个存储帐户的目标,因此不建议所有实体都使用相同的分区键值。

WindowsAzure 存储支持的唯一多对象事务是 WindowsAzure表的实体组事务,这些事务允许客户端使用单个原子事务一次性提交分区键相同的一批实体。跨地域冗余同样将此批次视为原子操作。因此,整个批事务可在辅助位置以原子方式提交。

跨地域故障转移过程是什么?

跨地域故障转移是将存储帐户的辅助位置配置为新的主要位置的过程。目前,故障转移在印章级别执行,我们不能对单个存储帐户执行故障转移。我们计划提供一个 API,使客户能够在帐户级别触发故障转移,但目前尚不可用。假设故障转移在印章级别执行,如果发生了影响主要位置的重大灾难,我们将首先尝试恢复主要位置中的数据。我们优先恢复主要位置,这是由于故障转移到辅助位置可能会因为复制的异步特性而丢失最近的增量更改;此外如果可以恢复主要位置的可用性,则可能并非所有应用程序都倾向于故障转移。

如果需要执行故障转移,将通过订阅时提供的联系信息通知受影响的客户。作为故障转移的一部分,客户的“account.<service>.core.windows.net”DNS条目将从主要位置更新到辅助位置。在此 DNS 更改传播后,现有的 Blob、表、队列 URI 将继续工作。这意味着您无需更改应用程序的 URI -所有现有的 URI在跨地域故障转移前后都将以相同方式运行。例如,如果存储帐户“myaccount”的主要位置在美国中北部,则myaccount.<service>.core.windows.net DNS条目将直接与美国中北部进行通信。如果需要执行跨地域故障转移,则系统会自动更新myaccount.<service>.core.windows.net DNS条目,以便将存储帐户的所有通信直接发往美国中南部。在发生故障转移后,接受通信的位置将视为存储帐户的新的主要位置。设定新的主要位置并接受通信后,我们将启动新的辅助位置,以再次执行数据的跨地域冗余。

GRSRPO RTO是什么?

恢复点目标 (RPO):在 GRS RA-GRS中,存储服务将数据从主要位置异步跨地域复制到辅助位置。发生重大区域性灾难,需要进行故障转移时,未进行跨地域冗余复制的最新增量变化可能会丢失。这种潜在数据丢失的分钟数称为 RPO(即数据可以恢复到的时间点)。我们的 RPO通常不会超过 15分钟,不过目前尚无 SLA涉及跨地域冗余复制需要多长时间。

恢复时间目标 (RTO):另一个需要了解的指标是 RTO。这指的是进行故障转移以及执行故障转移后使存储帐户恢复联机所需的时间。进行故障转移所用的时间包括:

  • 调查并确定能否在主要位置恢复数据或是否应该进行故障转移所需要的时间
  • 通过更改 DNS条目实现帐户故障转移的时间

我们非常重视保护您的数据,所以如果有任何恢复数据的可能,我们将暂缓故障转移,并尽量恢复主要位置的数据。将来我们计划提供一个 API,使客户可以在帐户级别触发故障转移,从而使他们能够自己控制 RTO,但目前尚不可用。

使用GRS的场景

GRS 的使用客户一般是要求业务连续性规划 (BCP)具有最高持久性级别的群体,他们通过将数据分别存储在两个相距数百英里远的区域实现数据持久性,以防受到区域性灾难影响。

对跨地域冗余存储的只读访问功能 (RA-GRS)简介:

RA-GRS 通过提供对复制到辅助位置的数据的只读访问权,实现对存储帐户的更高读取可用性。启用该功能后,在主要区域无法获取数据时,可使用辅助位置获取更高可用性。该功能为选择使用,要求存储帐户进行跨地域冗余复制。

如何启用 RA-GRS

在有限预览中,客户需要在Windows Azure预览页面注册获取预览版。您的订阅 ID将进入待批准队列。得到批准后您将收到获批邮件通知。获批之后,即可对与此订阅相关的任何帐户启用 RA-GRS。您可以通过Service Management REST APIs中有关创建更新存储帐户的API或通过Windows Azure门户启用对辅助区域的只读访问权。在使用 API启用 RA-GRS时,需确保GeoReplicationEnabled SecondaryReadEnabled属性在请求负载中设置为 true通过 WindowsAzure 门户,可将存储帐户的复制属性配置为只读访问跨地域冗余存储。请注意,在该预览中,中国北部和中国东部尚不提供 RA-GRS,其他区域均提供。RA-GRS在中国提供时我们将及时更新博客文章。

RA-GRS如何运行?

启用对辅助区域的只读访问后,您将获得除主要端点以外的一个辅助端点,用于访问存储帐户。该辅助端点与主要端点类似,只是多了“-secondary”后缀。例如:如果主要端点为myaccount.<service>.core.windows.net,辅助端点即为 myaccount-secondary.<service>.core.windows.net。访问辅助端点所用的密钥就是访问主要端点的密钥。使用相同的密钥可使同一个共享服务签名同时适用于主要端点和辅助端点。也就是说,用于访问主要端点和辅助端点的签名中的规范资源部分需保持一致。因此,规范资源部分所用的帐户名称应避免使用“-secondary”后缀(注意,一些现有的存储资源管理工具使用 DNS 提取帐户名称时可能未排除该后缀,因此可能无法从辅助端点读取)。这样在主要端点无法实现更高可用性时,便可向已获取的辅助端点分配读取请求。请注意,对该辅助端点的任何放入/删除请求都会被自动拒绝,并返回 HTTP 状态代码 403

接下来我们再重新看看前面所述的跨地域冗余复制过程。主要区域中的事务异步复制到辅助区域。但是,由于各分区键之间的事务可能发生次序颠倒问题,我们引入了一个新术语上次同步时间,这是保守估计的 RPO 时间。上次同步时间(按 UTC确定)之前的所有主要更新肯定都能在辅助区域中读取。这一时间点之后的在主要区域中做的更新则不一定能在辅助区域中读取。同一存储帐户针对 Blob、表和队列的上次同步时间值会有所不同。上次同步时间的计算方法如下:跟踪各个分区的跨地域复制同步时间,然后报告 Blob、表和队列所用的最短时间。下面我们用一个例子更好地说明这一概念。下表列出了blob“myaccount.blob.core.windows.net/mycontainer/blob1.txt”blob“myaccount.blob.core.windows.net/mycontainer/blob2.txt”的操作时间表

UTC 时间

用户操作

复制

上次同步时间

辅助区域读取请求

2013 年 10 月
  23 日,周三 22:00:00

用户上传 blob1,内容为“Hello”– 事务
  1

 

2013 年 10 月
  23 日,周三 21:58:00

 

2013 年 10 月
  23 日,周三 22:01:00

用户上传 blob2,内容为“Cheers”– 事务
  2

 

2013 年 10 月
  23 日,周三 21:58:00

 

2013 年 10 月
  23 日,周三 22:02:00

用户发出对辅助位置上 blob1 和 blob2 的读取请求

 

2013 年 10 月
  23 日,周三 21:58:00

blob1 和 blob2 读取结果为
  404

2013 年 10 月
  23 日,周三 22:03:00

 

上传的事务 2 已复制到辅助位置

2013 年 10 月
  23 日,周三 21:58:00

 

2013 年 10 月
  23 日,周三 22:04:00

用户更新 blob1,内容为“Adios”– 事务
  3

 

2013 年 10 月
  23 日,周三 21:58:00

 

2013 年 10 月
  23 日,周三 22:05:00

用户发出对辅助位置上 blob1 和 blob2 的读取请求

 

2013 年 10 月
  23 日,周三 21:58:00

读取 blob1 时返回 404,读取
  blob2 时返回“Cheers”

2013 年 10 月
  23 日,周三 22:05:30

 

上传的事务 1 已复制到辅助位置

2013 年 10 月
  23 日,周三 22:01:00

 

2013 年 10 月
  23 日,周三 22:06:00

用户发出对辅助位置上 blob1 的读取请求

 

2013 年 10 月
  23 日,周三 22:01:00

读取 blob1 时返回“Hello”

2013 年 10 月
  23 日,周三 22:07:00

 

上传的事务 3 已复制到辅助位置

2013 年 10 月
  23 日,周三 22:04:00

 

2013 年 10 月
  23 日,周三 22:08:00

用户发出对辅助位置上 blob1 的读取请求

 

2013 年 10 月
  23 日,周三 22:04:00

读取 blob1 时返回“Adios”

此处需要注意的是:

  • 2013 10 23日,周三 22:03:00,由于尚未复制事务 1,因此,尽管已复制事务 2且可以读取 blob2,上次同步时间仍然是“2013 10 23日,周三 21:58:00”。上次同步时间是保守估计的 RPO时间,可以保证截止到该时间的所有事务均可在存储帐户的所有 Blob之间的辅助位置进行读取访问。

  • 2013 10 23日,周三 22:05:30,只有复制了事务 1,上次同步时间才会移动到 22:01(由于事务 2 已复制)。由于尚未复制与更改相关的事务 3,因此读取 blob1 时,将返回事务 1所设置的内容“Hello”

  • 2013 10 23日,周三 22:07:00,只有复制了事务 3,上次同步时间才会移动到 22:04:00,指明 blob1 blob2上的所有更改可在辅助位置进行读取访问。此时,对 blob1的任何读取都会将内容的更改反映为“Adios”

如何使用RA-GRS查找上次同步时间?

REST版本2013-08-15开始,新的 REST API“Get Service Stats”可用于Blob队列服务。 API 只能从辅助端点使用,为上述服务提供跨地域冗余统计。统计包括为每项服务维护的两方面信息:

1.跨地域冗余状态:可为以下三个值之一

a.Live:表示启用跨地域冗余,使其处于活动状态并正常运行

b.Bootstrap:表示存储帐户从 LRS更改为 GRS时,将数据从主要位置引导到辅助位置的初始化阶段。在此阶段中,可能无法读取辅助端点。

c.Unavailable:由于系统中断,系统无法计算上次同步时间,或者尚未计算上次同步时间。

2.上次同步时间:这表示上文所述的服务的复制滞后时间。如果跨地域冗余状态为“Bootstrap”“Unavailable”,则此时间为空。如果处于“Live”状态,则此时间是有效的 UTC 时间。

RA-GRS SLA和定价是什么?

RA-GRS的优势是其为存储帐户提供的读取可用性高于 GRS,达到 99.99+%,后者为 99.9+%。使用 RA-GRS 时,写入可用性仍为 99.9+%(与现在的 GRS 相同);RA-GRS的读取可用性是 99.99+%,如果主要位置不可用,数据将从辅助位置中读取。在定价方面,RA-GRS的容量 (GB)费用略高于 GRS,而 GRS RA-GRS的事务和带宽费用则相同。有关 SLA和定价的更多详细信息,请单击此处访问 Windows Azure 存储定价页面。

RA-GRS存储分析是什么?

WindowsAzure存储服务向用户提供存储分析数据,利用这些数据可以监控存储服务的使用情况。通过 RA-GRS,还可以使用对辅助端点所执行事务的存储指标,只要这些指标是通过为 Windows AzureBlob队列使用设置服务属性的主要端点而启用的。简单地说,在辅助端点发出的事务的指标数据只能在以下表名的主要端点上使用:

  • $MetricsHourSecondaryTransactionsBlob
  • $MetricsHourSecondaryTransactionsTable

  • $MetricsHourSecondaryTransactionsQueue

  • $MetricsMinuteSecondaryTransactionsBlob

  • $MetricsMinuteSecondaryTransactionsTable

  • $MetricsMinuteSecondaryTransactionsQueue

    对主要端点所执行事务的存储指标在以下位置仍然可用:

  • $MetricsHourPrimaryTransactionsBlob

  • $MetricsHourPrimaryTransactionsTable

  • $MetricsHourPrimaryTransactionsQueue

  • $MetricsMinutePrimaryTransactionsBlob

  • $MetricsMinutePrimaryTransactionsTable

  • $MetricsMinutePrimaryTransactionsQueue

RA-GRS的预览版中,日志尚不能用于针对辅助端点的事务。有关分析的详细信息,请参阅MSDN

场景

对辅助端点的只读访问实现了较高的读取可用性。存储帐户的主要端点不可用时,若应用程序需要较高可用性且可以处理最终一致性读取,可以在辅助端点发出读取请求。

存储客户端库支持 RA-GRS

Storage Client Library 3.0使用 REST版本2013-08-15,为 RA-GRS 提供了新功能。它能够查询Blob、表和队列的上次同步时间;如果对主要端点的请求超时,还提供库支持,从而自动重试读取辅助端点。

1. CloudBlobClientCloudTableClientCloudQueueClient GetServiceStats API:此 API使应用程序能够轻松检索每个服务的跨地域冗余状态和上次同步时间。

示例:

CloudStorageAccount account = CloudStorageAccount.Parse(cxnString);
CloudTableClient client = account.CreateCloudTableClient();
    
// Note that Get Service Stats is supported only on secondary endpoint
client.LocationMode = LocationMode.SecondaryOnly;
ServiceStats stats = client.GetServiceStats();
string lastSyncTime = stats.GeoReplication.LastSyncTime.HasValue ?
stats.GeoReplication.LastSyncTime.Value.ToString() : "empty";
Console.WriteLine("Replication status = {0} and LastSyncTime = {1}", stats.GeoReplication.Status, lastSyncTime);

2.LocationMode属性:此属性能够在主要端点不可用时使用辅助端点。此属性的重要值为:

a. PrimaryOnly:所有读取请求均只对主要端点发出。

b. PrimaryThenSecondary:首先对主要端点发出读取请求,如果此操作失败,并出现可重试错误,则后续重试将在辅助端点和主要端点之间交替发出请求。如果访问辅助端点为对象返回 404(未找到),则后续重试将仍然位于主要端点。

c.SecondaryOnly:对辅助端点发出读取请求。

请注意,除“SecondaryOnly”外,所有LocationMode选项仅向主要端点继续发送写入请求。对写入请求使用“SecondaryOnly”选项将抛出存储异常。

此属性可在以下两个位置之一设置:

  • Cloud[Blob|Table|Queue]ClientLocationMode选项可用于通过与此客户端关联的对象发出的所有请求。

  • [Blob|Table|Queue]RequestOptions:可以使用同一个客户端对象在 API 级别覆盖LocationMode选项。

使用 PrimaryThenSecondary发出下载Blob请求的代码示例:

CloudStorageAccount account =CloudStorageAccount.Parse(cxnString);
CloudBlobClient client =account.CreateCloudBlobClient();
CloudBlobContainer container =client.GetContainerReference(containerName);
CloudBlockBlob blob =container.GetBlockBlobReference(blobName);

//使用请求选项设置请求的位置模式。此请求将首先尝试
//使用主要位置下载 Blob,如果此操作失败,后续重试将尝试辅助位置
blob.DownloadToFile(
localFileName,
FileMode.OpenOrCreate,
null/*访问条件 */,
new BlobRequestOptions()
{
LocationMode =LocationMode.PrimaryThenSecondary,
ServerTimeout =TimeSpan.FromMinutes(3)
});

3. 已引入新的重试策略接口 IExtendedRetryPolicy,使用户可以扩展重试,其后续重试的目标位置可以更改。此接口提供了一种取代 ShouldRetry 选项的新方法Evaluate。请注意,ShouldRetry仍然保留在此接口中以保证向后兼容性,但不再使用。

RetryInterval外,Evaluate方法还使用户返回 RetryInfo,其中包含了在后续重试中要使用的位置。

在重试类实现的以下示例中,我们仅在最后一次尝试时将目标位置更改为辅助位置。

public RetryInfo Evaluate(RetryContext retryContext,OperationContext operationContext)
{
   statusCode =retryContext.LastRequestResult.HttpStatusCode;

   if(retryContext.CurrentRetryCount >=this.maximumAttempts
       || ((statusCode >= 300 &&statusCode < 500 && statusCode != 408)
       || statusCode == 501//未实施
       || statusCode == 505//版本不受支持
           ))
   {
       //不重试
       returnnull;
   }

   RetryInfo info =new RetryInfo();
   info.RetryInterval = EvaluateBackoffTime();
   if(retryContext.CurrentRetryCount ==this.maximumAttempts - 1)
   {
       //在辅助端点上重试
       info.TargetLocation =StorageLocation.Secondary;
   }

   return info;
}

希望您喜欢这项新功能,请在本博客或 WindowsAzure存储论坛发表评论,提供您的反馈。

Jai Haridas Brad Calder

本文翻译自:

http://blogs.msdn.com/b/windowsazurestorage/archive/2013/12/04/introducing-read-access-geo-replicated-storage-ra-grs-for-windows-azure-storage.aspx

原文地址:https://www.cnblogs.com/new0801/p/6176289.html