Windows Azure 存储:一个高可用的强一致云存储

概要

Blob是存储二进制大对象(Binary Large Object)的简称用来存储视频,图像,文档等非结构化数据。

Windows Azure Storage(WAS)是一个云存储系统提供给客户可靠性以存储以在任何有限的时间存储看似无限的数据量。WAS的客户可以在任何时间任何方式访问它们的数据只需要支持它们使用的存储的费用。在WAS中,使用本地和地理复制持久地存储数据,以促进灾难恢复。WAS(Web 应用程序服务)存储以以下形式存在:Blob(文件)、表(结构化存储)和队列(消息传递)。在这篇论文中,我们描述了WAS架构,全局命名空间和数据模型,以及它的资源供应,负载均衡和复制系统。

介绍

Windows Azure Store是一个可扩展的云存储系统它自从2008年11月开始就被投入生产系统。它被用在微软内部应用程序,比如社交网络查询,视频服务,音乐盒游戏内容,管理医疗记录和更多。此外,成千上万个用户在在微软之外使用WAS,并且任何人都可以使用网络访问这个系统。

WAS啊提供了Blob(文件)、表(结构化存储)和队列(消息传递)形式的云存储云存储。这三个数据抽象提供了整体存储和用于许多应用的工作流。我们看到的常见使用模式通过Blob传输,输入和输出数据,队列提供了处理blob的整体工作流以及中间服务状态以及最终的状态被保存在表或者Blobs中。

这个模式的一个例子是摄取引擎服务,构建在Windows Azure上已提供简介实时的Facebook和Twitter查询。这个服务是大规模数据处理流水线的一部分,它在FaceBook或者Twitter发布更新状态15秒之内,提供了公开可查的内容(通过我们的搜索引擎Bing)。FaceBook和Twitter发送原始公开内容到WAS(即用户请求,用户状态更新)以让内容可以被公开搜索。这个内容被存储在WAS的Blob中。摄取引擎用用户认证、垃圾邮件和成分分析注释这些数据;内容分类;和语言分类以及命名的实体分类。此外,引擎还会抓取扩展数据中的链接。在处理过程中,摄取引擎高速访问WAS 表并且存储返回到Blob中的结果。然后将这些Blob折叠到Bing搜索引擎使这些内容公开可搜索。摄入引擎使用了队列来管理工作流,索引作业以及将结果放入搜索引擎的时机。在写本文的时候,摄取引擎为FaceBook和Twitter保存了大概350T的数据(在复制之前)。在事务方面,摄取引擎的峰值流量负载约为每秒40,000个事务,每天处理20至30亿次事(见第七章额外的工作配置讨论)。

在构建于WAS的进程中,潜在的内在的和外部的客户推动了许多设计决策,来自这些反馈的一些关键设计特性的结果包括:

存储一致性 许多客户想要存储一致性:特别是企业客户它们将业务应用线迁移到云。它们也想要可靠谱的执行条件读取写入和删除已在持久化存储上进行乐观的并发控制。为此WAS提供了三个在CAP理论中声称是难以在同一时间达到的:强一致性,高可用性,分区容错性。

全局和可扩展的命名空间/存储 为了易于使用,WAS实现了全局的命名空间,这使得数据可以在世界上任何地方以一致的方式存储和访问。因此WAS的一个主要的目标是实现海量的数据存储,这个目标命名空间必须处理EB甚至更大的数据,我们讨论我们全局命名空间设计在第二章的细节中。

灾害发现 WAS存储谷歌数据跨越多个数据中心彼此之间相隔数百英里。这种冗提高了必要的数据恢复保护应对类似地政业火,龙卷风,核反应堆摧毁等。

多租户和存储成本 为了减少存储成本,许多顾客共享相同的存储设施。WAS结合了许多不同顾客的工作负载的不同资源,因此,与那些服务在它们自己的专用硬件上运行相比,在任何一个时间点需要配置的存储都要少得多。

2 全局分区的命名空间

我们存储系统的一个关键的目标是提供单点全局的命名空间,它允许客户端在云上寻址它的全部存储,并随着时间扩展存储需求到任意量级。为了提供此功能,我们利用DNS作为存储命名空间的一部分,并且将存储命名空间分为三部分:主名称,分区名称和对象名称。作为结果我,所有数据可以通过URI的形式进行访问。

http(s)://AccountName.<service>1.core.windows.net/PartitionName/ObjectName

账户名是谷歌选择的用来访问存储,并且是DNS域名的一部分。AccountName DNS转换用于定位存储数据的主存储数据中心。这个主位置是所有请求访问该账户数据的位置。一个应用可能使用了多个账户名来存储数据穿过不同的位置。

与账户名一起使用的分区名在请求到达时定位数据。分区名被用来基于流量需求扩展存储节点对数据的访问。

当分区名持有许多对象,对象名标识在分区中的单独的对象。系统支持自动的原子事务,跨越对象但在一个分区名下。对象名,对象名是可选的,因为对于某些类型的数据,分区名是唯一的标识账户中的对象。

这种命名方法让WAS能够灵活的支持三种数据抽象。对于Blob,完全的blob名是分区名。对于表,每个实体(行)在表中都有逐渐,它包含了俩部分的资源:分区名和对象名。这种差异运行应用使用标来分组行到相同的分区,以执行跨越分区的原子性事务。对于队列,队列名是分区名,每个信息有对象名唯一的在队列中进行表示。

2 高级架构

这里我们提出了WAS架构的高级讨论,以及如何适配到Windows Azure 云平台。

3.1 Windows Azure 云平台

Windows Azure云平台运行了许多云服务器跨越不同的数据中心和不同的地理区域。Windows Azure Fabric控制器是一个资源供应和管理层,它提供了资源分配部署更新和在WindwowsAzure 平台上的资源管理。WAS是这些服务的其中之一运行在Fabric控制器之上。

