BigTable

摘要html

Bigtable是一个用于管理结构型数据的分布式存储系统,被设计为可扩展到很大规模:经过数以千台的机器存储PB级数据。不少Google的工程都将数据存储在Bigtable中,包括网页索引、Google Earth和Google金融。这些应用在数据量和延迟方面对Bigtable的需求很不相同。尽管这些不尽相同的需求,Bigtable可以成功的为这些Google的产品提供一个弹性的、高性能的解决方案。在这篇文章中,咱们描述Bigtable提供的简单的数据模型,它能给客户端在数据布局和格式上提供动态控制,并且咱们会描述Bigtable的设计和实现。web

1、        介绍算法

通过过去两年半的时间,咱们设计、实现并部署了一个分布式存储系统,用来管理Google的结构化数据,称为Bigtable。数据库

Bigtable被设计为一个可靠扩展到PB数据和数千台机器。Bigtable实现了几个目标:普遍应用、可扩展、高性能和高可用。目前Bigtable已被用于超过60个的Google产品和工程,包括Google分析、Google金融、Orkut、我的搜索、Writely和Google Earth。这些系统针对于各类不一样的需求使用了Bigtable,范围从面向吞吐量的批处理进程到时延敏感的面向终端用户的数据服务。这些产品使用的Bigtable集群跨越了不少的结构,从少数到数以千计的服务器,存储多至几百TB的数据。数组

在不少方面,Bigtable很像一个数据库:它实现了不少数据库的策略。并行数据库和内存数据库已经实现了可扩展和高性能,可是Bigtable与这些系统相比提供了不一样的接口。Bigtable不支持全关系型的数据模型;做为代替,它提供了一种简单的数据模型,在数据布局和格式上提供了动态控制,而且容许客户端推出在底层存储中数据的位置属性。数据经过行和列名进行索引,这些名字能够是任意的字符串。Bigtable也将数据当作是无解释的字符串,尽管客户端常常将结构化和半结构化的数据序列化成不一样的格式。客户端可以经过在它们的模式中精心的选择来控制他们的数据位置。最后,Bigtable模式参数可使客户端动态的控制是从内存中获取数据仍是从硬盘中。缓存

第二章描述了数据模型的更多细节,而且在第三章提供了客户端API进行了概述。第四章简要的描述了Bigtable使用到的Google基础项目。第五章描述了Bigtable实现的基本原理。第六章描述了几个用于提升Bigtable性能的优化。第七章测试了Bigtable的性能。咱们在第八章描述了几个如何使用Bigtable的例子,并在第九章讨论一些在设计和实现Bigtable中的教训。最后,在第十章描述相关工做,在第十一章进行了总结。服务器

2、        数据模型网络

一个Bigtable是一个稀疏的、分布式的、持久的多维排序映射(MAP)。这个映射(MAP)由行key、列key和时间戳进行索引,每一个映射值都是一个连续的byte数组。数据结构

(row:string, column:string,time:int64)  à  string架构

 

图1:存储Web页面的样例表中的一部分。行名是一个翻转了的URL。Contents列族包含了页面内容,anchor列族包含了涉及页面中的全部anchor的文本。CNN主页被Sports Illustrated和My-look主页引用,因此,本行包含了名为anchor:cnnsi.com和anchor:my.look.ca的列。每一个anchor单元都有一个版本;contents列在根据时间戳t3,t5和t6有三个对应的版本。

在调查了一个相似Bigtable系统的各类潜在用途以后,咱们选定了这个数据模型。做为驱动咱们对设计作出一些决定的一个具体的例子,假设咱们想要保留一个包含大量网页的集合和用于不少不一样项目相关信息的拷贝,咱们将这个特殊的表称为Webtable。在Webtable中,咱们使用URL做为row key,页面的不一样属性做为column names,并将页面的内容存储在contents:如图1所示,它们被获取时的时间戳将做为存放它们的列的列关键字。

2.1 行(Rows)

在表中,行的key是任意字符串(目前最大为64KB,尽管用户大多数只使用10-100字节)。每次在一行中读或写数据都是一个原子操做(尽管一行中不一样列正在进行读或写),一个设计决定使客户端更加方便的推导出在并发更新相同行的系统行为。

Bigtable以row key的字典序保存数据。一个表的行范围是动态分配的。每一个行范围被称为一个tablet,它是分布式和负载平衡的单位。所以,小范围的读取是高效的,只须要少许机器的通讯。客户端能够经过选择合适的row keys来利用这个属性,这样能够为他们的数据访问提供良好的局域性。例如,在Webtable中,相同域名下的页面经过反转URL中的hostname,被集中存放到连续的行中。例如,咱们将maps.google.com/index.html存放在关键字com.google.maps/index.html下。将相同域名的网页存储在一块儿能够更加高效对一些主机和域名进行分析。

2.2 列族(Column Families)

列关键字(Column keys)被聚合到一个名为列族的集合中,它造成了访问控制的基础单元。存储在一个列族中的全部数据一般有相同的类型(咱们将在一个列族下的数据压缩到一块儿)。必须先建立列族,而后才能将数据存储到其下面的列;在列族建立好以后,列族中的任意一个列关键字(column key)均可用。咱们的目的是每一个表中不一样的列族数量较小(最多不会多于几百个),而且列族不多在操做中变化。相反的,一个表能够有无限数量的列。

一个列关键字(column key)使用下面的语法进行命名:family:qualify。列族名必须是可显示的,可是qualify能够是任意字符。例如,Webtable的列族名是language,用于存储页面所用到的语言。咱们在language列族中只使用一个列关键字(column key),它存储每一个页面的languageID。另一个有用的列族是anchor;这个族中的每一个列关键字(column key)表明一个单独的anchor,如图1所示。qualify(限定词)是引用站点;该单元的内容是连接文本。

访问控制,以及磁盘和内存的统计信息都是在列族层面上进行的。在咱们的Webtable例子中,这些控制容许咱们管理几个不一样的应用类型:一些添加新的基础数据,一些读取基础数据并建立派生列族,一些只容许查看已存在的数据(甚至出于隐私考虑也不能查看全部已存在的列族)。

