【转】谷歌三大核心技术(三)Google BigTable中文版

 

摘要

Bigtable 是一个分布式的结构化数据存储系统,它被设计用来处理海量数据:一般是分布在数千台普通服务器上的PB级的数据。Google的不少项目使用 Bigtable存储数据,包括Web索引、Google Earth、Google Finance。这些应用对Bigtable提出的要求差别很是大,不管是在数据量上(从URL到网页到卫星图像)仍是在响应速度上(从后端的批量处理到 实时数据服务)。尽管应用需求差别很大,可是,针对Google的这些产品,Bigtable仍是成功的提供了一个灵活的、高性能的解决方案。本论文描述 了Bigtable提供的简单的数据模型,利用这个模型,用户能够动态的控制数据的分布和格式;咱们还将描述Bigtable的设计和实现。html

1 介绍

在 过去两年半时间里,咱们设计、实现并部署了一个分布式的结构化数据存储系统 — 在Google,咱们称之为Bigtable。Bigtable的设计目的是可靠的处理PB级别的数据,而且可以部署到上千台机器上。Bigtable已 经实现了下面的几个目标:适用性普遍、可扩展、高性能和高可用性。Bigtable已经在超过60个Google的产品和项目上获得了应用,包括 Google Analytics、Google Finance、Orkut、 PersonalizedSearch、Writely和Google Earth。这些产品对Bigtable提出了迥异的需求,有的须要高吞吐量的批处理,有的则须要及时响应,快速返回数据给最终用户。它们使用的 Bigtable集群的配置也有很大的差别,有的集群只有几台服务器,而有的则须要上千台服务器、存储几百TB的数据。程序员

在不少方 面,Bigtable和数据库很相似:它使用了不少数据库的实现策略。并行数据库【14】和内存数据库【13】已经具有可扩展性和高性能,可是 Bigtable提供了一个和这些系统彻底不一样的接口。Bigtable不支持完整的关系数据模型;与之相反,Bigtable为客户提供了简单的数据模 型,利用这个模型,客户能够动态控制数据的分布和格式alex注:也就是对BigTable而言,数据是没有格式的,用数据库领域的术语说,就是数据没有Schema,用户本身去定义Schema),用户也能够本身推测(alex注:reasonabout)底层存储数据的位置相关性(alex注: 位置相关性能够这样理解,好比树状结构,具备相同前缀的数据的存放位置接近。在读取的时候,能够把这些数据一次读取出来)。数据的下标是行和列的名字,名 字能够是任意的字符串。Bigtable将存储的数据都视为字符串,可是Bigtable自己不去解析这些字符串,客户程序一般会在把各类结构化或者半结 构化的数据串行化到这些字符串里。经过仔细选择数据的模式,客户能够控制数据的位置相关性。最后,能够经过BigTable的模式参数来控制数据是存放在 内存中、仍是硬盘上。web

第二节描述关于数据模型更多细节方面的东西;第三节概要介绍了客户端API;第四节简要介绍了BigTable底层使 用的Google的基础框架;第五节描述了BigTable实现的关键部分;第6节描述了咱们为了提升BigTable的性能采用的一些精细的调优方法; 第7节提供了BigTable的性能数据;第8节讲述了几个Google内部使用BigTable的例子;第9节是咱们在设计和后期支持过程当中获得一些经 验和教训;最后,在第10节列出咱们的相关研究工做,第11节是咱们的结论。正则表达式

2 数据模型

Bigtable是一个稀疏的、分布式的、持久化存储的多维度排序Map(alex注:对于程序员来讲,Map应该不用翻译了吧。Map由key和value组成,后面咱们直接使用key和value,再也不另外翻译了)。Map的索引是行关键字、列关键字以及时间戳;Map中的每一个value都是一个未经解析的byte数组。算法

 

(row:string,column:string,time:int64)->string数据库

 

我 们在仔细分析了一个相似Bigtable的系统的种种潜在用途以后,决定使用这个数据模型。咱们先举个具体的例子,这个例子促使咱们作了不少设计决策;假 设咱们想要存储海量的网页及相关信息,这些数据能够用于不少不一样的项目,咱们姑且称这个特殊的表为Webtable。在Webtable里,咱们使用 URL做为行关键字,使用网页的某些属性做为列名,网页的内容存在“contents:”列中,并用获取该网页的时间戳做为标识(alex注:即按照获取时间不一样,存储了多个版本的网页数据),如图一所示。编程

图一:一个存储Web网页的例子的表的片段。行名是一个反向URL。contents列族存放的是网页的内容,anchor列族存放引用该网页的锚连接文本(alex注:若是不知道HTML的Anchor,请Google一把)。CNN的主页被Sports Illustrater和MY-look的主页引用,所以该行包含了名为“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每一个锚连接只有一个版本(alex注:注意时间戳标识了列的版本,t9和t8分别标识了两个锚连接的版本);而contents列则有三个版本,分别由时间戳t3,t5,和t6标识。后端

表 中的行关键字能够是任意的字符串(目前支持最大64KB的字符串,可是对大多数用户,10-100个字节就足够了)。对同一个行关键字的读或者写操做都是 原子的(无论读或者写这一行里多少个不一样列),这个设计决策可以使用户很容易的理解程序在对同一个行进行并发更新操做时的行为。数组

