Google’s BigTable 原理 (翻译)
题记:google 的成功除了一个个出色的创意外,还因为有 Jeff Dean 这样的软件架构天才。
—— 编者
官方的 Google Reader blog 中有对BigTable 的解释。这是Google 内部开发的一个用来处理大数据量的系统。这种系统适合处理半结构化的数据比如 RSS 数据源。 以下发言 是 Andrew Hitchcock 在 2005 年10月18号 基于: Google 的工程师 Jeff Dean 在华盛顿大学的一次谈话 (Creative Commons License).
首先,BigTable 从 2004 年初就开始研发了,到现在为止已经用了将近8个月。(2005年2月)目前大概有100个左右的服务使用BigTable,比如: Print,Search History,Maps和 Orkut。根据Google的一贯做法,内部开发的BigTable是为跑在廉价的PC机上设计的。BigTable 让Google在提供新服务时的运行成本降低,最大限度地利用了计算能力。BigTable 是建立在 GFS ,Scheduler ,Lock Service 和 MapReduce 之上的。
每个Table都是一个多维的稀疏图 sparse map。Table 由行和列组成,并且每个存储单元 cell 都有一个时间戳。在不同的时间对同一个存储单元cell有多份拷贝,这样就可以记录数据的变动情况。在他的例子中,行是URLs ,列可以定义一个名字,比如:contents。Contents 字段就可以存储文件的数据。或者列名是:”language”,可以存储一个“EN”的语言代码字符串。
为了管理巨大的Table,把Table根据行分割,这些分割后的数据统称为:Tablets。每 个Tablets大概有 100-200 MB,每个机器存储100个左右的 Tablets。底层的架构是:GFS。由于GFS是一种分布式的文件系统,采用Tablets的机制后,可以获得很好的负载均衡。比如:可以把经常响应 的表移动到其他空闲机器上,然后快速重建。
Tablets在系统中的存储方式是不可修改的 immutable 的SSTables,一台机器一个日志文件。当系统的内存满后,系统会压缩一些Tablets。由于Jeff在论述这点的时候说的很快,所以我没有时间把听到的都记录下来,因此下面是一个大概的说明:
压缩分为:主要和次要的两部分。次要的压缩仅仅包括几个Tablets,而主要的压缩时关于整个系统的压缩。主压缩有回收硬盘空间的功能。 Tablets的位置实际上是存储在几个特殊的BigTable的存储单元cell中。看起来这是一个三层的系统。
客户端有一个指向METAO的Tablets的指针。如果METAO的Tablets被频繁使用,那个这台机器就会放弃其他的tablets专门支持 METAO这个Tablets。METAO tablets 保持着所有的META1的tablets的记录。这些tablets中包含着查找tablets的实际位置。(老实说翻译到这里,我也不太明白。)在这个系统中不存在大的瓶颈,因为被频繁调用的数据已经被提前获得并进行了缓存。
现在我们返回到对列的说明:列是类似下面的形式: family:optional_qualifier。在他的例子中,行:www.search-analysis.com 也许有列:”contents:其中包含html页面的代码。 “ anchor:cnn.com/news” 中包含着 相对应的url,”anchor:www.search-analysis.com/” 包含着链接的文字部分。列中包含着类型信息。
(翻译到这里我要插一句,以前我看过一个关于万能数据库的文章,当时很激动,就联系了作者,现在回想起来,或许google的 bigtable 才是更好的方案,切不说分布式的特性,就是这种建华的表结构就很有用处。)
注意这里说的是列信息,而不是列类型。列的信息是如下信息,一般是:属性/规则。 比如:保存n份数据的拷贝或者保存数据n天长等等。当 tablets 重新建立的时候,就运用上面的规则,剔出不符合条件的记录。由于设计上的原因,列本身的创建是很容易的,但是跟列相关的功能确实非常复杂的,比如上文提到 的 类型和规则信息等。为了优化读取速度,列的功能被分割然后以组的方式存储在所建索引的机器上。这些被分割后的组作用于 列 ,然后被分割成不同的 SSTables。这种方式可以提高系统的性能,因为小的,频繁读取的列可以被单独存储,和那些大的不经常访问的列隔离开来。
在一台机器上的所有的 tablets 共享一个log,在一个包含1亿的tablets的集群中,这将会导致非常多的文件被打开和写操作。新的log块经常被创建,一般是64M大小,这个 GFS的块大小相等。当一个机器down掉后,控制机器就会重新发布他的log块到其他机器上继续进行处理。这台机器重建tablets然后询问控制机器处理结构的存储位置,然后直接对重建后的数据进行处理。
这个系统中有很多冗余数据,因此在系统中大量使用了压缩技术。 Dean 对压缩的部分说的很快,我没有完全记下来,所以我还是说个大概吧:压缩前先寻找相似的 \行,列,和时间数据。
他们使用不同版本的: BMDiff 和 Zippy 技术。
BMDiff 提供给他们非常快的写速度: 100MB/s – 1000MB/s 。Zippy 是和 LZW 类似的。Zippy 并不像 LZW 或者 gzip 那样压缩比高,但是他处理速度非常快。
Dean 还给了一个关于压缩 web 蜘蛛数据的例子。这个例子的蜘蛛 包含 2.1B 的页面,行按照以下的方式命名:“com.cnn.www/index.html:http”.在未压缩前的web page 页面大小是:45.1 TB ,压缩后的大小是:4.2 TB , 只是原来的 9.2%。Links 数据压缩到原来的 13.9% , 链接文本数据压缩到原来的 12.7%。
Google 还有很多没有添加但是已经考虑的功能。
1. 数据操作表达式,这样可以把脚本发送到客户端来提供修改数据的功能。
2. 多行数据的事物支持。
3. 提高大数据存储单元的效率。
4. BigTable 作为服务运行。
好像:每个服务比如: maps 和 search history 历史搜索记录都有他们自己的集群运行 BigTable。
他们还考虑运行一个全局的 BigTable 系统,但这需要比较公平的分割资源和计算时间。
大表(Bigtable):结构化数据的分布存储系统
http://labs.google.com/papers/bigtable-osdi06.pdf
{中是译者评论,程序除外}
{本文的翻译可能有不准确的地方,详细资料请参考原文.}
摘要
bigtable是设计来分布存储大规模结构化数据的,从设计上它可以扩展到上2^50字节,分布存储在几千个普通服务器上.google的很多项目使用 bt来存储数据,包括网页查询,google earth和google金融.这些应用程序对bt的要求各不相同:数据大小(从URL到网页到卫星图象)不同,反应速度不同(从后端的大批处理到实时数 据服务).对于不同的要求,bt都成功的提供了灵活高效的服务.在本文中,我们将描述bt的数据模型.这个数据模型让用户动态的控制数据的分布和结构.我 们还将描述BT的设计和实现.
1.介绍
在过去两年半里,我们设计,实现并部署了BT.BT是用来分布存储和管理结构化数据的.BT的设计使它能够管理2^50 bytes(petabytes)数据,并可以部署到上千台机器上.BT完成了以下目标:应用广泛,可扩展,高性能和高可用性(high availability). 包括google analytics, google finance, orkut, personalized search, writely和google earth在内的60多个项目都使用BT.这些应用对BT的要求各不相同,有的需要高吞吐量的批处理,有的需要快速反应给用户数据.它们使用的BT集群也各不相同,有的只有几台机器,有的有上千台,能够存储2^40字节(terabytes)数据.
BT在很多地方和数据库很类似:它使用了很多数据库的实现策略.并行数据库[14]和内存数据库[13]有可扩展性和高性能,但是BT的界面不同.BT不支持完全的关系数据模型;而是为客户提供了简单的数据模型,让客户来动态控制数据的分布和格式{就是只存储字串,格式由客户来解释},并允许客户推断底层存储数据的局部性{以提高访问速度}.数据下标是行和列的名字,数据本身可以是任何字串.BT的数据是字串,没有解释{类型等}.客户会在把各种结构或者半结构化的数据串行化{比如说日期串}到数据中.通过仔细选择数据表示,客户可以控制数据的局部化.最后,可以使用BT模式来控制数据是放在内存里还是在硬盘上.{就是说用模式,你可以把数据放在离应用最近的地方.毕竟程序在一个时间只用到一块数据.在体系结构里,就是:locality, locality, locality}
第二节描述数据模型细节.第三节关于客户API概述.第四节简介BT依赖的google框架.第五节描述BT的实现关键部分.第6节叙述提高BT 性 能的一些调整.第7节提供BT性能的数据.在第8节,我们提供BT的几个使用例子,第9节是经验教训.在第10节,我们列出相关研究.最后是我们的结论.
2.数据模型
BT是一个稀疏的,长期存储的{存在硬盘上},多维度的,排序的映射表.这张表的索引是行关键字,列关键字和时间戳.每个值是一个不解释的字符数组.{数据都是字符串,没类型,客户要解释就自力更生吧}.
(row:string, column:string,time:int64)->string {能编程序的都能读懂,不翻译了}
我们仔细查看过好些类似bigtable的系统之后定下了这个数据模型。举一个具体例子(它促使我们做出某些设计决定), 比如我们想要存储大量网页及相关信息,以用于很多不同的项目;我们姑且叫它Webtable。在Webtable里,我们将用URL作为行关键字,用网页 的某些属性作为列名,把网页内容存在contents:列中并用获取该网页的时间戳作为标识,如图一所示。
图一:一个存储Web网页的范例列表片断。行名是一个反向URL{即com.cnn.www}。contents列族{原文用 family,译为族,详见列族} 存放网页内容,anchor列族存放引用该网页的锚链接文本。CNN的主页被Sports Illustrater{即所谓SI,CNN的王牌体育节目}和MY-look的主页引用,因此该行包含了名叫“anchor:cnnsi.com”和 “anchhor:my.look.ca”的列。每个锚链接只有一个版本{由时间戳标识,如t9,t8};而contents列则有三个版本,分别由时间 戳t3,t5,和t6标识。
行表中的行关键字可以是任意字符串(目前支持最多64KB,多数情况下10-100字节足够了)。在一个行关键字下的每一个读写操作都是原子操作(不管读写这一行里多少个不同列),这是一个设计决定,这样在对同一行进行并发操作时,用户对于系统行为更容易理解和掌控。
Bigtable通过行关键字的字典序来维护数据。一张表可以动态划分成多个连续行。连续行在这里叫做“子表”{tablet},是数据分布和负载 均衡的单位。这样一来,读较少的连续行就比较有效率,通常只需要较少机器之间的通信即可。用户可以利用这个属性来选择行关键字,从而达到较好数据访问地域 性{locality}。举例来说,在Webtable里,通过反转URL中主机名的方式,可以把同一个域名下的网页组织成连续行。具体来说,可以把 maps.google.com/index.html中的数据存放在关键字com.google.maps/index.html下。按照相同或属性相 近的域名来存放网页可以让基于主机和基于域名的分析更加有效。
列族
一组列关键字组成了“列族”,这是访问控制的基本单位。同一列族下存放的所有数据通常都是同一类型(同一列族下的数据可压缩在一起)。列族必须先创 建,然后在能在其中的列关键字下存放数据;列族创建后,族中任何一个列关键字均可使用。我们希望,一张表中的不同列族不能太多(最多几百个),并且列族在 运作中绝少改变。作为对比,一张表可以有无限列。
列关键字用如下语法命名:列族:限定词。 列族名必须是看得懂{printable}的字串,而限定词可以是任意字符串。比如,Webtable可以有个列族叫language,存放撰写网页的语 言。我们在language列族中只用一个列关键字,用来存放每个网页的语言标识符。该表的另一个有用的列族是anchor;给列族的每一个列关键字代表 一个锚链接,如图一所示。而这里的限定词则是引用该网页的站点名;表中一个表项存放的是链接文本。
访问控制,磁盘使用统计,内存使用统计,均可在列族这个层面进行。在Webtable举例中,我们可以用这些控制来管理不同应用:有的应用添加新的基本数据,有的读取基本数据并创建引申的列族,有的则只能浏览数据(甚至可能因为隐私权原因不能浏览所有数据)。
时间戳
Bigtable表中每一个表项都可以包含同一数据的多个版本,由时间戳来索引。Bigtable的时间戳是64位整型。可以由Bigtable 来 赋值,表示准确到毫秒的“实时”;或者由用户应用程序来赋值。需要避免冲突的应用程序必须自己产生具有唯一性的时间戳。不同版本的表项内容按时间戳倒序排 列,即最新的排在前面。
为了简化对于不同数据版本的数据的管理,我们对每一个列族支持两个设定,以便于Bigtable对表项的版本自动进行垃圾清除。用户可以指明只保留表项的最后n个版本,或者只保留足够新的版本(比如,只保留最近7天的内容)。
在Webtable举例中,我们在contents:列中存放确切爬行一个网页的时间戳。如上所述的垃圾清除机制可以让我们只保留每个网页的最近三个版本。
3.API
BT的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);
图 2: 写入Bigtable.
在BT中,客户应用可以写或者删除值,从每个行中找值,或者遍历一个表中的数据子集.图2的c++代码是使用RowMutation抽象表示来进行一系列的更新(为保证代码精简,没有包括无关的细节).调用Apply函数,就对Webtable进行了一个原子修改:它为 http://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());
}
图3: 从Bigtable读数据.
图3的C++代码是使用Scanner抽象来遍历一个行内的所有锚点.客户可以遍历多个列族.有很多方法可以限制一次扫描中产生的行,列和时间戳. 例如,我们可以限制上面的扫描,让它只找到那些匹配正则表达式*.cnn.com的锚点,或者那些时间戳在当前时间前10天的锚点.
BT还支持其他一些更复杂的处理数据的功能.首先,BT支持单行处理.这个功能可以用来对存储在一个行关键字下的数据进行原子的读-修改-写操作. BT目前不支持跨行关键字的处理,但是它有一个界面,可以用来让客户进行批量的跨行关键字处理操作.其次,BT允许把每个表项用做整数记数器.最后,BT 支持在服务器的地址空间内执行客户端提供的脚本程序.脚本程序的语言是google开发的Sawzall[28]数据处理语言.目前,我们基于的 Sawzall的API还不允许客户脚本程序向BT内写数据,但是它允许多种形式的数据变换,基于任何表达式的过滤和通过多种操作符的摘要.
BT可以和MapReduce[12]一起使用.MapReduce是google开发的大规模并行计算框架.我们为编写了一套外层程序,使BT 可以作为MapReduce处理的数据源头和输出结果.
4.建立BT的基本单元
BT是建立在其他数个google框架单元上的.BT使用google分布式文件系统(GFS)[17]来存储日志和数据文件{yeah, right, what else can it use, FAT32?}.一个BT集群通常在一个共享的机器池中工作,池中的机器还运行其他的分布式应用{虽然机器便宜的跟白菜似的,可是一样要运行多个程序,命苦的象小白菜},BT和其他程序共享机器{BT的瓶颈是IO/内存,可以和CPU要求高的程序并存}.BT依赖集群管理系统来安排工作,在共享的机器上管理资源,处理失效机器并监视机器状态{典型的server farm结构,BT是上面的应用之一}.
BT内部存储数据的格式是google SSTable格式.一个SSTable提供一个从关键字到值的映射,关键字和值都可以是任意字符串.映射是排序的,存储的{不会因为掉电而丢失},不可改写的.可以进行以下操作:查询和一个关键字相关的值;或者根据给出的关键字范围遍历所有的关键字和值.在内部,每个SSTable包含一列数据块(通常每个块的大小是64KB,但是大小是可以配置的{索引大小是16 bits,应该是比较好的一个数}).块索引(存储在SSTable的最后)用来定位数据块;当打开SSTable的时候,索引被读入内存{性能}.每次查找都可以用一个硬盘搜索完成{根据索引算出数据在哪个道上,一个块应该不会跨两个道,没必要省那么点空间}:首先在内存中的索引里进行二分查找找到数据块的位置,然后再从硬盘读去数据块.最佳情况是:整个SSTable可以被放在内存里,这样一来就不必访问硬盘了.{想的美,前面是谁口口声声说要跟别人共享机器来着?你把内存占满了别人上哪睡去?}
BT还依赖一个高度可用的,存储的分布式数据锁服务Chubby[8]{看你怎么把这个high performance给说圆喽}.一个Chubby服务由5个活的备份{机器}构成,其中一个被这些备份选成主备份,并且处理请求.这个服务只有在大多数备份都活着并且互相通信的时候才是活的{绕口令?去看原文吧,是在有出错的前提下的冗余算法}.当有机器失效的时候,Chubby使用Paxos算法 [9,23]来保证备份的一致性{这个问题还是比较复杂的,建议去看引文了解一下问题本身}.Chubby提供了一个名字空间,里面包括了目录和小文件{万变不离其宗}.每个目录或者文件可以当成一个锁来用,读写文件操作都是原子化的.Chubby客户端的程序库提供了对Chubby文件的一致性缓存{究竟是提高性能还是降低性能?如果访问是分布的,就是提高性能}.每个Chubby客户维护一个和Chubby服务的会话.如果一个客户不能在一定时间内更新它的会话,这个会话就过期失效了{还是针对大server farm里机器失效的频率设计的}.当一个会话失效时,其拥有的锁和打开的文件句柄都失效{根本设计原则:失效时回到安全状态}.Chubby客户可以在文件和目录上登记回调函数,以获得改变或者会话过期的通知.{翻到这里,有没有人闻到java的味道了?}
BT使用Chubby来做以下几个任务:保证任何时间最多只有一个活跃的主备份;来存储BT数据的启动位置(参考5.1节);发现小表 (tablet)服务器,并完成tablet服务器消亡的善后(5.2节);存储BT数据的模式信息(每张表的列信息);以及存储访问权限列表.如果有相当长的时间Chubby不能访问,BT就也不能访问了{任何系统都有其弱点}.最近我们在使用11个Chubby服务实例的14个BT集群中度量了这个效果,由于Chubby不能访问而导致BT中部分数据不能访问的平均百分比是0.0047%,这里Chubby不能访问的原因是Chubby本身失效或者网络问题.单个集群里,受影响最大的百分比是0.0326%{基于文件系统的Chubby还是很稳定的}.