2.3 时间戳(Timestamps)

Bigtable中的每一个单元都可以包含相同数据的多个版本;这些版本由时间戳进行索引。Bigtable时间戳是一个64bit的整数。它们可以由Bigtable分配,在这种状况下,它们表现成以毫秒为单位的当前时间,或者显式的由客户端应用指定。应用必须保证时间戳的惟一性。不一样版本的单元以时间戳的降序进行排列,这样可使最近的版本最先被读取。

为了减小管理不一样版本数据的工做量,咱们支持两个列族设置,它们可使Bigtable自动的垃圾回收单元中的版本。客户端能够指定保留最近的n个版本,也能够指定只保留new-enouge内的版本(如:只保留最近7天写入的数据)。

在咱们的Webtable例子中,咱们将抓取页面的时间戳存放在contents中:页面被抓取的时间就是所对应的时间戳。上面所提到的垃圾回收机制使咱们只保留一个页面最近的三个版本。

3、        API

Bigtable API 提供了建立、删除表和族列的方法。它也提供了更改集群、表和列族元数据(如访问控制权限)的方法。

 

图2:向Bigtable写数据

客户端应用可以写或删除Bigtable中的值,从单独的行中查询值,或者循环访问一个表中的一部分数据。图2显示了使用一个RowMutation抽象执行的一系列更新操做的C++代码(为了使例子较短,去掉了不相干的代码)。Apply对Webtable执行了一个原子操做:它增长了另外一个连接到www.cnn.com的anchor,并删除了一个不一样的anchor。

 

图3:从Bigtable中读数据

图3显示了使用Scanner抽象方法遍历一个指定行的全部anchors的C++代码。客户端能够遍历多个列族,而且有一些对行、列和时间戳的浏览的限制机制。好比,咱们能限制只浏览列能够正则匹配anchor:*.cnn.com列的anchors,或者是时间戳在距离当前时间10天以内的anchors。

Bigtable支持另外的一些功能,可使用户经过更加复杂的方法操做数据。第一,Bigtable提供了单行事务,它可以用于在一个行关键字(row key)的数据上执行原子的读、改、写操做。Bigtable目前不支持通常的跨行关键字的事务处理,尽管在客户端提供了一个跨行关键字批写入的接口。第二,Bigtable容许单元用于整数计数。最后,Bigtable支持在服务器地址空间中执行客户端提供的脚本。这些脚本使用Google用于数据处理的语言Sawzall编写。同时,咱们基于Sawzall的API不容许客户端脚本写回Bigtable,可是它容许不一样形式的数据转换,基于任意表达式的过滤,以及使用各类操做的进行数据汇总。

Bigtable可以与MapReduce一块儿使用,后者是一个Google用于运行大规模并行计算的框架。咱们已经写了一系列包装器,使Bigtable既能用于MapReduce的输入源,也能用于它的输出目标。

4、        构件

Bigtable创建在几个其它的Google基础构件之上。Bigtable使用Google文件系统GFS存储log和数据文件。一个Bigtable集群一般工做在一个共享的机器池中,运行各类的其它分布式应用,Bigtable进程与其它进程共享同一台机器。Bigtable依靠集群管理系统来分配任务、管理共享系统的资源、处理机器故障和监视机器状态。

Google的SSTable文件格式用于内部存储Bigtable数据。SSTable提供了一个持久的、排序不变的key-value映射,其中key和value均可以是任意字符串。提供了根据一个指定的key查找value的操做,以及遍历指定key的一个范围内全部的key-value对。内部地,每一个SSTable包含了一系列的块(通常状况下,每一个块的大小为64KB,可是这是可配置的)。一个块索引(存储在SSTable的末尾)用来定位块,这个索引会在SSTable打开时载入到内存中。一个查询能够执行一次独立的磁盘查询:咱们首先经过在内存索引中的二叉查找找到适当的块,而后从磁盘中将适当的块读取出来。一样地,一个SSTable可以完整的被映射到内存中,这样可让咱们在不接触磁盘的状况下来执行查询和浏览。

Bigtable依赖一个高可用的、持久的称为Chubby的分布式锁服务。一个Chubby服务由5个活跃的副本组成,其中一个被选举为master,并处理请求。当大多数副本正常运行,而且可以相互通讯时,这个服务是正常的。Chubby使用Paxos算法来保证它的副本在面对故障时的一致性。Chubby提供一个由目录和少许文件组成的命名空间。每一个目录或者文件都能被用作一个锁,而且读和写文件都是原子的。Chubby客户端库提供了一致的Chubby文件缓存。每一个Chubby客户端维护一个Chubby服务的会话。若是一个客户端会话在租约到期后不能更新它的会话租约时,则就会过时。当一个客户端会话过时时,它将释放全部的锁和打开的句柄。Chubby客户端可以在Chubby文件和目录下注册回调函数,用于处理变化通知或会话过时。

Bigtable将Chubby用于各类工做:确保在任什么时候刻最多存在一个活跃的master;存储Bigtable数据的引导位置;发现tablet服务器和处理失败的tablet服务器;存储Bigtable概要信息(每一个表的列族信息);存储访问控制列表。若是Chubby超过一段时间不可用,则Bigtable会变为不可用。咱们最近在一个有11个Chubby实例的14个Bigtable集群上测试了效果。因为Chubby不可用(由Chubby中断或者是网络问题形成)而形成存储在Bigtable中的数据不可用的时间占Bigtable服务器时间的平均百分比为0.0047%。在单独的集群中受到Chubby不可用影响的最大百分比为0.0326%。

5、        实现

Bigtable实现由三个主要的组成部分:一个链接到每一个客户端的库、一个master服务器和许多tablet服务器。Tablet服务器可以从集群中动态的增长(或删除)以适应工做量的变化。

Master负责将tablet分配到tablet服务器上,探测增长和超时的tablet服务器,平衡tablet服务器的负载,以及在GFS上回收垃圾文件。此外,它会处理概要变化,如表和列族的建立。

每一个tablet服务器管理一个tablet集合(通常状况下在一个tablet服务器上咱们有10到1000个tablets)。Tablet服务器处理它负载的tablet相关的读和写请求,并在tablets过大后进行分片。