Bigtable经过行关键字的字典顺序来组织数据。表中的每一个行均可以动态分区。每一个分区叫作一个”Tablet”,Tablet是 数据分布和负载均衡调整的最小单位。这样作的结果是,当操做只读取行中不多几列的数据时效率很高,一般只须要不多几回机器间的通讯便可完成。用户能够经过 选择合适的行关键字,在数据访问时有效利用数据的位置相关性,从而更好的利用这个特性。举例来讲,在Webtable里,经过反转URL中主机名的方式, 能够把同一个域名下的网页汇集起来组织成连续的行。具体来讲,咱们能够把maps.google.com/index.html的数据存放在关键字 com.google.maps/index.html下。把相同的域中的网页存储在连续的区域可让基于主机和域名的分析更加有效。缓存

列族

列 关键字组成的集合叫作“列族“,列族是访问控制的基本单位。存放在同一列族下的全部数据一般都属于同一个类型(咱们能够把同一个列族下的数据压缩在一 起)。列族在使用以前必须先建立,而后才能在列族中任何的列关键字下存放数据;列族建立后,其中的任何一个列关键字下均可以存放数据。根据咱们的设计意 图,一张表中的列族不能太多(最多几百个),而且列族在运行期间不多改变。与之相对应的,一张表能够有无限多个列。

列关键字的命名语法以下:列族:限定词。列族的名字必须是可打印的字符串,而限定词的名字能够是任意的字符串。好比,Webtable有个列族language,language 列族用来存放撰写网页的语言。咱们在language列族中只使用一个列关键字,用来存放每一个网页的语言标识ID。Webtable中另外一个有用的列族是 anchor;这个列族的每个列关键字表明一个锚连接,如图一所示。Anchor列族的限定词是引用该网页的站点名;Anchor列族每列的数据项存放 的是连接文本。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。在咱们的Webtable的例子中,上述的控制权限能帮助咱们管理不 同类型的应用:咱们容许一些应用能够添加新的基本数据、一些应用能够读取基本数据并建立继承的列族、一些应用则只容许浏览数据(甚至可能由于隐私的缘由不 能浏览全部数据)。

时间戳

在 Bigtable中,表的每个数据项均可以包含同一份数据的不一样版本;不一样版本的数据经过时间戳来索引。Bigtable时间戳的类型是64位整型。 Bigtable能够给时间戳赋值,用来表示精确到毫秒的“实时”时间;用户程序也能够给时间戳赋值。若是应用程序须要避免数据版本冲突,那么它必须本身 生成具备惟一性的时间戳。数据项中,不一样版本的数据按照时间戳倒序排序,即最新的数据排在最前面。

为了减轻多个版本数据的管理负担,咱们对每个列族配有两个设置参数,Bigtable经过这两个参数能够对废弃版本的数据自动进行垃圾收集。用户能够指定只保存最后n个版本的数据,或者只保存“足够新”的版本的数据(好比,只保存最近7天的内容写入的数据)。

在Webtable的举例里,contents:列存储的时间戳信息是网络爬虫抓取一个页面的时间。上面说起的垃圾收集机制可让咱们只保留最近三个版本的网页数据。

3 API

Bigtable提供了创建和删除表以及列族的API函数。Bigtable还提供了修改集群、表和列族的元数据的API,好比修改访问权限。

 // Open the table

Table *T = OpenOrDie(“/bigtable/web/webtable”);

// Write a new anchor and delete an old anchor

RowMutation r1(T, “com.cnn.www”);

r1.Set(“anchor:www.c-span.org”, “CNN”);

r1.Delete(“anchor:www.abc.com”);

Operation op;

Apply(&op, &r1)

Figure 2: Writing to Bigtable.

 

客 户程序能够对Bigtable进行以下的操做:写入或者删除Bigtable中的值、从每一个行中查找值、或者遍历表中的一个数据子集。图2中的C++代码 使用RowMutation抽象对象进行了一系列的更新操做。(为了保持示例代码的简洁,咱们忽略了一些细节相关代码)。调用Apply函数对 Webtable进行了一个原子修改操做:它为www.cnn.com增长了一个锚点,同时删除了另一个锚点。

 Scanner scanner(T);

ScanStream *stream;

stream = scanner.FetchColumnFamily(“anchor”);

stream->SetReturnAllVersions();

scanner.Lookup(“com.cnn.www”);