Fabric控制器提供了阶段管理,网络配置,健康监控,服务事例的启动停止以及WAS系统的服务部署。此外,WAS检索网络图片信息,集群物理布局和存储节点在Fabric控制器重的硬件配置。WAS负责管理复制,和跨磁盘的数据防止和数据负载均衡以及存储集群重的应用流量。

3.2 WAS架构组件

WAS中一个重要的特性是可靠的存储并且提供巨大的存储量(EB甚至更大)。我们当前有70PB的的原始存储在生产中,在供应过程中,根据2012年客户的需求增加几百PB的原始存储空间。

WAS生产系统包括了存储戳(Storage Stamps)和定位服务(如图1所示)。

图1:高级架构

Storage Stamps 一个存储戳是由N个存储节点机架组成的集群,其中每一个机架都是独立的故障区域具有冗余的网络和电源。集群通常在10-20个机架之间,每个机架有18个磁盘密集存储节点。我们每一代的存储戳拥有大约2PB的原始存储空间。我们的下一代存储戳持有30PB的原始存储。


磁盘短形成是一种优化磁盘性能的技术,用磁盘的外部分

为了提供低的存储成本,我们需要保持生产环境中尽可能保持高利用率的存储供应。我们的目标是保证存储戳在容量事务和贷款的录用率为70%。我们尝试避免达到80%,因为我们希望为(a)磁盘短行程保留20%的预留空间,以便通过利用磁盘的外部磁道获得更好的寻道时间和更高的吞吐量(b)为了保持戳机架出现故障时继续提供存储容量和可用性。当存储戳达到70%的录用率,位置服务使用时间戳,复制将账户迁移到不同的戳。(在3.4)

位置服务(LS) 位置服务管理所有的数据存储。它也负责管理所有戳的账户名称,LS分配账户到存储戳,然后管理它们跨越管理戳以进行容灾恢复和负载均衡。位置服务本身分布在俩个地理位置上,为了它自己的灾难恢复。

WAS提供了来自多个位置的存储在三个地理区域。美国欧洲和亚洲。每个位置都有数据咨询您,并且在该位置有多个建筑物,每个位置持有多个存储戳。为了提供额外的容量,LS可以容易的添加到新的区域,新的坐标到一个区域,或者新的 戳到一个地点。因此为了增加存储的数量,我们部署了一个或多个的存储戳在需要的位置的数据中心中,并且添加它们到LS中。LS可以为客户分别配信的存储账户给这些戳,并且从旧的戳负载均衡(迁移)到已经存在的戳。

图1展示了位置服务和俩个存储戳以及存储戳中的层。LS通过每个在生产中跨越所有位置的存储戳跟踪资源的使用。当应用请求信的存储数账户,它指定存储的位置关联(如尚美国北部)。LS然后在该位置中选择一个存储来作为账户的主戳,使用基于负载信息戳记的启发式方法(它考虑戳的充分性和其他的指标,比如网络和事务录用率)。LS之后存储账户的元数据信息在选择的存储戳,它告诉戳开始接受指定账户的流量。LS之后更新DNS来允许请求从进行路由,从名称$https://AccountName.service.core.windows.net/$到它的存储戳的虚拟IP(VIP,存储戳记向外部流量公开的IP地址)

3.3 存储戳中的三层

图1还展示了存储戳中的三层从上下往上依次是

Stream层 这一层存在磁盘上存储字节,负责分布和复制数据穿过许多服务以保持数据在数据戳的高可用。stream层可看作为是stamp的分布式文件系统层。它将文件视做stream(是一个大存储区块的有序列表被叫做区段(extents)),如何存储他们如何复制他们等,但是它是它它不理解高级对象结构和语义。数据存储在stream层中,但是可以从分区层中访问它(partition layer)。事实上,分区服务(分区层的守护进程)和流服务在戳中共同协作。

分区层 分区层的构建是为了

  1. 管理和理解高层的数据抽象(Blob,表,队列)
  2. 提供可扩展性的对象命名空间
  3. 提供对象的排序和事务的强一致性
  4. 存储对象数据在stream层之上
  5. 我缓存对象数据以减少磁盘IO

另一个这个层的意义是通过分区戳所有的数据对象已实现可扩展性。如上所述,所有对象都有分区名;他被分区名的值被分解为不相关的区段并且通过不同的分区服务提供服务。此层负责管理每个分区服务器为 Blob、表格和队列提供服务的 PartitionName 区段。此外,它提供了自动的分区名的负载均衡跨越分区服务来应对对象的流量需求。

前端(FE)层 Froint-END层包含了一组无状态服务,它接受到来的请求。FE查找账户名,验证和授权请求,然后路由请求到分区层中的分区服务(基于分区名)。分区维护分区Map,它跟踪了分区的区段以及哪个分区为哪个分区提供服务。FE服务缓存分区Map并且使用它确定转发请求到哪个分区服务。FE服务也直接从stream层流式大对象的传输,并且缓存频繁访问的数据以提高效率

3.4 俩个负载引擎

在描述stream和分区层的细节之前,我们首先简单的概要在我们系统中的俩个复制引擎,以及他们各自的责任。

Intra-Stamp复制(流层)这个系统提供了同步复制聚焦于确保所有写入戳的数据在戳中被持久化。它保证足够多的穿过不同节点在不同容错区域的数据副本在戳内以保持数据的可靠性,以面对词频,节点,和机架故障。戳内复制完全由stream层完成,并且客户端写入请求在特定的分区上。一旦在戳内复制成功复制后,成功可以被返回给客户端。

Inter-Stamp复制 (分区层)这个系统提供异步复制并且聚焦于跨戳的复制数据。Inter-stamp复制在后台完成,并且不在客户请求的关键路径上。这个复制是在对象级别的,其中为给定账户复制整个对象或者复制最近的增量更改。inter-stamp复制被用来保持账户的数据的副本于俩个位置中,对于灾害回复和在戳之间迁移账号的数据。Inter-stamp复制通过定位服务并且通过分区层执行对账号进行配置。

