修正了alex翻译版的几个欠妥之处
Bigtable:一个分布式的结构化数据存储系统
摘要
Bigtable是一个管理结构化数据的分布式存储系统,它被设计用来处理海量数据:分布在数千台通用服务器上的PB级的数据。Google的很多项目将数据存储在Bigtable中,包括Web索引、Google Earth、Google Finance。这些应用对Bigtable提出的要求差异非常大,无论是在数据规模(从URL到网页到卫星图像)还是在响应速度上(从后端的批量处理到实时数据服务)。尽管应用需求差异很大,但是,针对所有Google这些产品,Bigtable还是成功地提供了一个灵活的、高性能的解决方案。本文描述了Bigtable提供的简单的数据模型,利用这个模型,用户可以动态的控制数据的布局和格式;并且我们还将描述Bigtable的设计和实现。
1 介绍
在过去两年半时间里,我们设计、实现并部署了一个用于管理结构化数据的分布式的存储系统—在Google,我们称之为Bigtable。Bigtable的设计目的是可靠地适应PB级别的数据和成千上万台机器。Bigtable已经实现了下面的几个目标:广泛的适用性、可扩展、高性能和高可用性。已经有超过60个Google的产品和项目在使用Bigtable,包括Google Analytics、Google Finance、Orkut、Personalized Search、Writely和Google Earth。这些产品使用Bigtable完成迥异的工作负载需求,这些需求从面向吞吐量的批处理作业到对终端用户而言延时敏感的数据服务。它们使用的Bigtable集群的配置也有很大的差异,从少数机器到成千上万台服务器,这些服务器里最多可存储几百TB的数据。
在很多方面,Bigtable和数据库很类似:它使用了很多数据库的实现策略。并行数据库【14】和内存数据库【13】已经具备可扩展性和高性能,但是Bigtable提供了一个和这些系统完全不同的接口。Bigtable不支持完整的关系数据模型;与之相反,Bigtable为客户提供了简单的数据模型,利用这个模型,客户可以动态控制数据的布局和格式(alex注:也就是对BigTable而言,数据是没有格式的,用数据库领域的术语说,就是数据没有Schema,用户自己去定义Schema),用户也可以自己推测(alex注:reason about)在底层存储中展示的数据的位置属性(alex注:位置相关性可以这样理解,比如树状结构,具有相同前缀的数据的存放位置接近。在读取的时候,可以把这些数据一次读取出来)。数据用行和列的名字进行索引,名字可以是任意的字符串。虽然客户程序通常会在把各种结构化或半结构化的数据串行化到字符串里,Bigtable同样将数据视为未经解析的字符串。通过仔细选择数据的模式,客户可以控制数据的位置。最后,可以通过BigTable的模式参数动态地控制数据读或写(control whether to serve data out of memory or from disk)。
第二节更详细地描述了数据模型,第三节概要介绍了客户端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都是一个未经解析的字节数组。
(row:string, column:string,time:int64)->string
我们在仔细分析了一个类似Bigtable的系统的种种潜在用途之后,决定选用这个数据模型。我们先举个具体的例子,这个例子促使我们做了很多设计决策;假设我们想要备份海量的网页及相关信息,这些数据可以用于很多不同的项目,我们姑且称这个特殊的表为Webtable。在Webtable里,我们使用URL作为行关键字,使用网页的各种属性(aspect)作为列名,网页的内容存在“contents:”列中,并用获取该网页的时间戳作为标识(alex注:即按照获取时间不同,存储了多个版本的网页数据),如图一所示。
图一:一个存储Web网页的例子的表的片断。行名是一个反向URL。contents列族容纳的是网页的内容,anchor列族容纳引用该网页的锚链接文本。CNN的主页被Sports Illustrater和MY-look的主页引用,因此该行包含了名为“anchor:cnnsi.com”和“anchhor:my.look.ca”的列。每个锚链接数据项只有一个版本(alex注:注意时间戳标识了列的版本,t9和t8分别标识了两个锚链接的版本);而contents列则有三个版本,分别由时间戳t3,t5,和t6标识。
行
表中的行关键字是任意字符串(目前支持最大64KB的字符串,但是对大多数用户,10-100个字节就足够了)。在单一行关键字下的每一个读或者写操作都是原子的(不管在这一行里被读或者写的不同列的数目),这个设计决策能够使用户很容易地推测(reason about)对同一个行进行并发更新操作时的系统行为。
Bigtable通过行关键字的字典顺序来维护数据。表中一定范围内的行被动态分区。每个分区叫做一个”Tablet”,Tablet是数据分布和负载均衡的单位。这样做的结果是,读取一定范围内的少数行很高效,并且往往只需要跟少数机器通信。用户可以通过选择他们的行关键字来开发这种特性,这样可以为他们的数据访问获得好的本地性(get good locality)。举例来说,我们在关键字com.google.maps/index.html的索引下为maps.google.com/index.htm存储数据。把相同的域中的网页存储在连续的区域可以让一些主机和域名的分析更加有效。
列族
列关键字组成的集合叫做“列族“,列族构成了访问控制的基本单位。存放在同一列族下的所有数据通常都属于同一个类型(我们把同一个列族下的数据压缩在一起)。列族必须先创建,然后才能在列族中任何的列关键字下存放数据;列族创建后,其中的任何一个列关键字下都可以存放数据。我们的意图是,一张表中不同列族的数目要小(最多几百个),并且列族在操作中很少改变。与此相反,一张表可以有无限多个列。
列关键字的命名语法如下:列族:限定词。列族的名字必须是可打印的字符串,但是限定词可以是任意字符串。比如,Webtable有个列族language,用来存放撰写网页的语言。我们在language列族中只使用一个列关键字,用来存放每个网页的语言标识ID。Webtable中另一个有用的列族是anchor;这个列族的每一个列关键字代表单独一个锚链接,如图一所示。限定词是引用该网页的站点名;数据项内容是链接文本。
访问控制、磁盘和内存的计数都是在列族层面进行的。在我们的Webtable的例子中,上述的控制权限能帮助我们管理不同类型的应用:一些应用可以添加新的基本数据、一些可以读取基本数据并创建派生的列族、一些则只允许浏览现存数据(甚至可能因为隐私的原因不能浏览所有现存列族)。
时间戳
在Bigtable中,每一个数据项都可以包含同一数据的不同版本;这些版本通过时间戳来索引。Bigtable时间戳是64位整型数。时间戳可由Bigtable指定,这种情况下时间戳代表精确到毫秒的“实时”时间,或者该值由库户程序明确指定。需要避免冲突的程序必须自己生成一个唯一的时间戳。数据项中不同版本按照时间戳倒序排列,所以最新的版本可以被先读到。
为了减轻多个版本数据的管理负担,我们对每一个列族提供两个设置参数,Bigtable通过这两个参数可以对废弃版本的数据进行自动垃圾收集。用户既可以指定只保存最后n个版本的数据,也可以只保存“足够新”的版本的数据(比如,只保存最近7天的内容写入的数据)。
在我们的例子Webtable中,我们将存储在contents:列中爬虫经过的页面的时间戳设置为这个版本的页面被实际爬过的时(alex注: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);
客户程序可以对Bigtable进行如下的操作:写入或者删除Bigtable中的值、从个别行中查找值、或者遍历表中的一个数据子集。图2中的C++代码使用RowMutation抽象对象执行一系列的更新操作。(为了保持示例简洁,我们省略了无关细节)。对Apply的调用执行了了对Webtable的一个原子修改(mutation)操作:它为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 中的C++代码使用Scanner抽象对象遍历一个特定行内的所有锚点。客户程序可以遍历多个列族,有几种机制可以对扫描输出的行、列和时间戳进行限制。例如,我们可以限制上面的扫描,让它只输出那些列匹配正则表达式*.cnn.com的锚点,或者那些时间戳在当前时间前10天的锚点。
Bigtable还支持一些其它的特性,这些特性允许用户以更复杂的方法操作数据。首先,Bigtable支持单行上事务处理,利用这个功能,用户可以对存储在一个单独行关键字下的数据执行原子性的读取