for (; !stream->Done(); stream->Next()) {

    printf(“%s %s %lld %s\n”,

    scanner.RowName(),

    stream->ColumnName(),

    stream->MicroTimestamp(),

    stream->Value());

Figure3: Reading from Bigtable.

 

图 3中的C++代码使用Scanner抽象对象遍历一个行内的全部锚点。客户程序能够遍历多个列族,有几种方法能够对扫描输出的行、列和时间戳进行限制。例 如,咱们能够限制上面的扫描,让它只输出那些匹配正则表达式*.cnn.com的锚点,或者那些时间戳在当前时间前10天的锚点。

Bigtable 还支持一些其它的特性,利用这些特性,用户能够对数据进行更复杂的处理。首先,Bigtable支持单行上的事务处理,利用这个功能,用户能够对存储在一 个行关键字下的数据进行原子性的读-更新-写操做。虽然Bigtable提供了一个容许用户跨行批量写入数据的接口,可是,Bigtable目前还不支持 通用的跨行事务处理。其次,Bigtable容许把数据项用作整数计数器。最后,Bigtable容许用户在服务器的地址空间内执行脚本程序。脚本程序使 用Google开发的Sawzall【28】数据处理语言。虽然目前咱们基于的Sawzall语言的API函数还不容许客户的脚本程序写入数据到 Bigtable,可是它容许多种形式的数据转换、基于任意表达式的数据过滤、以及使用多种操做符的进行数据汇总。

Bigtable能够和MapReduce【12】一块儿使用,MapReduce是Google开发的大规模并行计算框架。咱们已经开发了一些Wrapper类,经过使用这些Wrapper类,Bigtable能够做为MapReduce框架的输入和输出。

4 BigTable构件

Bigtable 是创建在其它的几个Google基础构件上的。BigTable使用Google的分布式文件系统(GFS)【17】存储日志文件和数据文件。 BigTable集群一般运行在一个共享的机器池中,池中的机器还会运行其它的各类各样的分布式应用程序,BigTable的进程常常要和其它应用的进程 共享机器。BigTable依赖集群管理系统来调度任务、管理共享的机器上的资源、处理机器的故障、以及监视机器的状态。

BigTable内部存储数据的文件是GoogleSSTable格式的。SSTable是一个持久化的、排序的、不可更改的Map结构,而Map是一个key-value 映射的数据结构,key和value的值都是任意的Byte串。能够对SSTable进行以下的操做:查询与一个key值相关的value,或者遍历某个 key值范围内的全部的key-value对。从内部看,SSTable是一系列的数据块(一般每一个块的大小是64KB,这个大小是能够配置的)。 SSTable使用块索引(一般存储在SSTable的最后)来定位数据块;在打开SSTable的时候,索引被加载到内存。每次查找均可以经过一次磁盘 搜索完成:首先使用二分查找法在内存中的索引里找到数据块的位置,而后再从硬盘读取相应的数据块。也能够选择把整个SSTable都放在内存中,这样就不 必访问硬盘了。

BigTable还依赖一个高可用的、序列化的分布式锁服务组件,叫作Chubby【8】。一个Chubby服务包括了5个活动的副本,其中的一个副本被选为Master,而且处理请求。只有在大多数副本都是正常运行的,而且彼此之间可以互相通讯的状况下,Chubby服 务才是可用的。当有副本失效的时候,Chubby使用Paxos算法【9,23】来保证副本的一致性。Chubby提供了一个名字空间,里面包括了目录和 小文件。每一个目录或者文件能够当成一个锁,读写文件的操做都是原子的。Chubby客户程序库提供对Chubby文件的一致性缓存。每一个Chubby客户 程序都维护一个与Chubby服务的会话。若是客户程序不能在租约到期的时间内从新签定会话的租约,这个会话就过时失效了(alex注:又用到了lease。原文是:Aclient’s session expires if it is unable to renew its session lease within the leaseexpiration time.)。当一个会话失效时,它拥有的锁和打开的文件句柄都失效了。Chubby客户程序能够在文件和目录上注册回调函数,当文件或目录改变、或者会 话过时时,回调函数会通知客户程序。

Bigtable使用Chubby完成如下的几个任务:确保在任何给定的时间内最多只有一个活动的Master副本;存储BigTable数据的自引导指令的位置(参考5.1节);查找Tablet服 务器,以及在Tablet服务器失效时进行善后(5.2节);存储BigTable的模式信息(每张表的列族信息);以及存储访问控制列表。若是 Chubby长时间没法访问,BigTable就会失效。最近咱们在使用11个Chubby服务实例的14个BigTable集群上测量了这个影响。因为 Chubby不可用而致使BigTable中的部分数据不能访问的平均比率是0.0047%(Chubby不能访问的缘由多是Chubby自己失效或者 网络问题)。单个集群里,受Chubby失效影响最大的百分比是0.0326%(alex注:有点莫名其妙,原文是: The percentage for the single cluster that was most affected byChubby unavailability was 0.0326%.)。

5 介绍

Bigtable包括了三个主要的组件:连接到客户程序中的库、一个Master服务器和多个Tablet服务器。针对系统工做负载的变化状况,BigTable能够动态的向集群中添加(或者删除)Tablet服务器。

Master服务器主要负责如下工做:为Tablet服务器分配Tablets、检测新加入的或者过时失效的Table服务器、对Tablet服务器进行负载均衡、以及对保存在GFS上的文件进行垃圾收集。除此以外,它还处理对模式的相关修改操做,例如创建表和列族。

每一个Tablet服务器都管理一个Tablet的集合(一般每一个服务器有大约数十个至上千个Tablet)。每一个Tablet服务器负责处理它所加载的Tablet的读写操做,以及在Tablets过大时,对其进行分割。

和 不少Single-Master类型的分布式存储系统【17.21】相似,客户端读取的数据都不通过Master服务器:客户程序直接和Tablet服务 器通讯进行读写操做。因为BigTable的客户程序没必要经过Master服务器来获取Tablet的位置信息,所以,大多数客户程序甚至彻底不须要和 Master服务器通讯。在实际应用中,Master服务器的负载是很轻的。

一个BigTable集群存储了不少表,每一个表包含了一个 Tablet的集合,而每一个Tablet包含了某个范围内的行的全部相关数据。初始状态下,一个表只有一个Tablet。随着表中数据的增加,它被自动分 割成多个Tablet,缺省状况下,每一个Tablet的尺寸大约是100MB到200MB。

5.1Tablet的位置

咱们使用一个三层的、相似B+树[10]的结构存储Tablet的位置信息(如图4)。

第一层是一个存储在Chubby中的文件,它包含了Root Tablet的位置信息。Root Tablet包含了一个特殊的METADATA表里全部的Tablet的位置信息。METADATA表的每一个Tablet包含了一个用户Tablet的集合。RootTablet其实是METADATA表的第一个Tablet,只不过对它的处理比较特殊 — Root Tablet永远不会被分割 — 这就保证了Tablet的位置信息存储结构不会超过三层。

在 METADATA表里面,每一个Tablet的位置信息都存放在一个行关键字下面,而这个行关键字是由Tablet所在的表的标识符和Tablet的最后一 行编码而成的。METADATA的每一行都存储了大约1KB的内存数据。在一个大小适中的、容量限制为128MB的METADATA Tablet中,采用这种三层结构的存储模式,能够标识2^34个Tablet的地址(若是每一个Tablet存储128MB数据,那么一共能够存储 2^61字节数据)。

客户程序使用的库会缓存Tablet的位置信息。若是客户程序没有缓存某个Tablet的地址信息,或者发现它缓存的 地址信息不正确,客户程序就在树状的存储结构中递归的查询Tablet位置信息;若是客户端缓存是空的,那么寻址算法须要经过三次网络来回通讯寻址,这其 中包括了一次Chubby读操做;若是客户端缓存的地址信息过时了,那么寻址算法可能须要最多6次网络来回通讯才能更新数据,由于只有在缓存中没有查到数 据的时候才能发现数据过时(alex注:其中的三次通讯发现缓存过时,另外三次更新缓存数据)(假设METADATA的Tablet没 有被频繁的移动)。尽管Tablet的地址信息是存放在内存里的,对它的操做没必要访问GFS文件系统,可是,一般咱们会经过预取Tablet地址来进一步 的减小访问的开销:每次须要从METADATA表中读取一个Tablet的元数据的时候,它都会多读取几个Tablet的元数据。

在METADATA表中还存储了次级信息(alex注:secondary information),包括每一个Tablet的事件日志(例如,何时一个服务器开始为该Tablet提供服务)。这些信息有助于排查错误和性能分析。

5.2Tablet分配

在 任何一个时刻,一个Tablet只能分配给一个Tablet服务器。Master服务器记录了当前有哪些活跃的Tablet服务器、哪些Tablet分配 给了哪些Tablet服务器、哪些Tablet尚未被分配。当一个Tablet尚未被分配、而且恰好有一个Tablet服务器有足够的空闲空间装载该 Tablet时,Master服务器会给这个Tablet服务器发送一个装载请求,把Tablet分配给这个服务器。

BigTable使用 Chubby跟踪记录Tablet服务器的状态。当一个Tablet服务器启动时,它在Chubby的一个指定目录下创建一个有惟一性名字的文件,而且获 取该文件的独占锁。Master服务器实时监控着这个目录(服务器目录),所以Master服务器可以知道有新的Tablet服务器加入了。若是 Tablet服务器丢失了Chubby上的独占锁 — 好比因为网络断开致使Tablet服务器和Chubby的会话丢失 — 它就中止对Tablet提供服务。(Chubby提供了一种高效的机制,利用这种机制,Tablet服务器可以在不增长网络负担的状况下知道它是否还持有 锁)。只要文件还存在,Tablet服务器就会试图从新得到对该文件的独占锁;若是文件不存在了,那么Tablet服务器就不能再提供服务了,它会自行退 出(alex注:so it killsitself)。当Tablet服务器终止时(好比,集群的管理系统将运行该Tablet服务器的主机从集群中移除),它会尝试释放它持有的文件锁,这样一来,Master服务器就能尽快把Tablet分配到其它的Tablet服务器。

Master 服务器负责检查一个Tablet服务器是否已经再也不为它的Tablet提供服务了,而且要尽快从新分配它加载的Tablet。Master服务器经过轮询 Tablet服务器文件锁的状态来检测什么时候Tablet服务器再也不为Tablet提供服务。若是一个Tablet服务器报告它丢失了文件锁,或者 Master服务器最近几回尝试和它通讯都没有获得响应,Master服务器就会尝试获取该Tablet服务器文件的独占锁;若是Master服务器成功 获取了独占锁,那么就说明Chubby是正常运行的,而Tablet服务器要么是宕机了、要么是不能和Chubby通讯了,所以,Master服务器就删 除该Tablet服务器在Chubby上的服务器文件以确保它再也不给Tablet提供服务。一旦Tablet服务器在Chubby上的服务器文件被删除 了,Master服务器就把以前分配给它的全部的Tablet放入未分配的Tablet集合中。为了确保Bigtable集群在Master服务器和 Chubby之间网络出现故障的时候仍然可使用,Master服务器在它的Chubby会话过时后主动退出。可是无论怎样,如同咱们前面所描述 的,Master服务器的故障不会改变现有Tablet在Tablet服务器上的分配状态。

当集群管理系统启动了一个Master服务器之 后,Master服务器首先要了解当前Tablet的分配状态,以后才可以修改分配状态。Master服务器在启动的时候执行如下步骤: (1)Master服务器从Chubby获取一个惟一的Master锁,用来阻止建立其它的Master服务器实例;(2)Master服务器扫描 Chubby的服务器文件锁存储目录,获取当前正在运行的服务器列表;(3)Master服务器和全部的正在运行的Tablet表服务器通讯,获取每一个 Tablet服务器上Tablet的分配信息;(4)Master服务器扫描METADATA表获取全部的Tablet的集合。在扫描的过程当中,当 Master服务器发现了一个尚未分配的Tablet,Master服务器就将这个Tablet加入未分配的Tablet集合等待合适的时机分配。

可 能会遇到一种复杂的状况:在METADATA表的Tablet尚未被分配以前是不可以扫描它的。所以,在开始扫描以前(步骤4),若是在第三步的扫描过 程中发现Root Tablet尚未分配,Master服务器就把Root Tablet加入到未分配的Tablet集合。这个附加操做确保了Root Tablet会被分配。因为Root Tablet包括了全部METADATA的Tablet的名字,所以Master服务器扫描完Root Tablet之后,就获得了全部的METADATA表的Tablet的名字了。

保存现有Tablet的集合只有在如下事件发生时才会改变: 创建了一个新表或者删除了一个旧表、两个Tablet被合并了、或者一个Tablet被分割成两个小的Tablet。Master服务器能够跟踪记录全部 这些事件,由于除了最后一个事件外的两个事件都是由它启动的。Tablet分割事件须要特殊处理,由于它是由Tablet服务器启动。在分割操做完成之 后,Tablet服务器经过在METADATA表中记录新的Tablet的信息来提交这个操做;当分割操做提交以后,Tablet服务器会通知 Master服务器。若是分割操做已提交的信息没有通知到Master服务器(可能两个服务器中有一个宕机了),Master服务器在要求Tablet服 务器装载已经被分割的子表的时候会发现一个新的Tablet。经过对比METADATA表中Tablet的信息,Tablet服务器会发现Master服 务器要求其装载的Tablet并不完整,所以,Tablet服务器会从新向Master服务器发送通知信息。

5.3Tablet服务

如图5所示,Tablet的持久化状态信息保存在GFS上。更新操做提交到REDO日志中(alex注:Updates are committed to a commit log that stores redo records)。 在这些更新操做中,最近提交的那些存放在一个排序的缓存中,咱们称这个缓存为memtable;较早的更新存放在一系列SSTable中。为了恢复一个 Tablet,Tablet服务器首先从METADATA表中读取它的元数据。Tablet的元数据包含了组成这个Tablet的SSTable的列表, 以及一系列的Redo Point(alex注:a set of redo points),这些Redo Point指向可能含有该Tablet数据的已提交的日志记录。Tablet服务器把SSTable的索引读进内存,以后经过重复Redo Point以后提交的更新来重建memtable。

当 对Tablet服务器进行写操做时,Tablet服务器首先要检查这个操做格式是否正确、操做发起者是否有执行这个操做的权限。权限验证的方法是经过从一 个Chubby文件里读取出来的具备写权限的操做者列表来进行验证(这个文件几乎必定会存放在Chubby客户缓存里)。成功的修改操做会记录在提交日志 里。能够采用批量提交方式(alex注:group commit)来提升包含大量小的修改操做的应用程序的吞吐量【13,16】。当一个写操做提交后,写的内容插入到memtable里面。

当对Tablet服务器进行读操做时,Tablet服务器会做相似的完整性和权限检查。一个有效的读操做在一个由一系列SSTable和memtable合并的视图里执行。因为SSTable和memtable是按字典排序的数据结构,所以能够高效生成合并视图。

当进行Tablet的合并和分割时,正在进行的读写操做可以继续进行。

5.4Compactions

(alex注:这个词挺简单,可是在这节里面挺难翻译的。应该是空间缩减的意思,可是彷佛又不能彻底归纳它在上下文中的意思,干脆,不翻译了)

随着写操做的执行,memtable的大小不断增长。当memtable的尺寸到达一个门限值的时候,这个memtable就会被冻结,而后建立一个新的memtable;被冻结住memtable会被转换成SSTable,而后写入GFS(alex注:咱们称这种Compaction行为为Minor Compaction)。MinorCompaction过程有两个目的:shrink(alex注:shrink是数据库用语,表示空间收缩)Tablet服务器使用的内存,以及在服务器灾难恢复过程当中,减小必须从提交日志里读取的数据量。在Compaction过程当中,正在进行的读写操做仍能继续。

每 一次Minor Compaction都会建立一个新的SSTable。若是Minor Compaction过程不停滞的持续进行下去,读操做可能须要合并来自多个SSTable的更新;不然,咱们经过按期在后台执行Merging Compaction过程合并文件,限制这类文件的数量。Merging Compaction过程读取一些SSTable和memtable的内容,合并成一个新的SSTable。只要Merging Compaction过程完成了,输入的这些SSTable和memtable就能够删除了。

合并全部的SSTable并生成一个新的 SSTable的Merging Compaction过程叫做Major Compaction。由非Major Compaction产生的SSTable可能含有特殊的删除条目,这些删除条目可以隐藏在旧的、可是依然有效的SSTable中已经删除的数据(alex 注:使人费解啊,原文是SSTables produced by non-major compactions can contain specialdeletion entries that suppress deleted data in older SSTables that are stilllive)。而MajorCompaction过程生成的SSTable不包含已经删除的信息或数据。 Bigtable循环扫描它全部的Tablet,而且按期对它们执行Major Compaction。Major Compaction机制容许Bigtable回收已经删除的数据占有的资源,而且确保BigTable能及时清除已经删除的数据(alex注:实际是回收资源。数据删除后,它占有的空间并不能立刻重复利用;只有空间回收后才能重复使用),这对存放敏感数据的服务是很是重要。

6 优化

上一章咱们描述了Bigtable的实现,咱们还须要不少优化工做才能使Bigtable到达用户要求的高性能、高可用性和高可靠性。本章描述了Bigtable实现的其它部分,为了更好的强调这些优化工做,咱们将深刻细节。

局部性群组

客 户程序能够将多个列族组合成一个局部性群族。对Tablet中的每一个局部性群组都会生成一个单独的SSTable。将一般不会一块儿访问的列族分割成不一样的 局部性群组能够提升读取操做的效率。例如,在Webtable表中,网页的元数据(好比语言和Checksum)能够在一个局部性群组中,网页的内容能够 在另一个群组:当一个应用程序要读取网页的元数据的时候,它没有必要去读取全部的页面内容。

此外,能够以局部性群组为单位设定一些有用的 调试参数。好比,能够把一个局部性群组设定为所有存储在内存中。Tablet服务器依照惰性加载的策略将设定为放入内存的局部性群组的SSTable装载 进内存。加载完成以后,访问属于该局部性群组的列族的时候就没必要读取硬盘了。这个特性对于须要频繁访问的小块数据特别有用:在Bigtable内部,咱们 利用这个特性提升METADATA表中具备位置相关性的列族的访问速度。

压缩

客户程序能够控制一个局部性群组的SSTable是否须要压缩;若是须要压缩,那么以什么格式来压缩。每一个SSTable的块(块的大小由局部性群组的优化参数指定)都使用用户指定的压缩格式来压缩。虽然分块压缩浪费了少许空间(alex注:相比于对整个SSTable进行压缩,分块压缩压缩率较低),可是,咱们在只读取SSTable的一小部分数据的时候就没必要解压整个文件了。不少客户程序使用了“两遍”的、可定制的压缩方式。第一遍采用Bentleyand McIlroy’s方式[6],这种方式在一个很大的扫描窗口里对常见的长字符串进行压缩;第二遍是采用快速压缩算法,即在一个16KB的小扫描窗口中寻 找重复数据。两个压缩的算法都很快,在如今的机器上,压缩的速率达到100-200MB/s,解压的速率达到400-1000MB/s。

虽 然咱们在选择压缩算法的时候重点考虑的是速度而不是压缩的空间,可是这种两遍的压缩方式在空间压缩率上的表现也是使人惊叹。好比,在Webtable的例 子里,咱们使用这种压缩方式来存储网页内容。在一次测试中,咱们在一个压缩的局部性群组中存储了大量的网页。针对实验的目的,咱们没有存储每一个文档全部版 本的数据,咱们仅仅存储了一个版本的数据。该模式的空间压缩比达到了10:1。这比传统的Gzip在压缩HTML页面时3:1或者4:1的空间压缩比好的 多;“两遍”的压缩模式如此高效的缘由是因为Webtable的行的存放方式:从同一个主机获取的页面都存在临近的地方。利用这个特性,Bentley- McIlroy算法能够历来自同一个主机的页面里找到大量的重复内容。不只仅是Webtable,其它的不少应用程序也经过选择合适的行名来将类似的数据 聚簇在一块儿,以获取较高的压缩率。当咱们在Bigtable中存储同一份数据的多个版本的时候,压缩效率会更高。

经过缓存提升读操做的性能

为 了提升读操做的性能,Tablet服务器使用二级缓存的策略。扫描缓存是第一级缓存,主要缓存Tablet服务器经过SSTable接口获取的Key- Value对;Block缓存是二级缓存,缓存的是从GFS读取的SSTable的Block。对于常常要重复读取相同数据的应用程序来讲,扫描缓存很是 有效;对于常常要读取刚刚读过的数据附近的数据的应用程序来讲,Block缓存更有用(例如,顺序读,或者在一个热点的行的局部性群组中随机读取不一样的 列)。

Bloom过滤器

(alex注:Bloom,又叫布隆过滤器,什么意思?请参考Google黑板报http://googlechinablog.com/2007/07/bloom-filter.html请务必先认真阅读)

如 5.3节所述,一个读操做必须读取构成Tablet状态的全部SSTable的数据。若是这些SSTable不在内存中,那么就须要屡次访问硬盘。咱们通 过容许客户程序对特定局部性群组的SSTable指定Bloom过滤器【7】,来减小硬盘访问的次数。咱们可使用Bloom过滤器查询一个 SSTable是否包含了特定行和列的数据。对于某些特定应用程序,咱们只付出了少许的、用于存储Bloom过滤器的内存的代价,就换来了读操做显著减小 的磁盘访问的次数。使用Bloom过滤器也隐式的达到了当应用程序访问不存在的行或列时,大多数时候咱们都不须要访问硬盘的目的。

Commit日志的实现

若是咱们把对每一个Tablet的操做的Commit日志都存在一个单独的文件的话,那么就会产生大量的文件,而且这些文件会并行的写入GFS。根据GFS服务器底层文件系统实现的方案,要把这些文件写入不一样的磁盘日志文件时(alex注:differentphysicallog files), 会有大量的磁盘Seek操做。另外,因为批量提交(alex注:groupcommit)中操做的数目通常比较少,所以,对每一个Tablet设置单独的日 志文件也会给批量提交本应具备的优化效果带来很大的负面影响。为了不这些问题,咱们设置每一个Tablet服务器一个Commit日志文件,把修改操做的 日志以追加方式写入同一个日志文件,所以一个实际的日志文件中混合了对多个Tablet修改的日志记录。

使用单个日志显著提升了普通操做的 性能,可是将恢复的工做复杂化了。当一个Tablet服务器宕机时,它加载的Tablet将会被移到不少其它的Tablet服务器上:每一个Tablet服 务器都装载不多的几个原来的服务器的Tablet。当恢复一个Tablet的状态的时候,新的Tablet服务器要从原来的Tablet服务器写的日志中 提取修改操做的信息,并从新执行。然而,这些Tablet修改操做的日志记录都混合在同一个日志文件中的。一种方法新的Tablet服务器读取完整的 Commit日志文件,而后只重复执行它须要恢复的Tablet的相关修改操做。使用这种方法,假若有100台Tablet服务器,每台都加载了失效的 Tablet服务器上的一个Tablet,那么,这个日志文件就要被读取100次(每一个服务器读取一次)。

为了不屡次读取日志文件,咱们 首先把日志按照关键字(table,row name,log sequence number)排序。排序以后,对同一个Tablet的修改操做的日志记录就连续存放在了一块儿,所以,咱们只要一次磁盘Seek操做、以后顺序读取就能够 了。为了并行排序,咱们先将日志分割成64MB的段,以后在不一样的Tablet服务器对段进行并行排序。这个排序工做由Master服务器来协同处理,并 且在一个Tablet服务器代表本身须要从Commit日志文件恢复Tablet时开始执行。

在向GFS中写Commit日志的时候可能会 引发系统颠簸,缘由是多种多样的(好比,写操做正在进行的时候,一个GFS服务器宕机了;或者链接三个GFS副本所在的服务器的网络拥塞或者过载了)。为 了确保在GFS负载高峰时修改操做还能顺利进行,每一个Tablet服务器实际上有两个日志写入线程,每一个线程都写本身的日志文件,而且在任什么时候刻,只有一 个线程是工做的。若是一个线程的在写入的时候效率很低,Tablet服务器就切换到另一个线程,修改操做的日志记录就写入到这个线程对应的日志文件中。 每一个日志记录都有一个序列号,所以,在恢复的时候,Tablet服务器可以检测出并忽略掉那些因为线程切换而致使的重复的记录。

Tablet恢复提速

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

利用不变性

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

因 为SSTable是不变的,所以,咱们能够把永久删除被标记为“删除”的数据的问题,转换成对废弃的SSTable进行垃圾收集的问题了。每一个 Tablet的SSTable都在METADATA表中注册了。Master服务器采用“标记-删除”的垃圾回收方式删除SSTable集合中废弃的 SSTable【25】,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个客户端,分配 方式是:只要客户程序处理完上一个区间的数据,调度程序就把后续的、还没有处理的区间分配给它。这种动态分配的方式有助于减小客户机上同时运行的其它进程对 性能的影响。咱们在每一个列关键字下写入一个单独的字符串。每一个字符串都是随机生成的、所以也没有被压缩(alex注:参考第6节的压缩小节)。另外,不一样列关键字下的字符串也是不一样的,所以也就不存在跨行的压缩。随机写入基准测试采用相似的方法,除了行关键字在写入前先作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服务器的性能。随机读的性能比其它操做慢一个数量级或以上(alex注:by the order of magnitude or more) 。 每一个随机读操做都要经过网络从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是不可用的),另外一个是咱们的基准测试程序产生的负载会有波动(alex注:the load generated by our benchmarks shifts around as the benchmarkprogresses)

随 机读基准测试的测试结果显示,随机读的性能随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 的。

8.1Google 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%。

8.2Google 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的列族。

8.3 个性化查询

个性化查询(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-fastfailture,指fail不立刻stop,在短期内return错误信息,而后再stop)。好比,咱们遇到过下面这些类型的错误致使的问题:内存数据损坏、网络中断、时钟误差、机器挂起、扩展的和非对称的网络分区(alex注:extended and asymmetric network partitions,不明白什么意思。partition也有中断的意思,可是我不知道如何用在这里)、我 们使用的其它系统的Bug(好比Chubby)、GFS配额溢出、计划内和计划外的硬件维护。咱们在解决这些问题的过程当中学到了不少经验,咱们经过修改协 议来解决这些问题。好比,咱们在咱们的RPC机制中加入了Checksum。咱们在设计系统的部分功能时,不对其它部分功能作任何的假设,这样的作法解决 了其它的一些问题。好比,咱们再也不假设一个特定的Chubby操做只返回错误码集合中的一个值。

另一个教训是,咱们明白了在完全了解一个 新特性会被如何使用以后,再决定是否添加这个新特性是很是重要的。好比,咱们开始计划在咱们的API中支持一般方式的事务处理。可是因为咱们还不会立刻用 到这个功能,所以,咱们并无去实现它。如今,Bigtable上已经有了不少的实际应用,咱们能够检查它们真实的需求;咱们发现,大可能是应用程序都只是 须要单个行上的事务功能。有些应用须要分布式的事务功能,分布式事务大多数状况下用于维护二级索引,所以咱们增长了一个特殊的机制去知足这个需求。新的机 制在通用性上比分布式事务差不少,可是它更有效(特别是在更新操做的涉及上百行数据的时候),并且很是符合咱们的“跨数据中心”复制方案的优化策略。

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

对咱们来讲,最宝贵的经验是简单设计的价值。考虑到咱们系统的代码量(大约100000行生产代码(alex注:non-test code)), 以及随着时间的推移,新的代码以各类难以预料的方式加入系统,咱们发现简洁的设计和编码给维护和调试带来的巨大好处。这方面的一个例子是咱们的 Tablet服务器成员协议。咱们初版的协议很简单:Master服务器周期性的和Tablet服务器签定租约,Tablet服务器在租约过时的时候 Kill掉本身的进程。不幸的是,这个协议在遇到网络问题时会大大下降系统的可用性,也会大大增长Master服务器恢复的时间。咱们屡次从新设计这个协 议,直到它可以很好的处理上述问题。可是,更不幸的是,最终的协议过于复杂了,而且依赖一些Chubby不多被用到的特性。咱们发现咱们浪费了大量的时间 在调试一些古怪的问题(alex注:obscure corner cases),有些是Bigtable代码的问题,有些事Chubby代码的问题。最后,咱们只好废弃了这个协议,从新制订了一个新的、更简单、只使用Chubby最普遍使用的特性的协议。

10 相关工做

Boxwood 【24】项目的有些组件在某些方面和Chubby、GFS以及Bigtable相似,由于它也提供了诸如分布式协议、锁、分布式Chunk存储以及分布式 B-tree存储。Boxwood与Google的某些组件尽管功能相似,可是Boxwood的组件提供更底层的服务。Boxwood项目的目的是提供创 建相似文件系统、数据库等高级服务的基础构件,而Bigtable的目的是直接为客户程序的数据存储需求提供支持。

如今有很多项目已经攻克 了不少难题,实现了在广域网上的分布式数据存储或者高级服务,一般是“Internet规模”的。这其中包括了分布式的Hash表,这项工做由一些相似 CAN【29】、Chord【32】、Tapestry【37】和Pastry【30】的项目率先发起。这些系统的主要关注点和Bigtable不一样,比 如应对各类不一样的传输带宽、不可信的协做者、频繁的更改配置等;另外,去中心化和Byzantine灾难冗余(alex注:Byzantine, 即拜占庭式的风格,也就是一种复杂诡秘的风格。Byzantine Fault表示:对于处理来讲,当发错误时处理器并不中止接收输出,也不中止输出,错就错了,只管算,对于这种错误来讲,这样可真是够麻烦了,由于用户根 本不知道错误发生了,也就根本谈不上处理错误了。在多处理器的状况下,这种错误可能致使运算正确结果的处理器也产生错误的结果,这样事情就更麻烦了,因此 必定要避免处理器产生这种错误。)也不是Bigtable的目的。

就提供给应用程序开发者的分布式数据存储模型而言,咱们相信, 分布式B-Tree或者分布式Hash表提供的Key-value pair方式的模型有很大的局限性。Key-value pair模型是颇有用的组件,可是它们不该该是提供给开发者惟一的组件。咱们选择的模型提供的组件比简单的Key-value pair丰富的多,它支持稀疏的、半结构化的数据。另外,它也足够简单,可以高效的处理平面文件;它也是透明的(经过局部性群组),容许咱们的使用者对系 统的重要行为进行调整。

有些数据库厂商已经开发出了并行的数据库系统,可以存储海量的数据。Oracle的RAC【27】使用共享磁盘存储 数据(Bigtable使用GFS),而且有一个分布式的锁管理系统(Bigtable使用Chubby)。IBM并行版本的DB2【4】基于一种相似于 Bigtable的、不共享任何东西的架构(a shared-nothing architecture)【33】。每一个DB2的服务器都负责处理存储在一个关系型数据库中的表中的行的一个子集。这些产品都提供了一个带有事务功能的 完整的关系模型。

Bigtable的局部性群组提供了相似于基于列的存储方案在压缩和磁盘读取方面具备的性能;这些以列而不是行的方式组织 数据的方案包括C-Store【1,34】、商业产品Sybase IQ【15,36】、SenSage【31】、KDB+【22】,以及MonetDB/X100【38】的ColumnDM存储层。另一种在平面文件中 提供垂直和水平数据分区、而且提供很好的数据压缩率的系统是AT&T的Daytona数据库【19】。局部性群组不支持Ailamaki系统中描 述的CPU缓存级别的优化【2】。

Bigtable采用memtable和SSTable存储对表的更新的方法与Log- StructuredMerge Tree【26】存储索引数据更新的方法相似。这两个系统中,排序的数据在写入到磁盘前都先存放在内存中,读取操做必须从内存和磁盘中合并数据产生最终的 结果集。

C-Store和Bigtable有不少类似点:两个系统都采用Shared-nothing架构,都有两种不一样的数据结构,一种 用于当前的写操做,另一种存放“长时间使用”的数据,而且提供一种机制在两个存储结构间搬运数据。两个系统在API接口函数上有很大的不一样:C- Store操做更像关系型数据库,而Bigtable提供了低层次的读写操做接口,而且设计的目标是可以支持每台服务器每秒数千次操做。C-Store同 时也是个“读性能优化的关系型数据库”,而Bigtable对读和写密集型应用都提供了很好的性能。

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

11 结论

我 们已经讲述完了Bigtable,Google的一个分布式的结构化数据存储系统。Bigtable的集群从2005年4月开始已经投入使用了,在此之 前,咱们花了大约7人年设计和实现这个系统。截止到2006年4月,已经有超过60个项目使用Bigtable了。咱们的用户对Bigtable提供的高 性能和高可用性很满意,随着时间的推移,他们能够根据本身的系统对资源的需求增长状况,经过简单的增长机器,扩展系统的承载能力。

因为 Bigtable提供的编程接口并不常见,一个有趣的问题是:咱们的用户适应新的接口有多难?新的使用者有时不太肯定使用Bigtable接口的最佳方 法,特别是在他们已经习惯于使用支持通用事务的关系型数据库的接口的状况下。可是,Google内部不少产品都成功的使用了Bigtable的事实证实 了,咱们的设计在实践中行之有效。

咱们如今正在对Bigtable加入一些新的特性,好比支持二级索引,以及支持多Master节点的、跨 数据中心复制的Bigtable的基础构件。咱们如今已经开始将Bigtable部署为服务供其它的产品团队使用,这样不一样的产品团队就不须要维护他们自 己的Bigtable集群了。随着服务集群的扩展,咱们须要在Bigtable系统内部处理更多的关于资源共享的问题了【3,5】。

最后, 咱们发现,建设Google本身的存储解决方案带来了不少优点。经过为Bigtable设计咱们本身的数据模型,是咱们的系统极具灵活性。另外,因为咱们 全面控制着Bigtable的实现过程,以及Bigtable使用到的其它的Google的基础构件,这就意味着咱们在系统出现瓶颈或效率低下的状况时, 可以快速的解决这些问题。

相关文章
相关标签/搜索