Inter-stamp复制聚焦于复制对象和应用事务到这些对象, 但是intra-stamp复制聚焦在复制磁盘存储的块,这些快被用于组成对象。

我们切分复制到intra-stamp和inter-stamp在俩个不同的层是因为以下原因。Intra-stamp复制提供了可靠性应对硬件故障,它在大尺度的分布式系统中发生的非常频繁,而inter-stamp复制提供了地理位置荣誉以应对地质灾害,这非常罕见。提供intra-stamp对于提供低延迟是非常关键的,因为他啊是一个关键的请求路径;然而戳复制的重点是优化在戳之间的网络带宽已达到可接受复制延迟的水平。不同的问题被通过俩个复制模式给解决了。

另一个创建这些俩个分开的复制层的原因是命名空间这俩层都必须维护。 执行intra-stamp复制在流层之上,允许需要维护的信息量被限定在单个存储戳大小的区段内。这个关注点允许所有intra-stamp的元状态被缓存在内存中以提高性能(见章节4),通过快速的提交在单个戳中客户请求的事务,使WAS提供快速的强一致性的复制。相反,分区层结合了位置服务控制和理解全局对象的命名空间穿过戳,允许它有效的复制和维护横跨数据中心的对象状态。

4 流层

流层提供了一个仅供分区实层使用的接口。他提供了类似文件系统的命名空间和API,只是所有的写都是追加的。它允许客户端(分区层)打开,关播,删除,重命名,读取,追加以及连接这些大文件,这被叫做流。stream是区段指针的有序列表,区段是一个连续的附加块。

图2展示了流//foo,其中包含了(指向了)四个区段(E1,E2,E3和E4)。每个区段包含了一组附加到上面的块。E1,E2和E3是封闭的块。这意味着他们不能在被追加;只有最后的流F4可以被追加。如果一个用户从头到尾读取流数据,它会得到区段的的块内容按照E1,E2,E3和E4的顺序。

这是最小的数据读写单元。一个块最多可以由N字节(4MB)。数据被写入(追加)作为一个或多个级联的块到区段中,其中块不必是相同的大小。客户端按块进行追加操作,并且控制每个块的大小。一个客户端读取给定流或者区域的偏移量,并且流层根据需要读取尽可能多的块,以满足读取的长度。当执行读取,将读取整个块的内容。这是因为Stream层存储它的校验和在块的级别,每个块一个校验和。整个块读取以执行校验和验证,并且在每一次块读取的时候进行检查。此外,所有的在系统中的块是被验证的,每隔几天检查一次,以检查数据完整性问题。

区段 区段是一个在流层中的复制单元,并且默认复制策略在存储戳中为区段保存三份副本。每一个区段被存储在NTFS文件并且包含连续的块。分区层使用1GB的分区大小。为了存储小对象,分区层追加许多他们到同一个区段,即便在连续的块中;为了存储哒的TB大小的对象(Blob),对象被分离到许多区段通过分区层。分区层跟踪流,区段,和字节偏移量和区段中对象的字节偏移量被作为它索引的一部分进行存储。

每个流都有分层的名字在命名空间中维护在流层上,并且流层对于分区来说就像一个大文件。流是追加的并且可以随机读取。一个流是指向区段的有序列表,它通过stream Manager进行维护。当区段链接在一起它们可以表示完全的寻址空间,在该空间中,可以按照添加到流中的顺序读取流。一个新的流可以被通过从已经存在的流中链接区段进行构建,这是一个快速的操作因为只需要更新指针列表。只有流中的最后一个区段可以被追加。所有之前的区段在流中是不可变的。

4.1 流管理者和区段节点

流层的俩个主要的区段组件是流管理者(SM)和区段节点(EN)如图3所示

Stream Manager(SM) SM保持流空间的跟踪以及流中的每个区段,并且跨区段节点的区段分配。SM是一个标准的paxos集群在之前的存储系统中被使用,并且不在客户端的关键请求路径上。SM负责:

擦除编码EC码是一种保护技术,将数据分割成多个块来实现冗余,这样部分数据损坏可以通过冗余来进行恢复

  1. 维护流命名空间和所有活动流和区段的状态
  2. 监控EN(区段节点)的健康
  3. 创建和指派区段到EN
  4. 执行由于硬件故障或者不可用导致丢失的区段的重新复制
  5. 对不在被任何流指向的区段进行垃圾收集
  6. 根据流策略调度区域数据的参数编码(见章节4.4)

SM定期的轮询(同步)EN的状态以及它存储的区段。如果SM发现区段在少于预期数量的EN上面被复制,区段的重复制将会通过SM惰性的创建,以重新恢复期望的副本级别。为了区段的副本的放置,SM随机的选择EN穿过不同的容错区域,以便将它们存储在不会因电源、网络或位于同一机架而产生相关故障的节点上。

SM不知道关于块的一切,只知道流和区段。SM位于客户请求的非关键路径上,不跟踪每个块追加,因此块的总数可以很大并且SM不能扩展以跟踪这些块。因为流和区段状态只在单个戳中被跟踪,状态的数量可以保持足够的小已适配SM的内存。流层的唯一客户端是分区层,分区层和流层是共同设计的,以确保在当前的存储戳大小下,它们不会为单个存储戳使用超过5000万个扩展和超过100,000个流。整个参数化可以很好的适应SM的32G内存。

区段节点(EN) 每个区段节点维护一组区段副本的存储通过SM进行指派。EN有N个磁盘缓存它完全被有序的区段副本和它的块控制。EN知道关于流的一切,只处理区段和块。在EN服务器的内部,每个区段都是磁盘上的文件,它持有数据块和它的校验和,以及区段偏移量映射到其他文件位置的索引。每个区段节点包括它所拥有的区段在同级副本的位置。该视图是由EN保存的SM保存的全局状态的缓存。EN只和其他EN通信以进行复制块写(追加)由客户端发出的请求,或者根据SM的指示创建一个现有副本的额外副本。当区段不被任何的流引用时,SM垃圾收集区段并且提醒EN以归还空间。