像许多单master分布式存储系统同样,客户端数据不能经过master进行传输:客户端直接与tablet服务器进行读和写的通讯。因为Bigtable客户端不依赖master获取tablet位置信息,大多数客户端不须要跟master进行通讯。所以,在实际中master的负载很轻。

一个Bigtable集群存储了大量的表。每一个表有一系列tablet组成,每一个tablet包含了一个行范围内的全部数据。最初,每一个表都仅仅由一个tablet组成。随着表的增加,它会自动的分片成多个tablets,默认状况下,每一个tablet的大小大约在100-200MB。

5.1 Tablet定位

咱们使用三层的相似于B+树的结构存储tablet位置信息(图4)。

 

图4:Tablet位置等级

第一层是存储在Chubby的一个文件,它包含了root tablet的位置。Root tablet将全部tablet的位置包含在一个特殊的METADATA表中。每一个METADATA tablet包含一系列的用户tablet位置。Root tablet只是METADATA表的第一个tablet,可是特殊之处在于其永远不会分裂,以此确保tablet位置层级不会超过3层。

在METADATA表中,每一个tablet的位置信息都存放在一个行关键字下面,而这个关键字是由tablet所在的表的标示符和它的最后一行编码造成的。每一个METADATA行在内存中存储了大约1KB的数据。经过限制tablets的大小为128MB,三层定位方案能够知足定位2^34个tablets(或者是2^61字节,按着每一个tablet有128MB数据)。

客户端库会缓存tablet位置。若是客户端不知道一个tablet的位置,或者它发现缓存的位置信息不正确,则它会递归查询tablet的位置信息。若是客户端的缓存是空的,定位算法须要3次网络交互更新数据,包括一次Chubby文件的读取。若是客户端缓存过期,则定位算法须要6次网络交互才能更新数据,由于过期的客户端缓存条目只有在没有查到数据的时候才能发现数据过时(假设METADATA tablets移动的不频繁)。尽管tablet的位置信息存在内存中,不须要访问GFS,可是咱们会经过客户端库预取tablet位置的方式来减小这种消耗:不管什么时候读取METADATA表都读取不止一个的METADATA tablets。

咱们在METADATA表中还存储了次要的信息,包含与每一个tablet有关的全部事件(如:何时一个服务器开始为该tablet提供服务)。这些信息对debugging和性能分析颇有帮助。

5.2 Tablet分配

每一个tablet只能分配给一个tablet服务器。Master记录了正常运行的tablet服务器、tablet服务器当前的tablet任务和没有被分配的tablet。当一个tablet没有被分配,而且有tablet服务器对于这个tablet有足够的空间可用时,master会经过向这个tablet服务器发送一个tablet载入请求分配这个tablet。

Bigtable使用Chubby跟踪tablet服务器的状态。当一个tablet服务器启动时,它在指定的Chubby目录下建立一个命名惟一的文件并获取一个互斥锁。Master监控这个目录(服务器目录)来检测tablet服务器。若是一个tablet服务器丢失了它的互斥锁,则中止它的tablet服务:例如,因为网络终端,形成了服务器丢失了它的Chubby会话。(Chubby提供了一个高效的机制使tablet服务器在不引入网络流量的状况下,可以检测它是否仍然持有它的锁。)只要文件依然存在,一个tablet服务器试图再次在这个文件上获取一个互斥锁。若是文件不存在了,则tablet服务器将不能再次提供服务,进而会kill掉本身。一个tablet服务器不管什么时候结束,它都会试图释放它的锁,以使master可以更加快速的从新分配它上面的tablets。

Master负责检测一个tablet服务器什么时候不能继续为它的tablets服务,并尽快将这些tablets从新分配。Master经过周期性的询问每一个tablet服务器的状态来检测一个tablet服务器什么时候不能继续工做。若是一个tablet服务器报告它失去了它的锁,或者若是master在最近的几回尝试都不能到达一个服务器,则master会尝试获取这个服务器文件的互斥锁。若是master可以获取这个锁,则Chubby运行正常, tablet要么是宕机了,要么就是不能与Chubby正常通讯了,所以master经过删除这个tablet服务器的服务器文件来确保这个服务器不能再次进行服务。一旦一个服务器文件被删除,master将以前分配到这个tablet服务器上的全部tablets移动到未被分配的tablets集合里面。为了确保Bigtable集群不易受到master和Chubby之间的网络问题的影响,master将会在它的Chubby会话超时后kill掉本身。然而,如上所说,master失败不会改变tablet服务器上的tablet分布。

当一个master被集群管理系统启动时,它须要在改变tablet分布以前先发现当前的分布。Master在启动时执行下面的步骤。

(1)            master从Chubby中抢占一个惟一的master锁,用来阻止其它的master实例化。

(2)            master扫描Chubby中的服务器目录,来查找哪些服务器正在运行。

(3)            master与每一个正常运行的tablet服务器通讯,获取每一个tablet服务器上tablet的分配信息。

(4)            master扫描METADATA表获取全部的tablets的集合。在扫描的过程当中,若是遇到一个tablet没有被分配,则将其放入到未被分配的tablets集合中,并能够进行分配。

一种复杂的状况是,在METADATA tablets被分配以前,不能扫描METADATA表。所以在开始扫描以前(第4步),若是在第三步发现root tablet没有分配,则master将root tablet加入到未被分配的tablet集合中。这个附加的操做确保了root tablet将会被分配。由于root tablet包含了全部的METADATA tablets的名字,因此在扫描完root tablet以后,master会获得全部METADATA tablet的名字。

已存在的tablet集合只有在建立或删除表、两个已存在的tablet合并成一个更大的tablet,或者一个已存在的tablet分裂成两个较小的tablet时才会改变。Master会记录全部的这些变化,由于上面几种状况除了最后一个都是它发起的。tablet分裂是比较特殊的,由于它是由tablet服务器发起的。Tablet服务器为METADATA表中记录新tablet的信息提交此次分裂操做。当分裂操做提交后,它会通知master。若是分裂通知丢失(由于tablet服务器或者master宕机),master在询问一个tablet器载入那个分裂的tablet时会检测到新的tablet。

