BigTable翻译

Bigtable:结构化数据的分布式存储系统

摘要

bigtable是一个管理结构化数据的分布式存储系统,它被设计成拥有很好的扩展性:存储在上千台商用服务器的PB级别的数据。谷歌商城的许多项目的数据都在bigtable中,包括网页索引,谷歌地图和谷歌金融。在bigtable中,这些应用有许多不同的需求,在数据量项目(从URL到网页的卫星图像)到潜在的需求(离线处理到实时数据服务)。尽管这些需求多种多样,bigtable仍旧可以对所有的谷歌产品提供一个灵活,高性能的解决方案。在这篇论文中,我们描述了bigtable提供的一个简单数据模型,它在数据的布局与格式给了客户端动态的控制,我们描述了这个设计和实现了bigtable。

1 引言

在过去的两年半的时间,我们设计并实现发布了一个在谷歌内部称作bigtable的管理结构化数据的分布式存储系统。Bigtable被设计成可靠存储在上千台机器的PB级别数据。Bigtable实现了几项目标:广泛的适用性,可扩展性,高性能和高可靠性。Bigtable被用在超过60个谷歌产品和项目中,包括谷歌分析,谷歌金融,ORkut,个性化搜索,文档写作和谷歌地图。这些产品使用bigtable做了许多不同要求的工作,设计面向吞吐量的批处理任务到对终端用户的数据敏感性服务。Bigtable的集群被这些产品应用到大范围的配置,从一些服务器到上千台服务器,存储几百TB数据量的数据。

在很多方面,bigtable看起来像是一个数据库:它拥有许多数据库的实现策略。并行数据库和内存数据库实现了可扩展和高性能,但bigtable提供了一个不同的接口与这些数据库相比。Bigtable不支持一个完全的关系型数据模型。相反,它提供了一个拥有简单数据模型的客户端,它支持对数据格式和布局的动态控制,还允许他对潜在存储的数据推测位置属性。数据用任意字符的行列进行索引。Bigtable把数据当做未解释的字符,尽管客户端经常序列化结构化与半结构化的数据成这些字符串。客户端仔细的用它的方案来控制这些数据的位置属性。最初,bigtable的方案参数来控制这些数据在内存中还是磁盘中。

第二部分描述数据模型的更多细节,第三部分提供客户端API的概览。第四部分简要描述构成谷歌基础设施所依靠的bigtable。第五部分描述了bigtable实现的基础。第六部分我们做的一些改进思想来提升bigtable的性能。第七部分提供了测量bigtable性能的方法。第八部分,我们描述了bigtable在谷歌应用的几个案例。第九部分我们学到的一些经验在设计和支持达标的过程中。最后,第十部分叙述了一些相关工作,第十一部分得出我们的结论。

2 数据模型

Bigtable是一个稀疏,分布式,持久的多维度的有序映射。映射被行健,列键和时间戳索引。在映射的每一个值都是一个未被翻译的字节数组。

在分析了许多类似bigtable系统的潜在使用,我们得出了这个数据模型。作为一个具体的例子使我们产生了一些设计的决定。假设我们想保存许多网页与相关信息的副本供其他项目使用;让我们称这个特殊的表为网页表。在网页表中,我们可以用URL作为行键。网页的其他信息作为列名,把网页内容存储在contents中。当他们全被取走时,列在时间戳下。

行:在bigtable的行键是任意的字符串(最大为64Kb,对大多数用户10-100个字节大小)。在单独行键的每次读取的是原子操作(不管被读的列有多少在这个行键中),一个设计的决定让客户端推测并发更新同一行的行为变得更容易。

Bigtable以字典序存储数据。在一个表的行的范围被动态的分区。每个范围的表被看做是一个表,他是分区和负载均衡的单元。结果,小范围的行的读取更加高效与更少的不同机器之间的交流。客户端可以利用这个特性来选取他的行,以便获取更好的数据访问位置。例如,在网站表中,在相同的领域的网页被分到连续的行通过翻转域名的URL,例如我们存储maps.google.com/index.html。在键com.google.maps/index.html,相同域名存储的网页在一起使域名和主机名的分析更加高效。