4.2 追加操作和封闭区段

流只能被追加,已经存在的数据不能被修改。追加操作是原子的:要么追加整个数据块要么什么都不追加。多个块可以在一次被追加就像一个院子的多块追加操作。最小化流中单个块的大小。多块追加操作允许我们写入大量的、序列数据在一次追加中,然后再晚些进行小读取。区段被用在客户端(分区层)和流层之间多个区块的追加会自动发生,并且如果客户端从来并没有听到请求的反馈(由于失败)客户端需要处理请求(或者封闭区段)。这就意味着客户端在面对超时时需要期望同一个块追加不止一次,并且正确的处理进程复制记录。分区层处理复制记录使用俩中方法(见第五章中分区层上的的细节)。对于元数据和提交的日志流,所有的事务写入都有一个序号,并且当如果有相同的序列号时复制记录。对于行数据和 blob 数据流,对于重复写入,只有最后一次写入会被 RangePartition 数据结构指向,因此先前的重复写入将没有引用,并将在稍后被垃圾回收。

区段有目标大小,被客户端指定(分区层),并且当它被填充到该大小时,区段在块的边界上是密封的,然后新的区段被添加到流并持续的追加到新的区段。一旦区段是密封的,就不能再追加它。密封的区段是不可变的,流层在密封区域执行特定的优化就像擦除码一样。流中的区段不必有相同的大小,它可以随时被密封,甚至任意变大。

4.3 流层的戳内复制

流层和分区层是一起设计以在对象事务级别提供强一致性存储。分区层的正确性提供的强一致性是建立在以下的来自流层的保证之上的:

  1. 一旦记录被追加并且返回给客户端,任何从副本记录的读取将会看到相同的数据(数据是不变的)
  2. 一旦区段被选中了,任何密封副本的读取将永远只能看到区段的相同内容。

数据中心,Fabric控制,和WAS有安全机制来防范恶意攻击,因此流复制无法处理此类威胁。我们考虑磁盘的故障区段和节点的错误断电,网络问题,位翻转和随机硬件故障,就像软件bug一样。这些错误可能导致数据损坏;校验和被用来探测这样的腐坏。本节的其余部分将在此上下文中讨论戳内复制方案。

4.3.1 复制流

如图3所示,当流第一次被创建(步骤A),SM指派三个副本到第一个区段(一个主和俩个secondary)到三个区段节点(步骤B),它通过SM随机的切分副本到不同的故障和更新域,同时考虑区段节点的使用情况(为了负载均衡)。此外,SM决定了那个副本会是区段的主副本。写入到区段总是从客户端到主EN的进行执行,并且主EN接管了协调向俩个secondary EN的写入。在扩展被追加数据期间,其主扩展节点(主EN)及其三个副本的位置始终保持不变(同时区段是不密封的)。因此不适用租约来表示某个区段的主EN,因此主总是确定的,同时区段是未密封的。

当SM分配区段,区段信息被送回客户端,它们会知道哪个EN持有三个副本以及哪个是主。这个状态流元数据信息的一部分并且在客户端缓存。当最后在流中的区段被密封时,相同的过程被重复。SM之后分配另一个区段,它这里成了流中区段的最后一段,并且所有新的追加到了新的流的最后的区段。

对于区段,每个追加在在区段的副本上复制三次。客户端发送所有的写请求到主EN,但是它可以从任何副本进行读取,即便是对于未密封的区段。追加被通过客户端发送到区段的主EN,并且主要负责

  1. 确定区段追加的偏移量
  2. 如果有相同长度的未完成并发请求,排序(选择偏移量)所有的追加
  3. 发送追内容加到它选中的偏移量到secondary扩展节点,
  4. 只有在对所有三个区段节点的磁盘进行了成功的追加操作之后,才向客户端返回追加操作的成功。

连续的在追加时的步骤如图3所示(被数字标记)。只有当所有的写入成功三个副本,主EN然后才会响应客户端它追加成功。如果由多个未完成的追加到相同的区段,主EN将会响应成功,按他们便宜量的顺序(按顺序进行提交)给客户端。由于追加项是按顺序进行提交的,最后的追加位置可以被认为是副本当前的提交长度。我们通过一个事实确认位在所有的副本中是相同的,那就是一个区段的主EN永远不会改变,它总是选择追加的偏移量,一个区段的追加是按顺序进行提交的,并且区段是在规章的时候是封闭的。(在4.3.2章中讨论)。

当一个流被打开了,它区段的元数据被缓存在客户端上,所以客户端可以直接到EN已读取和写入而不需要SM,直到要为流分配下一个区段。如果在写入中,一个副本的EN不可达或者它其中一个副本的磁盘故障,写失败会返回给客户端。客户端然后联系SM,并且通过当前提交的长度(4.2.2章)被写的区段是密封的。在此时不能将密封区段进行追加。SM之后将会分配新的区段副本在不同的可用的EN,这使得它称为流的最后一个区段。这个新区段的信息被返回给客户端。客户端继续使用它的新区段追加到流。这个通过SM进行密封处理并且分配新的区段平均在20ms内完成。这里的关键点是一旦新的区段被分配了,客户端可以继续追加到流,并且它不需要依赖特定的节点变得再次可用。

对于新密封的区段,如果需要,SM将在后台创建新的副本,使其恢复到预期的冗余水平。

4.3.2 密封

在高级别,SM在高级别上协调EN之间密封操作;它使用基于区段副本的提交长度的密封来确定区段的提交长度。一旦密封完毕,提交长度不会被改变。