5.3 Tablet服务

 

图5:Tablet表述

一个tablet的持久状态存储在GFS中,如图5中的描述。更新操做提交到REDO日志中。在这些更新中,最近提交的那些操做存储在一块名为memtable的有序缓存中;较老的更新存放在一系列的SSTable中。为了恢复这个tablet,一个tablet服务器会从METADATA表中读取它的元数据(metadata),这个元数据包含了组成这个tablet的SSTable的列表,以及一系列redo点,这些点指向可能含有该Tablet数据已提交的日志记录。服务器将SSTable的索引读入到内存,并经过执行从redo点开始的全部已提交的更新操做重构memtable。

当一个写操做到达一个tablet服务器时,服务器检查其是否符合语法要求,并验证发送者是否有权限执行这个操做。验证是经过读取一个Chubby文件(这个文件几乎会存在客户端的缓存中)中的可写用户的列表完成的。一个有效的写操做会写入到操做日志中。批处理方式能够提升大量细小操做的吞吐量。在写操做提交后,它的内容被写入到memtable中。

当一个读操做到达一个tablet服务器时,一样会检查是否符合语法要求和自己的权限。一个有效的读操做会在一系列SSTable和memtable合并视图上执行。因为SSTable和memtable是按字典序排序的数据,因此可以高效的生成合并视图。

当tables进行分裂和合并时,进来的读和写操做可以继续执行。

5.4 合并压缩

随着写操做的执行,memtable的大小会增长。当memtable的大小达到一个门限值时,这个memtable会被冻结,建立一个新的memtable,并将冻结的memtable转换成一个SSTable写入到GFS中。这里的次压缩(minor compaction)过程有两个目标:减小tablet服务器的内存使用,减小操做日志中在恢复tablet服务器时须要读取的数据总量。当压缩发生时,进来的读和写操做可以继续执行。

每次次压缩(minor compaction)都会建立一个新的SSTable。若是这种行为不停的进行下去,则读操做可能须要合并来自任意数量的SSTable的更新。不然,咱们经过在后台周期性的执行合并压缩来限制这些文件的数量。一个合并压缩读取一些SSTable和memtable中的内容,并写入到一个新的SSTable中。输入SSTable和memtable能够在压缩完成后当即丢弃。

一个将全部SSTables写入到一个SSTable中的合并压缩称为主压缩(major compaction)。非主压缩产生的SSTable可以包含特定的删除条目,它阻止在仍然活着的旧SSTable中删除数据。另外一方面,主压缩产生的SSTable不会包含删除信息或已删除的数据。Bigtable循环扫描全部的tablets,并按期的对它们执行主压缩。这些主压缩能够回收删除数据所使用的资源,并尽快的确保删除的数据在系统内完全消失,对于存储的敏感数据,这是十分重要的。

6、        优化

在前面章节所描述的实现须要一些优化来知足咱们用户的高性能、高可用和高可靠性需求。这一章经过对这些实现更加细节的描述来强调这些优化。

局域性群组

客户端能将多个列族汇集成一个局域性群组。对Tablet中每一个局域性群组都会产生一个单独的SSTable。将一般不会被一块儿访问的列族分隔成不一样的局域性群组可以提升读取效率。例如,Webtable中的页面元数据(如语言和校验和)做为一个局域性群组,而页面的内容做为另一个局域性群组:一个想要读取元数据的应用不须要读取全部的页面内容。

此外,能够针对于每一个局域性群组设定一些有用的调整参数。例如,一个局域性群组能够声明为存储在内存中。Tablet服务器采用惰性加载的策略对设定为内存中存储的局域性群组的SSTable进行内存加载。一旦加载过,则属于这些局域性群组的列族可以直接被读取,而不须要访问硬盘。这个功能对一些常常访问的小片数据颇有用:在内部,咱们使用它存放METADATA表中的位置信息列族。

压缩

客户端可以控制做为局域性群组的SSTable是否被压缩,若是压缩,选定什么样的压缩格式。每一个SSTable块(它的大小可以经过局域性群组特定的调整参数来控制)都会按着用户指定的格式进行压缩。尽管因为对每一个块分开压缩而浪费了一些空间,可是咱们可以受益于在不须要解压整个文件的状况下可以访问部分SSTable。许多客户端使用两遍定制的压缩方式。第一遍使用了Bentley和Mcllroy方式,它经过在一个很大的窗口中对常见的长字符串进行压缩;第二遍使用了一种快速压缩算法,它在16KB大小的窗口内查找重复的数据。两种压缩都很快,在当前的机器上,它们压缩速度为100MB-200MB/s,解压速度为400-1000MB/s。

虽然在选择压缩算法时咱们更看重速度而不是减小的空间,可是这种两遍的压缩方式对空间上的压缩也出奇的好。例如,在Webtable中,咱们使用了这种压缩方式存储web页面内容。在一个实验中,咱们在一个局域性群组中存储大量的文档。针对这个实验的目的,咱们没有存储文档的全部版本,而是只存了一份,这种方式实现了10:1的空间压缩率。这比对HTML页面的压缩率为3:1或4:1的经常使用的Gzip压缩更好,缘由在于Webtable存放行的方式:一个域名下的全部页面会存储在临近的地方。这使Bentley-Mcllroy算法可以将相同域名下的大量页面的共同部分识别出来。不少应用,不仅是Webtable,选择他们行名以致于全部类似的数据汇集到一块儿,所以实现了很好的压缩率。当咱们在Bigtable中存储相同值的多个版本时,压缩率会更好。

读操做的缓存

为了读操做的性能,tablet服务器使用双层缓存。扫描缓存是高层缓存,它缓存了tablet服务器代码使用SSTable接口获取的key-value对;块缓存是底层缓存,它缓存了从GFS上读取的SSTables块。扫描缓存主要用于倾向重复访问相同数据的应用。块缓存主要用于倾向读取近期数据附近数据的应用(如:顺序读取或随机读取同一个局域性群组的一个频繁访问行的不一样列)。

Bloom过滤器