列簇:列键按照列簇被分成不同组,它形成了访问控制的基本单元。在一个列簇的所有数据通常有相同的类型。(我们把一个列簇的数据压缩到一起。列簇在数据存储到列之前必须被创建;在列簇创建后,在这个列簇的任何列可以被使用。在表中不同列簇的数量会很小。(最多上百个),在操作中,簇很少改变是我们专注的地方。相反,一个表的列可能没有上界。

一个列键的命名用以下语法:

列族必须能够输出,但列名必须能够输出,但列名为任意字符串,对于一个网页表的样例列族是语言,它存储了这个网页书写的语言。在列族语言中我们仅有一个列,它存储每个网页的语言ID,另一个有用的列族是anchor。在这个列族中的每个列键代表一个anchor,如图一所示,列名的名字指的是网站,内容的方格连接着文章。

访问控制和磁盘与内存指针在列族的级别被表示。在我们的网页表中,这些控制允许我们管理几个不同类型的应用:一些增加新的基础数据,一些读取基础数据。一个创建获得列族,一些仅仅允许可是存在的数据(由于私人原因,不允许查看所有存在的列族)

时间戳:

在bigtable的每个方格包含多个版本的数据,这些版本以时间戳为索引,bigtable的时间戳是64位的整数。他们可以通过大表分配,他们是微妙级别的实时,或者通过客户端应用模糊分配。应用程序必须生成独一无二的时间戳来避免冲突。方格中存储的数据存储以时间戳的降序排序,所以可以读取最近版本的数据。

为了让管理不同版本的数据更加容易,我们对每个列族支持两种不同的版本来自动区分表和回收站。客户端可以指定一个方格表保留几个版本或者保留最新的版本(比如保留最近七天的版本)。

在我们的网页表例子中,我们设抓取的网页的时间戳存储在contents,这些网页版本实际抓取的时间,上述描述的回收站原理是让我们仅保留每页最近三个版本。

3 API

达标的API提供了对表和列族的创建与删除。它还提供了改变集群,表和列族元数据的函数,比如访问控制权力。客户端的应用可以写成删除bigtable中的值,从不同行中查找值,或迭代表中的一部分数据。图二展示了用C++对标做的一系列更新。(无关的细节被消除了,未来使例子变得短一些)。Apply的调用在网页表中的表现了原子变化:它对www.cnn.cim增加了锚点和删除了其他锚点。

图三表示了用scanner语法来遍历特定行的所有锚点。客户端可以遍历多个列族,这有几个用scan的机制来限制行,列和时间戳。比如,我们可以用正则表达式匹配的结果限制scan,或者产生时间戳在最近十天内的日期。

Bigtable允许用户操作数据以更复杂的方式功能。第一,bigtable支持单行事务,比如可以在单行执行原子的读取-修改-写入序列操作。现在大表不再支持对行键的事务,尽管它提供的批处理写入的接口。第二,bigtable允许cells作为整数计数器,最后bigtable支持对客户端提供的脚本的执行,脚本是以谷歌为处理数据所开发的语言Sawzall。现在,我们一sawall的api接口不允许客户脚本写入bigtable,但它允许不同格式的数据转变,以任意表达式的过滤,通过一系列操作得出的总结。Bigtable可以用mapreduce,谷歌所发明的一个并行处理计算框架。我们写了一些包装来使得bigtable可以作为mapreduce的输入或输出源。

4 构建块

Bigtable是在一些谷歌基础设施上构建出来的。谷歌使用分布式的谷歌文件系统(GFS)来存储日志和数据文件。Bigtable集群在运行着许多其他分布式应用的共享机器的是典型操作,bigtable经常共享相同的机器处理不同的应用。Bigtable依靠集群管理系统来为job安排时间:管理共享机器的资源,处理机器故障和监视机器状态。

谷歌的SStable文件格式在内部用来存储bigtable的数据,SStable提供持久的 ,有序不可变的键值映射,键值为任意字节的字符串,用特定的键来查询对应的值,和遍历所有指定键范围的键值对。在内部,每个SStable有一系列块(典型的块为64Kb,但是这是可配置的),块索引(存储在SStable的尾部)用来定位块:当表被打开时,下标被加载到内存中,一个查询可以在单块磁盘上执行。我们在内存中通过两交叉查找来找到合适的块,然后从磁盘中读取合适的块。可选择的,一个SStable可以通过映射到内存中来完成,这样可以不接触磁盘来执行查询和扫描。

Bigtable依靠高可用和称作Chubby可持久的分布式锁服务。一个Chubby服务存在5个活动的副本。当大多数副本运行和相互之间可交流,则chubby服务是存活的。Paxos算法让它的副本面临故障时也能够存在。Chubby提供一个由目录和小文件组成的命名空间。每个目录或文件被当做一个锁,使读写一个文件成为一个原子操作。Chubby客户端的库提供chubby文件的长期缓存。每个chubby客户端和chubby服务保持一个会话。如果它不能再租约到期时候,新客户端的会话就会过期。当一个客户端的会话过期了,它便失去了任何的锁和控制处理。在chubby文件和文件夹回调函数来提醒改变和会话到期。

Bigtable在许多任务中使用了chubby:确保任何时刻仅有一个活动的master,存储bigtable数据存储的文职,发现故障服务器和终结服务器。存储bigtable方案信息(每个表的列族信息)和存储访问控制列表。在一个扩展时期,如果一个chubby不可用,则bigtable也变得不可用,我们最近在14个bigtable集群扩展11个chubby示例的影响。在chubby不可用的情况下,存储在bigtable不可用率平均为0.0047%。(由于chubby或网络原因)单节点受影响为0.0326%。

5 实现

Bigtable的实现有三个重要组件:连接每个客户端和服务器的库,一个节点,节点服务器能动态的从一个集群中增加或移除一个来调整负载均衡。

Master负责向服务器注册节点服务,侦测服务器的增加的节点和到期时间,平均服务器负载,和在GFS 上进行垃圾回收。另外,他处理表和列族的创建的方案变化。

每个节点服务器管理一组节点(每个服务器典型的是管理10-100个集群)。服务器请求节点处理它拥有的读写,还有切分节点电脑的增长过大的数据。

大多数单主节点分布式存储系统。客户端数据没有把数据移到master节点,客户端直接和服务器进行读和写的通信。因为bigtable的客户端不仅靠master来获取节点的位置信息,大多数客户端从未和master进行通信。结果,master在实践中很少被加载,bigtable集群存储许多表,每个表存储在去多节点上,每个节点包括一个行范围的所有数据。最初,一个表在一个节点上,随着表的增长,它自动分成许多节点上,默认每个100-200Mb。

5.1 节点位置,我们用类似B+树存储节点的位置信息的三层机制

第一级是存储在chubby的包含根节点位置信息的文件,根节点包含所有节点的位置信息。存储在一个特殊的METADATA表中。每个METADATA节点包含一些用户节点的位置信息,根节点就是METADATA表的第一个节点,但是特别的-它从不切分-来确保节点位置等级不超过三层。

元数据表存储了节点的位置,以一个被编码的行键标识符和它的结束行。每个元数据行在内存中大约存储1Kb的数据。在一个由128MB元数据节点的限制上,我们的三级位置方案足够解决2的34次方个节点(或2的61次方字节在128MB节点)。客户端的库缓存了节点的位置信息,如果客户端不知道节点的位置或发现位置不正确,那么它迭代向上查询节点位置层级。如果客户端的缓存为空,位置算法会要求三个网络往返查询,包括从chubby读数据。如果客户端缓存过旧,位置算法占用6轮,因为旧的缓存仅仅发现错误信息(假设元数据更新不频繁),尽管节点信息存储在内存中,所以无需访问GFS,通过提前取得节点位置,我们进一步减少了成本在通常的情况下。无论任何时候读取元数据表,它都是从许多节点上读。

我们也存储次级信息在元数据表中,包括每个节点所有事件的日志。(比如当一个服务器开始服务)。这些信息对于查错和性能分析非常有用。

5.2 节点分配

同一时刻一个节点被分配给一个服务器。Master监控活着的服务器,节点与节点服务器的当前任务。包括哪个没有分配。当节点未被分配,和服务器对于一个节点来说有足够的空间是可用的。Master通过发送节点负载来请求服务器给节点分配。

Bigtable用chubby来观察服务器,当bigtable服务器启动,它创建和获取一个排外锁在一个指定的chubby文件夹总的一个命名唯一个文件。Master监控这个文件夹来发现服务器,服务器如失去它的排外锁便会停止服务节点,比如由于网络原因失去它的会话(chubby提供一个有效的机制来检查是否还拥有锁,在不引起网络流量的情况下),只要文件存在,chubby会再次尝试获取它的排外锁。如文件不存在,服务器便不会再服务,因而消除自己。无论何时,服务器终止(因为集群管理系统从集群中移除服务器),它尝试释放他的锁,以便master可以再次分配。

Master负责监听当一个服务器不在工作时,从新尽快分配这些节点,为了检查服务器什么时候不再工作,master周期的询问服务器的锁状态。如果最后几次master不能访问server,master则尝试获取排外锁。因此chubby活着而服务器死掉或有问题不能联系chubby,因此,master确信Server不再工作后,删除它的文件。一旦删除了server的文件,master会移除所有之前分给节点的任务。未来确保bigtable集群在master和chubby之间不会受到网络问题影响,若果chubby会话到期,它便会自杀,然后,如上所述,master的故障不会改变节点和server的分配。

通过集群管理系统master开始了,在改变节点之前,它需要当前的分配情况。Master执行如下步骤设置:1,master从chubby获取一个唯一锁,避免并发master实例。2,master扫描在chubby的文件夹找活着的server。3,master与每个节点通信来发现那个节点已经分配到每个服务器。4,master扫描meta数据来了解节点的集合,无论何时扫描发现未分配的节点,master会添加根节点到未分配节点集合,如果在第三步主节点的分配没有发现。这个确保根节点会被分配,因为根节点包含所有元数据节点的名字。在扫描后,master知道所有的名字。

当一个表的创建删除,两个表合成一个大表,或一个表分成两个更小的表,存在的节点会改变。Master会监控这些变化,因为它初始化了所有表的切分被特殊的对待,因为这个是服务器发起的。在元数据表张广宁记录新的节点信息。当切分提交后,它提醒master,以防切片信息丢失(或节点服务器server挂掉)master,当它要求节点服务器加载刚切分的切片,master检测新的节点。服务器会提醒切片的master,因为节点进入发现在元数据表会指定一部分节点上master请求加载。

5.3 节点服务

节点的持久状态存储在GFS上,如图5所示。更新提交在存储重做记录的提交日志中,至于这些更新,最近提交的记录以一个有序的叫做memtable缓存在内存中。旧些的更新以一个sstable序列存储着。为了恢复一个节点,节点服务器从metadata表中读取它的metadata。这个元数据由节点和一些重做记录组成。这些点指向任何的提交日志,这些可能包含节点的数据。服务器读物sstable下表到内存中,通过应用重构memtable所有提交过的更新。

当一个写操作到达节点服务器,服务器检查他是否正确,发送者被授权执行变化。从一个chubby文件中读取一列允许的写者后,授权被执行。一个有效的变化被写入提交日志中。组提交时用来提升小变化的吞吐量。在写操作提交后,它的内容插入了memtable。

当读操作到达服务器,它同样的检查正确性和权限。一个有效的读操作在一个sstable和memtable融合后的视图上被执行。因为sstable和memtable是字典序存储的数据结构,融合的视图会被有效的形成。来到的读和写操作仍能继续,当节点切分和融合后。

5.4 压缩

随着写操作的执行,memtable的大小会增加,当memtable的大小到达一个临界点,memtable会被冻结,一个新的memtable会被创建,冻结的memtable是转换成sstable写入GFS中,压缩执行有两个目标,减少节点服务器的内存使用,减少当一个节点挂掉的服务器恢复所读取的数据量,读写操作在压缩发生时仍可进行。

每个小的压缩创建一个新的sstable,如果这个行为持续未检查,读操作需要融合来自任意个sstable的更新。相反,我们为这样的文件数量设定界限来定期执行融合压缩在后台,一个融合压缩读取一些sstable和memtable的内容,写入到一个新的sstable:输入的sstable和memtable被称作一个主要压缩。Sstable通过非主要压缩能包含特殊的删除条目,这些条目限制仍活着的sstables。换句话说,一个主要的压缩产生一个不包含删除信息和删除的数据的sstable,大表轮转它的表,和对他们进入主要压缩。这些大压缩允许使用删除的数据重新申请资源,也同样允许删除的数据从系统中及时消失,这对于敏感数据服务是很重要的。

6 重新细化

前面所描述的实现需要许多细化来实现用户要求的高性能,可用性和可靠性。这一部分描述在实现部分更多细节来强调这些细化。

本地组。客户端能把多个列族分到同一个本地组。每个本地组在每个节点中生成一个分离的sstable。分离的列族通常不是一起访问在不同的本地组,确保更高效的读。比如在网页表的元数据能在一个本地组中,网页的内容在不同的组:一个应用想读元数据不需要读取所有的网页内容。

另外,一些参数在每个本地组中能被指定。比如,一个本地组能在内存中声明,在sstable中的本地组被懒加载到服务器的内存中,一旦加载完成,属于本地的列族能够不用访问内存读取。这个功能对于访问频繁少量数据是很有用的。我们内部用它对于位置列族在元数据表中。

压缩:客户端可以控制本地组的sstable是否压缩,若压缩,则要用哪种压缩格式。用户指定的压缩格式应用到所欲的sstable块。尽管我们分开压缩会失去一些空间,则有益我们读少量数据时不用解压所有的文件,许多客户端使用两遍自定义压缩方案。第一遍用B和M方案,用来压缩长字符串通过一个大窗口,第二遍用一个快速压缩算法来寻找16kb大小窗口的重复,两者压缩非常快,以100-200M/s速度压缩,以400-1000M/s解压缩。

在我们选择压缩算法的时候,尽管我们强调速度而不是空间减少,这两遍压缩算法确实让我们很惊喜。例如,我们用这个压缩方案存储网页内容,在一个实验中,我们存储许多压缩格式的文档。在本地组,未来实验我们只存储一个版本。这个方案取得了在存储空间的1/10。这笔典型的Gzip1/3或1/4 在html页面要好,因为网页表中的行被列出来了。一个主机的所有页面被存储在互相接近的地方。这允许BM算法确认大量共享相同用户模板。不仅是网页表,许多应用选择他们的行名,以便想死的数据能被聚一起。进而取得很好的压缩率。当存储多个版本的相同值在bigtable中,压缩率会更好。

为读取的性能:缓存。为了提升读的性能,节点服务器用二级缓存。扫描表缓存是一个高水平的缓存,缓存由sstable和节点服务器的键值对,块缓存是 一个低级的缓存。从GFS读的sstable的缓存,扫描缓存是最有用的对于倾向多次读取相同数据的应用。块级缓存倾向读取最近读的数据。

布隆过滤器

一个读操作读取所有sstable,标记节点的状态。如果这些sstable不在内存,我们可能需多次访问磁盘。我们减少访问次数通过允许客户端指定布隆过滤器在创建sstable在特别的本地组,一个布隆过滤器允许我们指定行列对。对一些应用,少量内存出书布隆过滤器极大的减少了访问磁盘的数量对于读操作来说我们用它也意味着在大多数查不到的列,行是不需要访问磁盘。

提交日志实现:如果我们为每个节点各自提交一个日志文件,大量文件会出现在GFS中,依赖于在每个节点服务器的潜在文件系统实现。写操作会引起许多磁盘的读写查找不同物理日志文件。另外分别提交也减少了分组提交的有效性。因为组会变得更小。未来解决这些问题,我们追加到每个节点服务器的日志,混合不同节点的变化到一个日志文件中。

用一个日志提供意义重大的性能提升在正常的操作中,但它的复杂性增加了。当一个节点服务器挂掉了,被服务的节点会移到其他服务器上。每个服务器特别的加载少量原服务器节点的日志。未来恢复节点的状态,新的服务器需重新执行日志。然而这些日志混合到一起了。一个方法读取所有日志,但仅执行它需要恢复的,然而在这个方案下,如果100个节点分配给了那个挂掉的服务器,那么这个日志文件需读取100次(被每个服务器)。

我们按照键关键字(表,行名,日志序列数)第一次对提交日志排序为了避免读取重复的日志。在排序的输出,对一个特别的表的所有变化是相邻的,因而对一个磁盘寻找和一个顺序读。为了并行化排序,我们把log日志分成64Mb大小的块,并行排序每一个部分在不同服务器上。这个排序被master所协调,当一个服务器表明它需要从一些日志文件恢复一些日志时会被发起。

向GFS中写入提交日志有时会引起性能下降对许多原因。未来避免GFS变化过大,每个服务器节点实际有两个写线程。每次写日志文件,同时只有一个线程写。如果一个线程写,性能不好则会换另一个线程。提交日志队列的变化会被新的写线程写入日志项包含序列数字来允许恢复执行时忽略由切换处理造成的重复日志。

加速节点恢复

当Master服务器将一个Tablet从一个Tablet服务器移到另外一个Tablet服务器时,源Tablet服务器会对这个Tablet做一次Minor Compaction。这个Compaction操作减少了Tablet服务器的日志文件中没有归并的记录,从而减少了恢复的时间。Compaction完成之后,该服务器就停止为该Tablet提供服务。在卸载Tablet之前,源Tablet服务器还会再做一次(通常会很快)Minor Compaction,以消除前面在一次压缩过程中又产生的未归并的记录。第二次Minor Compaction完成以后,Tablet就可以被装载到新的Tablet服务器上了,并且不需要从日志中进行恢复。

利用不变性

我们在使用Bigtable时,除了SSTable缓存之外的其它部分产生的SSTable都是不变的,我们可以利用这一点对系统进行简化。例如,当从SSTable读取数据的时候,我们不必对文件系统访问操作进行同步。这样一来,就可以非常高效的实现对行的并行操作。metatable是唯一一个能被读和写操作同时访问的可变数据结构。为了减少在读操作时的竞争,我们对内存表采用COW(Copy-on-write)机制,这样就允许读写操作并行执行。

因为SSTable是不变的,因此,我们可以把永久删除被标记为“删除”的数据的问题,转换成对废弃的SSTable进行垃圾收集的问题了。每个Tablet的SSTable都在METADATA表中注册了。Master服务器采用“标记-删除”的垃圾回收方式删除SSTable集合中废弃的SSTable,METADATA表则保存了Root SSTable的集合。

最后,SSTable的不变性使得分割Tablet的操作非常快捷。我们不必为每个分割出来的Tablet建立新的SSTable集合,而是共享原来的Tablet的SSTable集合。

7 性能评估

为了测试Bigtable的性能和扩展性,我们建立了一个包括N台Tablet服务器的Bigtable集群,这里N是可变的。每台Tablet服务器配置了1GB的内存,数据写入到一个包括1786台机器、每台机器有2个IDE硬盘的GFS集群上。我们使用N台客户机生成工作负载测试Bigtable。(我们使用和Tablet服务器相同数目的客户机以确保客户机不会成为瓶颈。) 每台客户机配置2GZ双核Opteron处理器,配置了足以容纳所有进程工作数据集的物理内存,以及一张Gigabit的以太网卡。这些机器都连入一个两层的、树状的交换网络里,在根节点上的带宽加起来有大约100-200Gbps。所有的机器采用相同的设备,因此,任何两台机器间网络来回一次的时间都小于1ms。

Tablet服务器、Master服务器、测试机、以及GFS服务器都运行在同一组机器上。每台机器都运行一个GFS的服务器。其它的机器要么运行Tablet服务器、要么运行客户程序、要么运行在测试过程中,使用这组机器的其它的任务启动的进程。

R是测试过程中,Bigtable包含的不同的列关键字的数量。我们精心选择R的值,保证每次基准测试对每台Tablet服务器读/写的数据量都在1GB左右。

在序列写的基准测试中,我们使用的列关键字的范围是0到R-1。这个范围又被划分为10N个大小相同的区间。核心调度程序把这些区间分配给N个客户端,分配方式是:只要客户程序处理完上一个区间的数据,调度程序就把后续的、尚未处理的区间分配给它。这种动态分配的方式有助于减少客户机上同时运行的其它进程对性能的影响。我们在每个列关键字下写入一个单独的字符串。每个字符串都是随机生成的、因此也没有被压缩。另外,不同列关键字下的字符串也是不同的,因此也就不存在跨行的压缩。随机写入基准测试采用类似的方法,除了行关键字在写入前先做Hash,Hash采用按R取模的方式,这样就保证了在整个基准测试持续的时间内,写入的工作负载均匀的分布在列存储空间内。

序列读的基准测试生成列关键字的方式与序列写相同,不同于序列写在列关键字下写入字符串的是,序列读是读取列关键字下的字符串(这些字符串由之前序列写基准测试程序写入)。同样的,随机读的基准测试和随机写是类似的。

扫描基准测试和序列读类似,但是使用的是BigTable提供的、从一个列范围内扫描所有的value值的API。由于一次RPC调用就从一个Tablet服务器取回了大量的Value值,因此,使用扫描方式的基准测试程序可以减少RPC调用的次数。

随机读(内存)基准测试和随机读类似,除了包含基准测试数据的局部性群组被设置为“in-memory”,因此,读操作直接从Tablet服务器的内存中读取数据,不需要从GFS读取数据。针对这个测试,我们把每台Tablet服务器存储的数据从1GB减少到100MB,这样就可以把数据全部加载到Tablet服务器的内存中了。

这里写图片描述

图6中有两个视图,显示了我们的基准测试的性能;图中的数据和曲线是读/写 1000-byte value值时取得的。图中的表格显示了每个Tablet服务器每秒钟进行的操作的次数;图中的曲线显示了每秒种所有的Tablet服务器上操作次数的总和。

单个Tablet服务器的性能

我们首先分析下单个Tablet服务器的性能。随机读的性能比其它操作慢一个数量级或以上。每个随机读操作都要通过网络从GFS传输64KB的SSTable到Tablet服务器,而我们只使用其中大小是1000 byte的一个value值。Tablet服务器每秒大约执行1200次读操作,也就是每秒大约从GFS读取75MB的数据。这个传输带宽足以占满Tablet服务器的CPU时间,因为其中包括了网络协议栈的消耗、SSTable解析、以及BigTable代码执行;这个带宽也足以占满我们系统中网络的链接带宽。大多数采用这种访问模式BigTable应用程序会减小Block的大小,通常会减到8KB。

内存中的随机读操作速度快很多,原因是,所有1000-byte的读操作都是从Tablet服务器的本地内存中读取数据,不需要从GFS读取64KB的Block。

随机和序列写操作的性能比随机读要好些,原因是每个Tablet服务器直接把写入操作的内容追加到一个Commit日志文件的尾部,并且采用批量提交的方式,通过把数据以流的方式写入到GFS来提高性能。随机写和序列写在性能上没有太大的差异,这两种方式的写操作实际上都是把操作内容记录到同一个Tablet服务器的Commit日志文件中。

序列读的性能好于随机读,因为每取出64KB的SSTable的Block后,这些数据会缓存到Block缓存中,后续的64次读操作直接从缓存读取数据。

扫描的性能更高,这是由于客户程序每一次RPC调用都会返回大量的value的数据,所以,RPC调用的消耗基本抵消了。

性能提升

随着我们将系统中的Tablet服务器从1台增加到500台,系统的整体吞吐量有了梦幻般的增长,增长的倍率超过了100。比如,随着Tablet服务器的数量增加了500倍,内存中的随机读操作的性能增加了300倍。之所以会有这样的性能提升,主要是因为这个基准测试的瓶颈是单台Tablet服务器的CPU。

尽管如此,性能的提升还不是线性的。在大多数的基准测试中我们看到,当Tablet服务器的数量从1台增加到50台时,每台服务器的吞吐量会有一个明显的下降。这是由于多台服务器间的负载不均衡造成的,大多数情况下是由于其它的程序抢占了CPU。 我们负载均衡的算法会尽量避免这种不均衡,但是基于两个主要原因,这个算法并不能完美的工作:一个是尽量减少Tablet的移动导致重新负载均衡能力受限(如果Tablet被移动了,那么在短时间内 — 一般是1秒内 — 这个Tablet是不可用的),另一个是我们的基准测试程序产生的负载会有波动。

随机读基准测试的测试结果显示,随机读的性能随Tablet服务器数量增加的提升幅度最小(整体吞吐量只提升了100倍,而服务器的数量却增加了500倍)。这是因为每个1000-byte的读操作都会导致一个64KB大的Block在网络上传输。这样的网络传输量消耗了我们网络中各种共享的1GB的链路,结果导致随着我们增加服务器的数量,每台服务器上的吞吐量急剧下降。

8 实际应用

这里写图片描述

截止到2006年8月,Google内部一共有388个非测试用的Bigtable集群运行在各种各样的服务器集群上,合计大约有24500个Tablet服务器。表1显示了每个集群上Tablet服务器的大致分布情况。这些集群中,许多用于开发目的,因此会有一段时期比较空闲。通过观察一个由14个集群、8069个Tablet服务器组成的集群组,我们看到整体的吞吐量超过了每秒1200000次请求,发送到系统的RPC请求导致的网络负载达到了741MB/s,系统发出的RPC请求网络负载大约是16GB/s。

这里写图片描述

表2提供了一些目前正在使用的表的相关数据。一些表存储的是用户相关的数据,另外一些存储的则是用于批处理的数据;这些表在总的大小、 每个数据项的平均大小、从内存中读取的数据的比例、表的Schema的复杂程度上都有很大的差别。本节的其余部分,我们将主要描述三个产品研发团队如何使用Bigtable的。

Google Analytics

Google Analytics是用来帮助Web站点的管理员分析他们网站的流量模式的服务。它提供了整体状况的统计数据,比如每天的独立访问的用户数量、每天每个URL的浏览次数;它还提供了用户使用网站的行为报告,比如根据用户之前访问的某些页面,统计出几成的用户购买了商品。

为了使用这个服务,Web站点的管理员只需要在他们的Web页面中嵌入一小段JavaScript脚本就可以了。这个Javascript程序在页面被访问的时候调用。它记录了各种Google Analytics需要使用的信息,比如用户的标识、获取的网页的相关信息。Google Analytics汇总这些数据,之后提供给Web站点的管理员。

我们粗略的描述一下Google Analytics使用的两个表。Row Click表(大约有200TB数据)的每一行存放了一个最终用户的会话。行的名字是一个包含Web站点名字以及用户会话创建时间的元组。这种模式保证了对同一个Web站点的访问会话是顺序的,会话按时间顺序存储。这个表可以压缩到原来尺寸的14%。

Summary表(大约有20TB的数据)包含了关于每个Web站点的、各种类型的预定义汇总信息。一个周期性运行的MapReduce任务根据Raw Click表的数据生成Summary表的数据。每个MapReduce工作进程都从Raw Click表中提取最新的会话数据。系统的整体吞吐量受限于GFS的吞吐量。这个表的能够压缩到原有尺寸的29%。

Google Earth

Google通过一组服务为用户提供了高分辨率的地球表面卫星图像,访问的方式可以使通过基于Web的Google Maps访问接口(maps.google.com),也可以通过Google Earth定制的客户端软件访问。这些软件产品允许用户浏览地球表面的图像:用户可以在不同的分辨率下平移、查看和注释这些卫星图像。这个系统使用一个表存储预处理数据,使用另外一组表存储用户数据。

数据预处理流水线使用一个表存储原始图像。在预处理过程中,图像被清除,图像数据合并到最终的服务数据中。这个表包含了大约70TB的数据,所以需要从磁盘读取数据。图像已经被高效压缩过了,因此存储在Bigtable后不需要再压缩了。

Imagery表的每一行都代表了一个单独的地理区域。行都有名称,以确保毗邻的区域存储在了一起。Imagery表中有一个列族用来记录每个区域的数据源。这个列族包含了大量的列:基本上市每个列对应一个原始图片的数据。由于每个地理区域都是由很少的几张图片构成的,因此这个列族是非常稀疏的。

数据预处理流水线高度依赖运行在Bigtable上的MapReduce任务传输数据。在运行某些MapReduce任务的时候,整个系统中每台Tablet服务器的数据处理速度是1MB/s。

这个服务系统使用一个表来索引GFS中的数据。这个表相对较小(大约是500GB),但是这个表必须在保证较低的响应延时的前提下,针对每个数据中心,每秒处理几万个查询请求。 因此,这个表必须在上百个Tablet服务器上存储数据,并且使用in-memory的列族。

个性化查询

个性化查询(www.google.com/psearch)是一个双向服务;这个服务记录用户的查询和点击,涉及到各种Google的服务,比如Web查询、图像和新闻。用户可以浏览他们查询的历史,重复他们之前的查询和点击;用户也可以定制基于Google历史使用习惯模式的个性化查询结果。

个性化查询使用Bigtable存储每个用户的数据。每个用户都有一个唯一的用户id,每个用户id和一个列名绑定。一个单独的列族被用来存储各种类型的行为(比如,有个列族可能是用来存储所有的Web查询的)。每个数据项都被用作Bigtable的时间戳,记录了相应的用户行为发生的时间。个性化查询使用以Bigtable为存储的MapReduce任务生成用户的数据图表。这些用户数据图表用来个性化当前的查询结果。

个性化查询的数据会复制到几个Bigtable的集群上,这样就增强了数据可用性,同时减少了由客户端和Bigtable集群间的“距离”造成的延时。个性化查询的开发团队最初建立了一个基于Bigtable的、“客户侧”的复制机制为所有的复制节点提供一致性保障。现在的系统则使用了内建的复制子系统。

个性化查询存储系统的设计允许其它的团队在它们自己的列中加入新的用户数据,因此,很多Google服务使用个性化查询存储系统保存用户级的配置参数和设置。在多个团队之间分享数据的结果是产生了大量的列族。为了更好的支持数据共享,我们加入了一个简单的配额机制(alex注:quota,参考AIX的配额机制)限制用户在共享表中使用的空间;配额也为使用个性化查询系统存储用户级信息的产品团体提供了隔离机制。

9 经验教训

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

一个教训是,我们发现,很多类型的错误都会导致大型分布式系统受损,这些错误不仅仅是通常的网络中断、或者很多分布式协议中设想的fail-stop类型的错误(alex注:fail-stop failture,指一旦系统fail就stop,不输出任何数据;fail-fast failture,指fail不马上stop,在 短时间内return错误信息,然后再stop)。比如,我们遇到过下面这些类型的错误导致的问题:内存数据损坏、网络中断、时钟偏差、机器挂起、扩展的和非对称的网络分区、我们使用的其它系统的Bug(比如Chubby)、GFS配额溢出、 计划内和计划外的硬件维护。我们在解决这些问题的过程中学到了很多经验,我们通过修改协议来解决这些问题。比如,我们在我们的RPC机制中加入了Checksum。我们在设计系统的部分功能时,不对其它部分功能做任何的假设,这样的做法解决了其它的一些问题。比如,我们不再假设一个特定的Chubby操作只返回错误码集合中的一个值。

另外一个教训是,我们明白了在彻底了解一个新特性会被如何使用之后,再决定是否添加这个新特性是非常重要的。比如,我们开始计划在我们的API中支持通常方式的事务处理。但是由于我们还不会马上用到这个功能,因此,我们并没有去实现它。现在,Bigtable上已经有了很多的实际应用,我们可以检查它们真实的需求;我们发现,大多是应用程序都只是需要单个行上的事务功能。有些应用需要分布式的事务功能,分布式事务大多数情况下用于维护二级索引,因此我们增加了一个特殊的机制去满足这个需求。新的机制在通用性上比分布式事务差很多,但是它更有效(特别是在更新操作的涉及上百行数据的时候),而且非常符合我们的“跨数据中心”复制方案的优化策略。

还有一个具有实践意义的经验:我们发现系统级的监控对Bigtable非常重要(比如,监控Bigtable自身以及使用Bigtable的客户程序)。比如,我们扩展了我们的RPC系统,因此对于一个RPC调用的例子,它可以详细记录代表了RPC调用的很多重要操作。这个特性允许我们检测和修正很多的问题,比如Tablet数据结构上的锁的内容、在修改操作提交时对GFS的写入非常慢的问题、以及在METADATA表的Tablet不可用时,对METADATA表的访问挂起的问题。关于监控的用途的另外一个例子是,每个Bigtable集群都在Chubby中注册了。这可以帮助我们跟踪所有的集群状态、监控它们的大小、检查集群运行的我们软件的版本、监控集群流入数据的流量,以及检查是否有引发集群高延时的潜在因素。

对我们来说,最宝贵的经验是简单设计的价值。考虑到我们系统的代码量(大约100000行生产代码),以及随着时间的推移,新的代码以各种难以预料的方式加入系统,我们发现简洁的设计和编码给维护和调试带来的巨大好处。这方面的一个例子是我们的Tablet服务器成员协议。我们第一版的协议很简单:Master服务器周期性的和Tablet服务器签订租约,Tablet服务器在租约过期的时候Kill掉自己的进程。不幸的是,这个协议在遇到网络问题时会大大降低系统的可用性,也会大大增加Master服务器恢复的时间。我们多次重新设计这个协议,直到它能够很好的处理上述问题。但是,更不幸的是,最终的协议过于复杂了,并且依赖一些Chubby很少被用到的特性。我们发现我们浪费了大量的时间在调试一些古怪的问题,有些是Bigtable代码的问题,有些事Chubby代码的问题。最后,我们只好废弃了这个协议,重新制订了一个新的、更简单、只使用Chubby最广泛使用的特性的协议。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值