为了密封一个区段,SM告诉三个EN他们当前的长度。在密封时,要么所有的副本有相同的长度这是简单的情况,或者给定的副本比一个副本或者另一个副本长或者更短。后一种情况只在追加失败时发生,其中一些但不是所有副本是可用的(即 一些副本得到了追加块,但是不是所有的)。我们保证SM将会密封区段,即便SM可能不能查询相关的所有EN。在密封区段时,SM将根据它可以与之通信的可用EN选择最小的提交长度。这将不会导致数据丢失因为主EN不会返回成功,除非三个EN的所有副本都被写入磁盘。这意味着最小的提交长度肯定包含着已经被客户端确认的所有操作。此外,如果最后的长度包含了永远不会被客户端确认的块也没有关系,因为客户端(分区层)自动处理这些就像在4.2中描述的。在封闭时,所有可访问的区段副本都被SM密封为被SM选中的提交长度。

一旦密封结束,区段的提交长度将永远不会被改变。如果EN在密封过程中无法被SM访问,但之后可以访问,SM将会强制EN同步给定的区段到选中的提交副本。这确保了一旦区段是密封的,所有它可用的副本(SM最终能到达的地方)是位相同的。

4.3.3 分区层的交互

一个有趣的例子是由于网络分区,客户端(分区服务)仍然能够在密封过程中与SM无法通信的EN进行交互。这一章举例分区层如何处理这些案例。

分区层有俩个不同的读模式:

在已知的路径读取 分区层使用俩个类型的数据流(行和 blob)。对这些流,它总是读取特定的路径(区段+偏移,长度)。更重要的,分区层只会使用从上一个成功追加的位置信息读取这俩个流。当所有提交成功追加到三个副本时才会发生这种情况。复制模式保证了这些读取永远能看到相同的数据

迭代分区上流中的所有记录 每个分区有俩个额外的流(元数据和提交日志)。这些是分区层从七点到流最后一条顺序的唯一流。这个操作只会发生在分区加载时(如5章所示)。这个分区层确保了,在分区加载期间分区层不会对这俩个流执行有用的附加操作。然后分区层和流层一起确保了相同的记录序列在分区加载的时候被返回。

在分区开始加载时,分区服务发送“检查提交大小”给这俩个流的最后一个区段的主EN。这检查副本是否可用,以及它们是否具有相同长度。如果不是,区段被密封并且只有读取被执行,并且仅在分区加载期间对SM密封的副本执行读取。这确保了分区加载将会看到所有的数据和区段相同的视图,即便我们要为流的最后一个区段从不同密封副本加载相同的分区读取。

4.4 擦除码密封区段

为了减少存储成本,WAS擦除码为了Blob存储密封了区段。WAS在边界分解一个区段为N个大致相同大小的片段。然后,对擦除编码算法采用Reed-Solomon法添加M个纠错码片段。只要丢失的片段不超过M个(跨越我数据段+代码段),WAS就可以创建完整的区段。

考虑到我们的数据量,擦除码密封区段是一个重要的操作。它将存储数据的成本从一个戳记内的三个完整副本(原始数据的三倍)降低到原始数据的1.3 - 1.5倍,具体取决于所使用的片段的数量。此外,与在一个戳中保留三个副本相比,擦除编码实际上提高了数据的持久性。

4.5 相关负载均衡

当读取被发送到有三个副本的区段,它们提交带有deadline的值,如果该值指定不能在deadline内读取完成,则不应该尝试读取。如果EN确定读取不能在时间限制内完成,它会立即回复客户端,不能满足deadline。这个机制允许客户端选择不同的EN以读取它的数据,类似的使得读取更快完成。

这个方法也被用在擦除码。当请求读取数据碎片所在的磁盘负载过重无法读取时,通过重构而不是读取该数据片段能够更快的提供服务。在这个例子中,读取(对于需要满足客户端请求的片段的区段)被发送到擦除编码的区段的所有片段,并且前N响应被用来重建需要的片段。

4.6 磁盘反饿死

很多硬件磁盘驱动经过优化,以实现尽可能高的吞吐量。牺牲公平性来达到这个目标。它倾向于顺序的读或者写。因此我们的系统维护许多流,它可能会非常大,我们在开发服务的过程中观察到,一些磁盘会锁定大流水线读或者写服务从而饿死其他操作。在一些磁盘上,我们观察到这可能会锁定2300毫秒的非排序IO。为了避免这个问题,当某个磁盘上已经有超过 100 毫秒的预期待处理 I/O 请求,或者当有任何已调度但未处理的待处理 I/O 请求超过 200 毫秒时,我们避免向该磁盘调度新的 I/O 请求。以稍微增加某些顺序请求的总延迟为代,使用我们自己定义的IO调度允许我们实现读写的公平性

4.7 持久性和记录

流层的持久性契约是当数据写入被流层确认时,必须有三个持久化的副本被存储在系统中。这个契约允许维护数据持久性,即便面对广泛的集群电源故障。我们这样操作我们的存储系统:所有写入操作在被确认返回给客户端之前,都必须首先写入到电源安全的存储中,以确保数据的持久性。

作为持久化契约的一部分,同时仍然获得良好的性能,一个对于系统层重要的优化是,在每个区段节点,我们为所有写入区段节点操作保留一个完整的磁盘启动器或SSD作为日志驱动器。这允许我们达到设备的全部写吞吐量潜力。当分区层执行流追加操作时,数据通过主EN被写入同时并行发送到俩个secondary进行写入。当每个EN进行追加的时候(a)将追加的所有数据写入到日志驱动器(B)将追加部分排队到区段文件所在的数据磁盘。一旦任何一个成功了,就可以区段成功。如果日志先成功了,则数据进入磁盘的时候数据也会被缓冲在内存中,并且任何读取操作直接在内存中提高知道数据位于磁盘上。从这一点起,数据从磁盘进行提供。这也允许对连续的写入合并成对磁盘数据更大的写入,并且更号的正确调度写入以及读取以达到良好的吞吐量。这是为了获得良好延迟付出的代价,代价是增加了关键路径之外的写入操作。