如5.3中描述的,一个读操做必须从全部的组成tablet的SSTable中读取数据。若是这些SSTable没有在内存中,则咱们最终会屡次访问硬盘。咱们经过容许客户端对特定局域性群组的SSTable指定Bloom过滤器来下降访问次数。一个Bloom过滤器容许咱们查询一个SSTable是否含有特定的行/列对的数据。对于某些特定应用,虽然存储Bloom过滤器占用了tablet服务器少许的内存,但可以完全的减小读操做对磁盘的查询次数。咱们使用Bloom过滤器也能够隐式的达到了当查询的行和列不存在时,不须要访问磁盘。

操做日志实现

若是咱们为每一个tablet保存一份单独的日志,那么我将会在GFS中并发的写大量的文件。取决于每一个GFS服务器的底层系统实现,这些写操做会引发大量的磁盘查找,用来将数据写入到不一样的物理日志文件中。此外,因为批操做一般较小,每一个tablet分开保存日志文件会影响批操做所带来的优化。针对这些问题,对于每一个tablet服务器咱们将修改追加到一份操做日志中,不一样的tablet修改混合存储在一个物理日志文件中。

在进行通常的操做时,使用一个日志能够提供很好的性能,可是会使恢复复杂化。当一个tablet服务器宕机,其上的tablets会被移动到大量其它的tablet服务器上:每一个tablet服务器一般只负载原始tablet服务器上的一小部分tablets。为了恢复一个tablet的状态,新的tablet服务器须要从新执行原始tablet服务器上操做日志中针对这个tablet的修改。然而,这些tablets的修改混合存在同一个物理日志文件中。一种方式是每一个新tablet的服务器会读取整个操做日志文件,而后只执行对于须要恢复的tablet的修改。然而,在这种方式下,若是100台机器每一个都从失败的tablet服务器上分配了一个tablet,那么日志文件将被读取100次。

咱们经过对日志文件条目以key<table, row name, log sequence number>进行排序,来避免这种重复的文件读取。在排序输出中,对于一个指定的tablet的全部修改将会是连续的,并所以可以经过一次硬盘查询和顺序读取进行高效的操做。为了并行排序,咱们先将日志文件分红64MB大小的片断,在不一样的tablet服务器上对每一个片断并行的进行排序。这个排序过程由master协同处理,并在当一个tablet服务器声明它须要从一些操做日志中恢复修改时启动。

向GFS中写日志文件有时会因为各类缘由引发性能波动(如:写操做进行时,一个GFS服务器宕机了,或者链接三个GFS副本服务器所在的网络发生拥塞或过载)。为了确保在GFS高负载时修改可以正常进行,每一个tablet服务器实际有两个写日志线程,每一个线程写本身的日志文件,同一时刻,两个线程中只有一个是工做的。若是一个写日志的线程效率不好,则会切换到另外一个线程,修改操做的日志就会写在这个线程下的日志文件中。每一个日志记录都有一个序列号,以此使tablet服务器在恢复时忽略掉线程切换所产生的重复的条目。

Tablet恢复提速

若是master将一个tablet从一个tablet服务器移动到另外一个服务器,源tablet服务器会在本地先进行一个次压缩。这个压缩经过减小了tablet服务器日志中没有归并的记录的数量来缩短恢复时间。压缩完成后,tablet服务器中止对该tablet的服务。在卸载tablet以前,源服务器还会再作一次次压缩(一般很快),以消除第一次次压缩过程当中新进入的未归并到SSTable中的修改。在此次次压缩完成后,tablet可以被载入到另外一个tablet服务器上,而无需经过任何的日志条目恢复。

利用不变性

除了SSTable缓存,实际中咱们产生的其它部分的SSTable都是不变的,咱们能够经过这个事实来简化Bigtable系统。例如,当从SSTable读取数据时,咱们不须要对文件系统访问操做进行任何同步。这样,就能够很是高效的实现对行的并行操做。Memtable是惟一一个能被读和写操做同时访问的可变数据结构。为了减小在读操做时的竞争,咱们对内存采用了copy-on-write机制,这样就容许读写操做同时进行了。

由于SSTable是不可修改的,因此咱们将永久移除要删除的数据问题转换为对废弃的SSTable进行垃圾回收的问题。Tablet的每一个SSTable都在METADATA表中注册过。Master经过标记-删除的方式移除SSTable集合中废弃的SSTable,METADATA表中包含了ROOT集合。

最后,SSTable的不可变性使咱们可以快速的分裂tablet。咱们让子tablet(分裂后的tablet)共享父tablet(分裂前的tablet)的SSTables,而不是为每一个子tablet建立新的SSTable集合。

7、        性能评估

咱们创建一个有N个tablet服务器的Bigtable集群,用于测试Bigtable的性能和可扩展性,其中N是能够变化的。每一个Tablet服务器配置为1G的RAM,并向一个由1786台有两块400GB IDE硬盘的机器组成的一个GFS单元上写数据。N个客户端机器为这个测试生成Bigtable负载。(咱们使用与tablet服务器相同数量的客户端,以保证客户端不会形成瓶颈。)每一个服务器配置双核Opteron 2GHz处理器,足够容纳全部进程工做数据集所需的内存,一个Gb以太网卡。这些机器排列在两层树结构的交互网络中,在根节点出的带宽大约有100-200Gbps。全部的机器都在同一个虚拟环境中,这样可使任意两台机器间的往返时间都在1ms之内。

Tablet服务器和master服务器、测试客户端,以及GFS服务器都运行在同一个机器集合中。全部的机器都运行一个GFS服务,其中一些机器同时还能够再运行一个GFS服务,或者是客户端进程,或者是其它的工做进程。

R是测试中Bigtable不一样行关键字的具体数量。R的选取保证了每次对每一个tablet服务器的基准读或写数据量都大概在1GB左右。

在序列写的基准测试中,使用了0 ~ R-1做为行关键字。这些行关键字能够被分红10N个大小相同的区间。这些区间经过一个中心调度者被分配到N个客户端,这个中心调度者可以快速的将下一个可用的区间分配到一个处理完以前分配过来的区间的客户端上。动态分配有助于缓解因为其余进程引发的性能变化所形成的影响。咱们在每一个行关键字下写一个单独的字符串。每一个字符串都是随机生成的,所以不可被压缩。此外,不一样行关键字下的字符串是不一样的,因此也就没有可能跨越行进行压缩。随机写基准测试是相似的,除了行关键字是在写入前经过模R散列出来的。这样保证了在整个基准测试过程当中,写操做的负载均匀的分布在整个行空间中。

序列读基准测试使用的生成行关键字的方法与序列写的基准测试的基本相同,但它是从行关键字下读取字符串(以前在序列写基准测试中已经写入),而不是写入。相似的,随机读基准测试与随机写基准测试类似。

扫描基准测试与序列读基准测试相似,可是使用了Bigtable API所提供的接口来扫描一个行区间的全部数据。因为一次RPC就可以从tablet得到大量的数据,因此使用扫描操做减小了基准测试RPCs的执行次数。

随机读(内存)基准测试与随机读基准测试相似,但包含基准测试数据的局域性群组被标识为in-memory,所以,这些读操做可以直接从内存中进行读取,而不须要访问GFS。对于这个基准测试,咱们将每一个tablet服务器的数据量从1GB减小到100MB,以保证这些tablet服务器可以将全部数据加载到内存中。

 

图6:每秒读/写1000字节的数据。上表显示了每一个tablet服务器的速率;上图显示了全部tablet服务器的总速率。

图6显示了当咱们向Bigtable读写1000字节的基准测试性能的两个视图。表中显示了每一个tablet服务器的操做数量,图中显示了每秒中操做的数量总和。

单Tablet服务器性能

让咱们先考虑单个tablet服务器的性能。随机读比其它的操做要慢一个数量级以上。每一个随机读包含了从GFS到一个tablet服务器的一个64KB大小的SSTable块的网络传输,其中只有1000字节是被用到的。一个tablet服务器每秒执行大约1200次的读操做,也就是从GFS上每秒读取75MB的数据。因为网络栈的开销、SSTable的解析和Bigtable代码的执行,这个带宽使tablet服务器的CPUs接近饱和,而且咱们系统中使用的网络链路也几乎被占满。大多数采用这种访问方式的Bigtable应用会把块大小设置为一个较小的值,一般状况下为8KB。

从内存中随机读会快不少,由于每一个1000字节的数据都是从tablet服务器本地内存中获取的,而不须要去从GFS上获取64KB的大数据块。

随机写和序列写比随机读表现的好一些,由于每一个tablet服务器将全部进来的写操做追加到一个单独的操做日志中,而且使用批提交的方式,将数据以流的形式高效的写入到GFS。随机写和序列写在性能上没有明显的区别,全部的tablet服务器写操做都是将记录追加到同一个操做日志中。

序列读比随机读的性能要好,由于每一个从GFS读取到的64KB的SSTable块都放入了块缓存中,咱们可使用它完成接下来的64个读请求。

扫描的速度更快,由于tablet服务器对于一个单独的客户端RPC能够返回大量的数据,所以RPC的消耗基本能够抵消了。

缩放规模

随着咱们将系统中的tablet服务器的数量从1增长到500,总吞吐量有着戏剧般的增加,有超过100倍的增加。举个例子,随着机器数量增长了500倍,从内存中随机读的性能同时提升了300倍。之因此是这种现象,是由于这个基准测试中,单个服务器的CPU达到了瓶颈。

然而,性能不会线性增加。对于大多数基准测试,随着tablet服务器数量从1增长到50,每台tablet服务器的吞吐量有着明显的降低。这是因为多台服务器配置负载不均衡形成的,主要是由于其余进程抢占CPU和网络。咱们使用均衡算法来尽可能解决这个问题,可是因为两个缘由,这个算法不能完美的工做:一个是为了减小tablet的移动会压制从新均衡(一个tablet在移动时会短暂的不可用,一般会小于1秒);另外一个是基准测试进程产生的负载会有波动。

随机读基准测试显示了最坏的比例(随着服务器数量增长了500倍,总吞吐量只增长了100倍)。形成这种状况的缘由(如上说解释的)就是为了1000字节的数据咱们须要在网络上传输64KB的大块数据,这种传输会使1G的链路饱和,所以每一个服务器吞吐量随着机器的增长降低的很明显。

8、        真实应用

 

表1:Bigtable机群中的tablet服务器分布

截止到2006年8月,已经有388个未经测试的Bigtable集群运行在各类Google的机群上了,总共大概有24500个tablet服务器。表1中显示了每一个集群中tablet服务器的粗略分布。这些集群中的不少都用于开发产品,所以会有一段时间比较空闲。观察一组由14个繁忙的集群、8069个tablet服务器集群组,咱们看到总体的流量是大约每秒1200万个请求,其中包括接收到的大约741MB/s的RPC流量,以及发送出去的大约1GB/s的RPC流量。

 

表2:一些产品使用的表的特色。表大小(压缩前)和#Cell指出近似的大小。在表中禁用压缩的产品对应的没有给出压缩率。

表2提供了当期正在使用的一些表的数据。一些表存储了与用户相关的数据,而另外一些存储了用于批处理的数据;表的大小、平均的单元大小、从内存中读取数据的比例和表概要的复杂度都有很大的区别。在本章下面的步骤中,咱们简要的描述三个产品如何使用Bigtable。

8.1 Google分析

Google分析是用来帮助Web站点管理者分析他们网站的流量模式的服务。它提供了汇总统计,如天天独立访问的用户量和每一个URL天天的访问量,它还提供了用户使用网站行为的报告,如根据以前访问的特殊页面,统计出有几成用户购买了东西。

为了开启这个服务,web站点管理员须要在他们的web页面中嵌入一小段Javascript程序。这个程序在页面被访问时调用。它记录了Google分析须要的各类信息,如一个用户的标识和获取的页面的信息。Google分析总结这些数据,提供给站点管理员有用的信息。