即便流层是一个只追加系统,我们发现添加一个日志驱动器提供了重要的好处,因为追加不必与数据磁盘上的读取操作相互冲突,以便将结果返回给客户端。日志允许分区的层的追加时间更具有一致性和更低的延迟。来看一个例子,分区层的提交日志流,在追加的操作中,速度只会和正在追加的副本中最慢的EN速度一样。对于没有日志记录的提交日志流的小追加,端到端的平均延迟为30ms。使用日志我们看到平均追加延迟为6ms。此外延迟的变化也明显的减小。

5 分区层

分区层存储不同类型的对象并且理解给定对象类型事务的意义(Blob,表,队列)。分区层提供

  1. 对象存储的不同类型的数据模型
  2. 处理不同类型对象的逻辑和语义
  3. 大量可扩展的对象命名空间
  4. 负载均衡跨越可用分区服务的对象访问
  5. 事务排序和对象访问的强一致性。

5.1 分区层数据模型

分区层提供了重要的内部数据结构叫做对象表(OT)。一个对象表是一个巨大的表可以增长到几个PB。对象表是动态分解为RangePartitions(基于表的流量负载)并且在戳中分布在分区服务上(5.2章)。一个RangePartition是对象表中给定从高key到低key的连续区段。所有给定对象表的RangePartition是非重叠的,并且每行表示一些RangePartition。

下面是分区层使用的对象表。账户表存储了分配给戳的元数据和每个存储账户的配置。Blob表存储了所有账户的所有的Blob对象在戳中。实体表存储了所有账户的所有实体行在戳中;它用于公共windows Azure 表数据抽象。信息表存储所有的账户队列的所有信息在戳中。模式表跟踪所有对象表的模式。分区映射表跟踪所有对象表的当前RangePartitions以及服务于每个Rangepartition的分区服务。

每个上述的对象表都有一个固定的模式被存储在模式表中。Blob表,实体表和信息表的主键包含了三个部分:账户名,分区名,和对象名。这个特性提供了对这些对象表的索引和排序顺序。

5.1.1 被支持的数据类型和操作

对象表被支持的特性类型模式是标准的简单类型(bool,binary,string,DateTime,double,GUID,int32,int64)。此外系统支持俩个特殊类型字典类型(DictionaryType)和Blob类型(BlobType)。字典类型允许灵活的属性(没有固定的模式)添加到一行中。这些灵活的类型以(名称、类型、值)元素的形式存储在字典类型中。从数据访问的角度来看,这些灵活的特性表现得像是行的一级属性,可以和行中的其他属性一样进行查询。Blob类型是一个特殊的类型被用来存储大的数据量当前只会被Blob使用。Blob类型避免了存储blob带有行属性的blob数据位。相反,blob 数据位被存储在一个独立的“blob 数据流”中,指向 blob 数据位的指针(由“区段 + 偏移量,长度”指针组成的列表)则存储在行中 BlobType 属性中。这使得大数据位与存储在行数据流中的对象表的可查询行属性值进行分开。

对象表支持标准的操作包括在行上插入更新和删除以及查询/获取操作。此外,对象表允许跨行进行批处理PartitionName的值。单个批处理中的操作作为单个事务提交。最后对象表提供了快照隔离以允许读取操作和写入操作一起发生。

5.2 分区层架构

分区层有三个主要的架构组件如图4所示:分区管理者(PM),分区服务(PS)锁服务(Lock Service)。

分区管理者(PM) 负责跟踪和切分大型的对表为RangePartition并且指派每个RangePartition到分区服务以提供对象的访问。PM切分了对象表位N个RangePartition在每个戳中,追踪每个对象表的RangePartition崩溃以及分配给他们的分区服务器。PM讲这个分配存储在分区映射表。PM保证在任何的时候将每个RangePartition精确地分批给一个活动的分区服务。并且俩个RangePartition不会重叠。它还负责对分区服务之间的RangePartition进行负载均衡。每个戳有多个PM运行时实例,它们都争用存储在所服务中的leader锁(见下文)。带租约的PM是活动的PM控制分区层。

分区服务(PS) 一个分区服务负责处理分配它的一组RangePartitions请求。PS存储分区的所有的持久化状态到流中,并且为了效率维护分区状态的内存换成在内存中。系统保证,通过使用锁服务租约不会有俩个分区服务同时服务于相同的RangePartition。这允许PS为它所服务的RangePartition对象提供强一致性和并发事务排序。一个分区服务可以同时的服务于多个来自不同对象表的RangePartition。在我们的部署中,一个PS服务在任何时候都平均服务10个RangePartition。

锁服务 paxos锁服务被用来PM选主。此外,每个PS也维护了锁服务的租约以服务于分区。我们不深入PM选主的细节或者PS租约管理,因为概念和在Chubby Lock论文中描述的相同。

在分区服务故障时,故障PS的所有N和RangePartition都由PM分配给可用的PM。PM将会选择N(或者更少)分区服务,基于这些服务的负载。PM将会指派RangePartition到PS,然后更新分区映射表指定为每个RangePartition提供服务的分区服务器。这允许前端层通过查看分区映射表找到RangePartition的顺序。当PS获得新的分配时,它将开始为新的RangePartitions提供服务,直到PS持有其分区服务器租约为止。

5.3 RangePartition数据结构

PS通过维护一组内存中的数据结构和一组在流中的持久化数据结构服务于分区。

5.3.1持久化数据结构

RangePartition分区被用在LSM树以维护持久化数据。每个对象表的RangePartition包含了它自己在流层中的一组流,流只属于给定的RangePartition,尽管由于RangePartition分裂,不同的RangePartition中的多个流可以可以指向底层区段。下面是组成每个RangePartition的一组流。 (如图5所示)

元数据流主要负责存储该分区相关的元数据和操作指针

元数据流 元数据流是RangePartition的根流。PM通过提供RangePartition的名称的元数据流指派分区到PS。元数据流包含了PS足够的信息以负载RangePartition,包括提交日志流的名和RangePartition的数据流以及指针(extent+offset)到这些流中用于在这些流中开始操作(例如,提交日志流中开始处理和数据流索引的跟)。负责该RangePartition的分区服务器还会讲未完成分割状态写入元数据流中,以及区段分区可能涉及的合并操作。