咱们简单描述Google分析使用的两个表。Raw click表(大约200TB)每一行保存着一个终端用户的会话信息。行名是一个由网站的名字和会话信息创建的时间组成的元祖。这种模式保证了访问同一个网站的会话信息是相邻的,而且是按着时间顺序排列的。这个表压缩成原始大小的14%。

Summary表(大约20TB)保存着每一个站点的预约义的各类汇总信息。这个表的数据是由周期性的调用MapReduce进程处理raw click表中的数据产生的,每一个MapReduce进程从raw click表中获取最新的数据。整个系统的吞吐量是受限于GFS的吞吐量的。这个表能压缩到原始大小的29%。

8.2 Google Earth

Google提供了一组的服务,用于为用户提供高分辨率的地球表面卫星图像,能够经过网页版的Google地图接口进行访问,也能够经过Google Earth的客户端进行访问。这些产品容许用户浏览地球表面:他们能在不一样的分辨率下平移、查看和标注卫星图像。这个系统使用一个表存储预处理的数据,用另外的一些表存储用户的数据。

预处理管道使用了一个表存储原始图像。在预处理过程当中,图像被清除并被固化到最终的服务数据中。这个表包含了大约70TB的数据,所以存储在磁盘中。图像数据已是高效压缩过的了,因此禁用了Bigtable的压缩功能。

图像表中的每一行都与一个地理分片相关。行名须要命名为可使相邻地理分片可以存放在相连的位置。表包含了一个列族用于记录每一个分片的原始数据。这个列族有不少列:基本上每列对应一个原始图片的数据。因为每一个分片只是由一些图片组成的,因此这个列族是十分稀疏的。

预处理严重依赖于运行在Bigtable上MapReduce的数据传输。在这些MapReduce工做期间,整个系统中每一个tablet服务器每秒处理1MB的数据。

这个服务系统使用了一个表来索引存在GFS中的数据。这个表相对较小(大约500GB)可是对于每一个数据中心,它必须低时延的每秒处理数以万计的请求。所以这个表保存在数百个tablet服务器上,而且存储在in-memory的列族中。

8.3 个性化搜索

个性化搜索是一个双向的服务,它记录用户的请求和点击,涉及到各类Google的服务,如网页搜索、图片和新闻。用户可以浏览他们的搜索记录,来查看他们以前的请求和点击,而且他们可以定制基于Google历史使用习惯模式的个性化搜索。

个性化搜索将每一个用户的数据存储在Bigtable中。每一个用户有一个惟一的用户ID,并基于用户ID分配一个行名。全部用户行为都会存储在这个表中。一个单独的列族保存每一个类型的行为(如,这有一个列族专门用来保存全部的web查询)。每一个数据元素使用用户行为发生时的时间做为Bigtable的时间戳。个性化搜索经过运行在Bigtable上的MapReduce产生用户的行为概要。这些用户的行为概要用于个性化搜索结果。

个性化搜索数据会复制到几个Bigtable集群中,用于提升可用性和下降因为到客户端的距离所形成的延迟。个性化搜索组最初在Bigtable之上创建了一个客户端的副本机制,以确保全部副本数据的一致性。目前的系统使用了一个创建在服务器中的副本子系统。

个性化搜索存储系统的设计容许其余团队向他们本身的列中增长新的用户信息,而且目前则个系统被不少其余的Google服务使用,须要存放用户配置选项和设置。在不少团队中共享一个表的结果就是产生大量的列族。为了支持共享,咱们为Bigtable增长一个简单的配额机制,来限制用户在共享表中的存储空间。这个机制也为使用这个系统进行用户信息存储的各类产品提供了隔离机制。

9、        经验教训

在设计、实现、维护和支持Bigtable过程当中,咱们获得了有用的经验和一些有趣的教训。

咱们获得的一个教训就是,大的分布式系统很容易受到各类故障的影响,不只仅是标准的网络中断和许多分布式协议中定义的“失败-中止”故障。举个例子,咱们遇到了下列缘由引发的问题:内存和网络问题、时钟误差过大、机器挂起、扩展的和不对称的网络分区、咱们使用的其它系统的bug(好比Chubby)、GFS配额溢出、计划内和计划外的硬件维护。咱们经过这些问题收获了不少经验,咱们经过修改各类协议解决了它们。举个例子,咱们为咱们的RPC机制增长了校验和。咱们还经过删除对系统其它部分的假设解决了一些问题。好比,咱们再也不假设一个给定的Chubby操做只返回一个指定集合内的错误。

另外一个教训就是推迟增长一个新功能,直到咱们弄清楚这个新功能如何使用。好比,咱们起初计划提供支持多用途的API。由于咱们不是立刻须要使用它们,因此咱们就先不实现它们。如今,咱们有不少现实的应用运行在Bigtable上,咱们能调查它们的真正需求,而且发现大多数应用只须要单行上的事务功能。人们须要分布式事务功能之处,大多数都是用来存储二级索引的,咱们计划增长一个特定的机制来实现这个需求。这个新机制的通用性比分布式事务要差,可是效率更高(特别是对于几百行以上的更新操做),而且更符合咱们的跨数据中心复制的优化方案。

咱们从支持Bigtable中还学到了一个有实践意义的教训,适当的系统级监控至关重要(如:监控Bigtable自己和监控使用Bigtable的客户端)。好比,咱们扩展了咱们的PRC系统,所以对于一个RPC的例子,它能够为这个RPC详细记录重要的动做。这个功能容许咱们检测和修正不少问题,如在tablet上数据结构的锁竞争。当提交Bigtable修改时GFS写入慢,当METADATA tablet不可用时访问METADATA操做被挂起。关于监控的另一个例子是,每一个Bigtable集群都在Chubby中进行注册。这容许咱们追踪多有的集群,发现他们有多大,查看它们运行的软件的版本,它们收到了多少流量以及是否存在任何问题。

咱们学到的最重要的一课就是简单设计的价值。考虑到咱们的系统的大小(大约100000行的非测试代码),随着时间的推移,新的代码以各类意想不到的方式加入进来,咱们发现简洁的设计和编码给维护带来了巨大的好处。这方面的一个例子就是咱们的tablet服务器的成员协议。咱们初版的协议很简单:master周期性的和tablet服务器签定租约,它们的租约过时后会kill掉本身。不幸的是,这个协议的可用性在网络问题出现时明显的下降了,而且对于master的恢复时间十分敏感。咱们又从新几回设计了这个协议,直到这个协议表现良好。然而,最终的协议过于复杂,并且依赖了Chubby不多被其它应用使用的功能特性。咱们发现浪费了大量的时间去调试各类古怪的用例,不只有Bigtable的问题,也有Chubby自己的问题。最后,咱们只好放弃这个协议,从新设计了一个新的更为简单的协议,只使用了Chubby广为使用的功能特性。

10、        相关工做

Boxwood项目的组件在一些方面与Chubby、GFS和Bigtable相重叠,由于它也提供了分布式协议、锁、分布式块存储和分布式B树存储。在这些方面,Boxwood组件相对于其它Google服务来讲,更倾向于提供更为底层的服务。Boxwood项目的目标是给相似于文件系统或数据库的高层服务提供基础构件,而Bigtable的目的则是为了给客户端应用直接提供数据存储服务的。

许多现有的项目已经攻克了不少问题,实现了广域网上的分布式存储或高级服务。这包括了分布式的Hash表,这个项目最初是由CAN、Chord、Tapestry和Pastry项目发起的。这些系统的主要关注点与Bigtable不一样,好比各类不一样的带宽、不可信的协做者或者频繁的配置更改等。此外,去中心化和Byzantine错误容忍也不是Bigtable的目的。

依照应用开发者提供的分布式数据存储的模型,咱们相信,分布式B树或分布式Hash表提供的key-value对模型有很大的局限性。Key-velue是颇有用的组件,可是它们不该该是惟一的提供给开发者的组件。咱们选择的模型要不限于简单的key-value对,支持稀疏的半结构化的数据。另外,它依旧足够简单,以能高效的处理平面文件;它也是透明的(经过局域性群组),以容许用户对系统重要的行为进行调整。

有些数据库厂商已经开发出并行数据库,它可以存储海量的数据。Oracle的实时应用集群数据库使用了共享磁盘存储数据(Bigtable使用了GFS),而且有一个分布式锁管理系统(Bigtable使用了Chubby)。IBM的DB2平行版本基于一种相似于Bigtable的无共享架构。每一个DB2服务器都复制处理存储在一个关系型数据库中的表的一个子表。两个产品都提供了带有事务功能的完整的关系模型。

Bigtable局域性群组基于列存储的方式实现了相似压缩和磁盘读性能的提高,而不是基于行进行存储,这样的系统主要有C-Stroe和一些商业产品如Sybase IQSenSage、KDB+和MonetDB/X100的ColumnDM存储层。另外的一些系统对平面文件使用了垂直和水平分区,能够实现很好的数据压缩率,如AT&T的Daytona数据库。局域性群组不支持CPU级缓存优化,如Ailamaki中所描述的。

Bigtable使用了memtables和SSTables存储tablet更新的方法,与Log-Structured Merge Tree存储更新索引数据的方法相似。这两个系统中,排序好的数据在写入磁盘前会存在内存中,读操做必须对来自内存和磁盘的数据进行融合。

C-Store和Bigtable有不少共同的特色:两个系统都是用了无共享架构,而且都有两种不一样的数据结构,一个用于近期的写操做,另外一个用于长时间存储数据,并有一个将数据从一种形式转换到另外一种形式的机制。他们的系统API有很大的不一样:C-Store操做像一个关系型数据库,而Bigtable则提供了一个更底层的读和写接口,用于支持每台服务器每秒中的数千次操做。C-Store也是一个读优先的关系型DBMS,而Bigtable则在密集读和密集写的应用上都有很好的性能。

Bigtable也必须解决一些全部无共享数据库锁须要面对的、相同类型的负载和内存均衡方面的问题。咱们的问题再某种程度上简单一些:(1)咱们不须要考虑同一份数据有多个拷贝的问题,同一份数据可能因为视图或索引的缘由以不一样的形式表现出来;(2)咱们让用户决定哪些数据应该放在内存中,哪些应该放在磁盘中,而不是由系统动态的判断;(3)咱们的系统中没有复杂的查询操做或优化工做。

11、            总结

咱们已经描述了Bigtable,一个分布式存储系统,用来存储Google的结构化数据。Bigtable集群已经从2005年4月开始用于生产环境,在这以前,咱们在设计和实现上大概花费了7人年的时间。截止到2006年8月,已经有超过60个项目使用了Bigtable。咱们的用户喜欢Bigtable实现提供的性能和高可用性,而且他们能简单的经过向系统中增长更多的机器来进行扩容,由于他们的资源需求一直在变。

因为Bigtable提供的接口并不常见,一个有趣的问题时咱们的用户要去适应它有多困难。新的用户有时不肯定如何最好的使用Bigtable的接口,特别是他们已经习惯使用支持通用事务功能的关系型数据库。可是,事实是,Google的不少产品都成功的使用了Bigtable,这证实了咱们的设计在实际中工做的很好。

咱们在实现Bigtable的几个其它的功能,如支持二级索引,以及支持多Master节点的跨数据中心的复制的Bigtable基础构件。咱们已经开始部署Bigtable做为其它产品的服务,这样就可使不一样的团队不须要维护他们本身的Bigtable集群了。随着咱们的服务集群的扩容,咱们须要经过Bigtable本身处理更多的资源分配问题。

最后,咱们发现建设Google本身的存储解决方案带来了明显的优点。经过为Bigtable设计本身的数据模型,使咱们的系统有很好的灵活性。此外,因为咱们可以控制Bigtable的实现,以及Bigtable使用到的其它的Google的基础构件,这就意味着咱们在系统出现瓶颈或效率低下时可以及时解决问题。

12、            一些我的的理解

12.1 关于table、tablet和SSTable的关系

 

图7:table、tablet和SSTable的关系

12.2 METADATA表中的数据

这是本人根据这篇论文推测出来的,不必定正确,只是我的的一个理解。

 

图8:METADATA表中一行的内容

12.3 次压缩和主压缩对删除数据的处理

 

图9:次压缩和主压缩对删除数据的处理