提交日志流 是一个提交日志被用来存储最近的插入更新和删除操作,应用到RangePartition。因为最后的检查点是为RangePartition生成的。

行数据流 存储检查点行数据和RangePartition的index

Blob数据流 是一个只用来Blob表一以存储blob数据位

以上每一个都是对象表的RangePartition所拥有的流层中的一个独立流。

在 Blob 表中,一个 RangePartition 具有一个“行数据流”用于存储其行检查点数据(即 blob 索引),以及一个单独的“blob 数据流”用于存储前面提到的特殊 BlobType 的 blob 数据位。

5.3.2 内存中的数据结构

分区服务维护了如下的内存中的组件如图5所示

内存表 这个是内存版本的RangePartition提交日志,包含了所有的最近更新尚未被检查到行数据流的。当一个查询发生在内存表中,会检查以找到RangePartition 的最新更新

索引缓存 这个缓存存储了行数据流的检查点索引。我们将这个缓存从行数据缓存中分离出来,以确保我们保存了足够多的主索引缓存在内存中

行数据缓存 这是一个检查点行数据页的内存缓存。行数据缓存是只读的。当查询发生时,行数据缓存和内存表是被选中的,并有限检查内存表。

Bloom过滤器 如果数据没有在内存表或者行数据缓存中被发现,那么需要搜索数据流中的索引检查点。盲目的检查它们的代价是很高的。因此,为每个检查点保留一个bloom过滤器,它指示了访问的行是否在内存中。

我们不讨论组件的细节。

5.4 Data Flow

文中其他的数据流都是data stream,只有这里是 Data Flow

当PS接受到一个写入请求到写入请求到RangePartition,它追加操作到提交日志,然后添加新的改变行到内存表中。因此,所有对于分区的修改都会永久记录在提交日志中,也在内存表中反应。此时可以将成功返回给客户端(FE服务器)用于事务。当内存表的大小到达阈值大小或者提交日志的大小达到了阈值,分区服务将写入内存表的内容到检查点持久化的存储在RangePartition的行数据流中。相应的提交日志的部分可以被删除。为了控制RangePartition的检查点的总数,分区服务将会定期的合并检查点到更大的检查,并且通过垃圾收集删除老的检查点。

对于Blob表的RangePartition,我们也直接存储了Blob数据位到提交日志流(以最小化Blob操作流写入的数量),但是这些数据位不是行数据的一部分,所以他们不提交数据到内存表中。相反,行跟踪Blob类型属性跟踪Blob的数据位的位置(区段+偏移量,长度)。在检查点时,将从提交日志中删除的区段连接到RangePartition的Blob数据流。区段链接是流层提供的一个快速的操作,因为它只包含添加指针到Blob数据流结束的数据区段,而不需要拷贝任何的数据。

通过加载分区,PS可以开始为RangePartition提供服务。加载一个分区涉及读取RangePartition的元数据流,以定位活动的检查点集,并且回放提交日志中的事务,以重建内存状态。一旦这些完成了,PSPS有了RangePartition的最近视图可以服务于请求。

5.5RangePartition负载均衡

分区层的一个关键部分是分解大的对象表到RangePartition和自动负载均衡不同分区服务以应对多样的流量需求。

PM执行操作分解负载跨越分区服务兵控制戳中分区的总数。

负载均衡 这个操作定义了当给定PS有过多的流量和重新分配一个或多个RangePartition以减少分区服务的负载。

拆分 这个操作定义了当单个RangePartition有过多的负载拆分RangePartition到俩个或者更小的不连贯的RangePartition,然后负载均衡(重新分配他们在俩个或多个服务之间)

合并 这个操作合并了冷的或者轻负载 RangePartition,它们一起在对象表内形成一个连续的键。合并被用来保持RangePartition的数量。

WAS会将分区总数维持在低水位线和高水位线之间(通常大约是戳内分区服务器计数的10倍)。在平衡下,分区数量将会保持在低水位附近。如果有突发流量爆发集中在单个RangePartition,它将会切分以分散负载。当总RangePartition数接近高水位线,系统将会增加合并率,最终RangePartition数像低水位方向递减。因此每个OT的RangePartition数量是基于这些表中对象上的负载动态变化的。

RangePartition的高高水位是分区数量的十倍(存储戳有几百个分区服务)这是根据我们允许的流和区段元数据为SM增长的大小来选择的,并且仍然完全适合SM内存中的元数据。保留的RangePartitions比分区服务多得多,允许我们快速的分发故障的PS或机架的负载分布到许多其他PS上。一个给定的分区服务器可能最终服务于单个极度热点的分区,十几个轻加载的RangePartitions,或者俩者的混合,取决于当前的负载到戳中的RangePartitions。对于Blob表、实体表以及消息表而言,区段分区的数量取决于对于这些表中对象负载情况,并且基于流量在一个存储戳内持续发生变换。

对于每个戳,我们通常看到75个拆分和合并,每天以及200个RangePartition负载均衡。

5.1.1 负载均衡操作细节

我们跟踪RangePartition的负载以及每个PS的整体负载。对于俩者我们跟踪

  1. 传输、秒
  2. 平均处理事务数量
  3. 节流速率
  4. CPU录用率
  5. 网络录用率
  6. 请求延迟
  7. RangePartition的数据大小

PM维护了每个PS的心跳。在对心跳的响应中,这个信息被传回PM。如果PM基于metrics看到了RangePartition有过多的负载,那么他将会决定切分分区并且发送命令到PS以执行切分。如果相反,PS有过多的负载,但是没有RangePartition看起来有过高的负载,PM将会从PS中取出一个或多个RangePartition并且重新分配他们到更轻负载的PS。

我为了负载均衡RangePartition,PM发送卸载命令到PS,它将会在RangePartition卸载后写入当前的检查点。一旦完成PS响应回复PM这个卸载已经完成。PM然后指派RangePartition到新的PS并且更新分区映射表以指向新的PS。新的PS加载并开始为RangePartition提供流量服务。新PS上的新的PS加载并开始为RangePartition提供流量服务是非常快的,这是因为卸载之间的检查点导致提交日志很小。

5.5.2 切分操作

WAS拆分RangePartition是因为负载太大以及它的行或blob数据流的大小。如果PM发现了任意一种情况,他告诉PS服务的RangePartition以基于负载或大小进行切分。PM做出切分的决策,但是PS选择key(账户名,分区名)其中分区将会被切分。为了基于大小切分,RangePartition维护分区中的对象总数,以及分割键值,其中分区内的大小大约可以减半,PS使用它来选择分裂的位置。如果切分是基于负载的,PS选择key基于自适应区段剖析。PS自适应的跟踪RangePartition那个区域负载最大,并且使用它来确定用什么键来切分RangePartition。

为了将RangePartition(B)切分到俩个新的RangePartition(C,D),使用以下步骤

  1. PM命令PS以切分B到C和D
  2. 负责 B 的分区服务器对 B 进行检查点操作,然后在步骤3中短暂停止服务
  3. PS使用特殊的流操作“multiModify”以处理B的流(元数据,提交日志和数据)分别增加新C和D的一组流,这些流与B中流着相同的区段切顺序一致。PS然后追加新的分区键的部分附加到它们的元数据中。
  4. PS开始服务于到俩个新分区C和D的请求,查找他们各自不想交的区域。
  5. PS将拆分完成通知PM,并且PM相应的更新分区映射表和它的元数据信息。PM然后移动切分分区到不同的PS。

5.5.3 合并操作

为了合并俩个分区,PM将会选择俩个RangePartition C和D和有相同名字有低延迟的的临近的分区名区域。下面的步骤将C和D合并到一个新的RangePartition E中

  1. PM移动C和D,因此他们通过相同的PS进行服务。PM然后告诉PS合并C和D到E
  2. PS对C和D执行检查点,然后在步骤3中短暂的停止流量
  3. PS使用MultiModify流命令以创建新的E的提交日志和数据流。这些流的每个是连接所有的区段的链接在C和D中。这个合并意味着,E的新提交日志流中的盘区将会按照它们在 C 的提交日志流中的顺序包含 C 的所有盘区,随后按照其原始顺序包含 D 的所有盘区。对于E的新行和Blob数据流,这种布局是相同的。
  4. PS为E构造元数据流,该元数据流包含新的提交日志流和数据流的名称、E 的合并键范围,以及指向从 C 和 D 派生而来的 E 的提交日志中提交日志区域起始和末尾处的指针(盘区 + 偏移量)。以及数据流中数据索引的根。
  5. 在这个点,E的新的元数据流可以正确的加载,PS开始为新合并的RangePartitionE提供服务。
  6. PM之后更新分区映射表,以及它的元数据信息以反映合并。

5.6 分区层戳间复制

目前我们已经说了通过账户名关联到一个单独位置和存储戳,其中所有的数据访问都到那个戳。我们叫做账户的主戳。账户实际上有一个或者更多的从戳通过定位服务被指派,并且主从戳信息高速WAS已执行账户戳间复制,从主戳到从戳。

一个主要的戳间复制的尝尽是地理复制,在俩个数据中心之间用于容灾的账户数据。在这个场景中为账户选择了一个主和从戳的位置。举个例子, 我们希望主戳P位于美国南部,从戳位于美国北部。当配置账户是,LS会在每个位置选择一个戳记,并用这俩个戳注册账户名,因此南美戳P接受流量,北美戳S将只接受账户从戳P来的戳间复制(也叫地理复)流量。LS更新DNS以持有域名 AccountName.service.core.windows.netd指向存储戳P在南美的VIP。当账号的写入来到戳P,在流层使用intra-stamp复制,更改在戳中完全复制然后返回成功给客户端。在更新已经在戳P中被提交后,戳P的分区层将会使用戳间复制异步的地理复制修改到从戳S。当修改到达戳S,事务应用于分区层并且使用戳间复制在S中更新整个副本。

因此戳间复制是异步完成的,如果发生灾难,没有在戳间复制的最近更新可能会丢失。在生产环境中,在主戳上提交更新后,平均30秒内对次要戳的更改进行地理复制和提交。

戳间复制被用来账户地理复制和跨戳迁移。对于灾害回复,我们可能需要执行故障转移,其中最近的修改可能会丢失,但是对于迁移,我们执行感觉的故障转移因此没有数据丢失。在俩个故障恢复的场景,位置服务为账户创建一个活动的从戳,并将DNS切换到指向二级戳的VIP。注意,用于访问对象的URI在故障转移后不会更改。这允许使用现有的uri访问blob、table和在故障转移后继续工作的队列。

6 应用吞吐量

7 工作概要

8 设备选择和经验教训

9 相关工作

10 结论

Windows Azure存储平台实现了对于基于云方案的开发者来说必不可少的服务。强一致性、全局分区名称空间和灾难恢复的组合是WAS多租户环境中的重要客户特性。WAS运行一组不同的工作负载,同一组硬件上来自许多客户的各种峰值使用和配置文件。这大大降低了存储成本,因为要供应的资源数量远远少于在专用硬件上运行所有这些工作负载所需的峰值资源的总和。

如同我们样例中的展示, 三个存储抽象提供了Blob,Table,Queue为广泛的应用提供了存储机制和工作流控制。然而没有提到WAS系统的易用性。例如,在FaceBook和推特摄取引擎的初始版本,从开发到推出服务只有俩个月的时间。这个经理展示了我们服务的可用性以授权用户以开发和部署他们的应用程序到云。

Last modification:December 12, 2024
如果觉得我的文章对你有用,请随意赞赏