1、Bigtable: 一 个 分 布 式 的 结 构 化 数 据 存 储 系 统译者: alex摘 要Bigtable 是一个分布式的结构化数据存储系统,它被设计用来处理海量数据:通常是分布在数千台普通服务器上的 PB 级的数据。Google 的很多项目使用 Bigtable 存储数据,包括 Web 索引、Google Earth、Google Finance。这些应用对 Bigtable 提出的要求差异非常大,无论是在数据量上(从 URL到网页到卫星图像)还是在响应速度上(从后端的批量处理到实时数据服务)。尽管应用需求差异很大,但是,针对 Google 的这些产品, Bigtable 还是成功
2、的提供了一个灵活的、高性能的解决方案。本论文描述了 Bigtable 提供的简单的数据模型,利用这个模型,用户可以动态的控制数据的分布和格式;我们还将描述 Bigtable 的设计和实现。1 介 绍在过去两年半时间里,我们设计、实现并部署了一个分布式的结构化数据存储系统 在 Google,我们称之为 Bigtable。Bigtable 的设计目的是可靠的处理 PB 级别的数据,并且能够部署到上千台机器上。Bigtable 已经实现了下面的几个目标:适用性广泛、可扩展、高性能和高可用性。Bigtable 已经在超过60 个 Google 的产品和项目上得到了应用,包括 Google Analyt
3、ics、Google Finance、Orkut、Personalized Search、Writely 和 Google Earth。这些产品对 Bigtable 提出了迥异的需求,有的需要高吞吐量的批处理,有的则需要及时响应,快速返回数据给最终用户。它们使用的Bigtable 集群的配置也有很大的差异,有的集群只有几台服务器,而有的则需要上千台服务器、存储几百 TB 的数据。在很多方面,Bigtable 和数据库很类似:它使用了很多数据库的实现策略。并行数据库【14】和内存数据库【13】已经具备可扩展性和高性能,但是 Bigtable 提供了一个和这些系统完全不同的接口。Bigtable
4、不支持完整的关系数据模型;与之相反,Bigtable 为客户提供了简单的数据模型,利用这个模型,客户可以动态控制数据的分布和格式 ( alex 注:也就是对 BigTable 而言,数据是没有格式的,用数据库领域的术语说,就是数据没有 Schema,用户自己去定义 Schema), 用户也可以自己推测(alex注: reason about)底层存储数据的位置相关性(alex 注:位置相关性可以这样理解,比如树状结构,具有相同前缀的数据的存放位置接近。在读取的时候,可以把这些数据一次读取出来 )。数据的下标是行和列的名字,名字可以是任意的字符串。Bigtable 将存储的数据都视为字符串,但是
5、 Bigtable 本身不去解析这些字符串,客户程序通常会在把各种结构化或者半结构化的数据串行化到这些字符串里。通过仔细选择数据的模式,客户可以控制数据的位置相关性。最后,可以通过 BigTable 的模式参数来控制数据是存放在内存中、还是硬盘上。第二节描述关于数据模型更多细节方面的东西;第三节概要介绍了客户端 API;第四节简要介绍了BigTable 底层使用的 Google 的基础框架;第五节描述了 BigTable 实现的关键部分;第 6 节描述了我们为了提高 BigTable 的性能采用的一些精细的调优方法;第 7 节提供了 BigTable 的性能数据;第 8 节讲述了几个 Goog
6、le 内部使用 BigTable 的例子;第 9 节是我们在设计和后期支持过程中得到一些经验和教训;最后,在第 10 节列出我们的相关研究工作,第 11 节是我们的结论。2 数 据 模 型Bigtable 是一个稀疏的、分布式的、持久化存储的多维度排序 Map( alex 注:对于程序员来说, Map应该不用翻译了吧。 Map 由 key 和 value 组成,后面我们直接使用 key 和 value,不再另外翻译了)。Map 的索引是行关键字、列关键字以及时间戳; Map 中的每个 value 都是一个未经解析的 byte 数组。(row:string, column:string,time
7、:int64)-string我们在仔细分析了一个类似 Bigtable 的系统的种种潜在用途之后,决定使用这个数据模型。我们先举个具体的例子,这个例子促使我们做了很多设计决策;假设我们想要存储海量的网页及相关信息,这些数据可以用于很多不同的项目,我们姑且称这个特殊的表为 Webtable。在 Webtable 里,我们使用 URL 作为行关键字,使用网页的某些属性作为列名,网页的内容存在“contents:”列中,并用获取该网页的时间戳作为标识(alex 注:即按照获取时间不同,存储了多个版本的网页数据 ),如图一所示。图一:一个存储 Web 网页的例子的表的片断。行名是一个反向 URL。co
8、ntents 列族存放的是网页的内容,anchor 列族存放引用该网页的锚链接文本 ( alex 注:如果不知道 HTML 的 Anchor,请 Google 一把)。 CNN 的主页被 Sports Illustrater和 MY-look 的主页引用,因此该行包含了名为 “anchor:”和 “anchhor:my.look.ca”的列。每个锚链接只有一个版本 ( alex 注:注意时间戳标识了列的版本, t9 和 t8 分别标识了两个锚链接的版本); 而 contents 列则有三个版本,分别由时间戳 t3,t5,和 t6 标识。行表中的行关键字可以是任意的字符串(目前支持最大 64KB
9、 的字符串,但是对大多数用户,10-100 个字节就足够了)。对同一个行关键字的读或者写操作都是原子的(不管读或者写这一行里多少个不同列),这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。Bigtable 通过行关键字的字典顺序来组织数据。表中的每个行都可以动态分区。每个分区叫做一个”Tablet”,Tablet 是数据分布和负载均衡调整的最小单位。这样做的结果是,当操作只读取行中很少几列的数据时效率很高,通常只需要很少几次机器间的通信即可完成。用户可以通过选择合适的行关键字,在数据访问时有效利用数据的位置相关性,从而更好的利用这个特性。举例来说,在 Webtabl
10、e 里,通过反转 URL 中主机名的方式,可以把同一个域名下的网页聚集起来组织成连续的行。具体来说,我们可以把 的数据存放在关键字 com.google.maps/index.html 下。把相同的域中的网页存储在连续的区域可以让基于主机和域名的分析更加有效。列族列关键字组成的集合叫做“列族“ ,列族是访问控制的基本单位。存放在同一列族下的所有数据通常都属于同一个类型(我们可以把同一个列族下的数据压缩在一起)。列族在使用之前必须先创建,然后才能在列族中任何的列关键字下存放数据;列族创建后,其中的任何一个列关键字下都可以存放数据。根据我们的设计意图,一张表中的列族不能太多(最多几百个),并且列
11、族在运行期间很少改变。与之相对应的,一张表可以有无限多个列。列关键字的命名语法如下: 列族:限定词 。 列族的名字必须是可打印的字符串,而限定词的名字可以是任意的字符串。比如,Webtable 有个列族 language,language 列族用来存放撰写网页的语言。我们在 language 列族中只使用一个列关键字,用来存放每个网页的语言标识 ID。Webtable 中另一个有用的列族是 anchor;这个列族的每一个列关键字代表一个锚链接,如图一所示。Anchor 列族的限定词是引用该网页的站点名;Anchor 列族每列的数据项存放的是链接文本。访问控制、磁盘和内存的使用统计都是在列族层面
12、进行的。在我们的 Webtable 的例子中,上述的控制权限能帮助我们管理不同类型的应用:我们允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。时间戳在 Bigtable 中,表的每一个数据项都可以包含同一份数据的不同版本;不同版本的数据通过时间戳来索引。Bigtable 时间戳的类型是 64 位整型。Bigtable 可以给时间戳赋值,用来表示精确到毫秒的 “实时”时间;用户程序也可以给时间戳赋值。如果应用程序需要避免数据版本冲突,那么它必须自己生成具有唯一性的时间戳。数据项中,不同版本的数据按照时
13、间戳倒序排序,即最新的数据排在最前面。为了减轻多个版本数据的管理负担,我们对每一个列族配有两个设置参数,Bigtable 通过这两个参数可以对废弃版本的数据自动进行垃圾收集。用户可以指定只保存最后 n 个版本的数据,或者只保存“ 足够新”的版本的数据(比如,只保存最近 7 天的内容写入的数据)。在 Webtable 的举例里, contents:列存储的时间戳信息是网络爬虫抓取一个页面的时间。上面提及的垃圾收集机制可以让我们只保留最近三个版本的网页数据。3 APIBigtable 提供了建立和删除表以及列族的 API 函数。Bigtable 还提供了修改集群、表和列族的元数据的API,比如修改
14、访问权限。/ Open the tableTable *T = OpenOrDie(“/bigtable/web/webtable”);/ Write a new anchor and delete an old anchorRowMutation r1(T, “n.www”);r1.Set(“anchor:www.c-span.org”, “CNN”);r1.Delete(“anchor:”);Operation op;Apply(Figure 2: Writing to Bigtable.客户程序可以对 Bigtable 进行如下的操作:写入或者删除 Bigtable 中的值、从每个行中查
15、找值、或者遍历表中的一个数据子集。图 2 中的+代码使用 RowMutation 抽象对象进行了一系列的更新操作。(为了保持示例代码的简洁,我们忽略了一些细节相关代码)。调用 Apply 函数对ebtable 进行了一个原子修改操作:它为 增加了一个锚点,同时删除了另外一个锚点。Scanner scanner(T);ScanStream *stream;stream = scanner.FetchColumnFamily(“anchor”);stream-SetReturnAllVersions();scanner.Lookup(“n.www”);for (; !stream-Done();
16、 stream-Next() printf(“%s %s %lld %sn”,scanner.RowName(),stream-ColumnName(),stream-MicroTimestamp(),stream-Value(); Figure 3: Reading from Bigtable.图 3 中的 C+代码使用 Scanner 抽象对象遍历一个行内的所有锚点。客户程序可以遍历多个列族,有几种方法可以对扫描输出的行、列和时间戳进行限制。例如,我们可以限制上面的扫描,让它只输出那些匹配正则表达式* 的锚点,或者那些时间戳在当前时间前 10 天的锚点。Bigtable 还支持一些其它的特
17、性,利用这些特性,用户可以对数据进行更复杂的处理。首先,Bigtable支持单行上的事务处理,利用这个功能,用户可以对存储在一个行关键字下的数据进行原子性的读-更新-写操作。虽然 Bigtable 提供了一个允许用户跨行批量写入数据的接口,但是,Bigtable 目前还不支持通用的跨行事务处理。其次,Bigtable 允许把数据项用做整数计数器。最后,Bigtable 允许用户在服务器的地址空间内执行脚本程序。脚本程序使用 Google 开发的 Sawzall【28】数据处理语言。虽然目前我们基于的 Sawzall 语言的 API 函数还不允许客户的脚本程序写入数据到 Bigtable,但是它
18、允许多种形式的数据转换、基于任意表达式的数据过滤、以及使用多种操作符的进行数据汇总。Bigtable 可以和 MapReduce【12】一起使用,MapReduce 是 Google 开发的大规模并行计算框架。我们已经开发了一些 Wrapper 类,通过使用这些 Wrapper 类,Bigtable 可以作为 MapReduce 框架的输入和输出。4 BigTable 构 件Bigtable 是建立在其它的几个 Google 基础构件上的。BigTable 使用 Google 的分布式文件系统(GFS)【17】存储日志文件和数据文件。BigTable 集群通常运行在一个共享的机器池中,池中的机
19、器还会运行其它的各种各样的分布式应用程序,BigTable 的进程经常要和其它应用的进程共享机器。BigTable 依赖集群管理系统来调度任务、管理共享的机器上的资源、处理机器的故障、以及监视机器的状态。BigTable 内部存储数据的文件是 Google SSTable 格式的。SSTable 是一个持久化的、排序的、不可更改的 Map 结构,而 Map 是一个 key-value 映射的数据结构, key 和 value 的值都是任意的 Byte 串。可以对 SSTable 进行如下的操作:查询与一个 key 值相关的 value,或者遍历某个 key 值范围内的所有的 key-value
20、 对。从内部看,SSTable 是一系列的数据块(通常每个块的大小是 64KB,这个大小是可以配置的)。SSTable 使用块索引(通常存储在 SSTable 的最后)来定位数据块;在打开 SSTable 的时候,索引被加载到内存。每次查找都可以通过一次磁盘搜索完成:首先使用二分查找法在内存中的索引里找到数据块的位置,然后再从硬盘读取相应的数据块。也可以选择把整个 SSTable 都放在内存中,这样就不必访问硬盘了。BigTable 还依赖一个高可用的、序列化的分布式锁服务组件,叫做 Chubby【8】。一个 Chubby 服务包括了 5 个活动的副本,其中的一个副本被选为 Master,并且
21、处理请求。只有在大多数副本都是正常运行的,并且彼此之间能够互相通信的情况下,Chubby 服务才是可用的。当有副本失效的时候,Chubby使用 Paxos 算法【9,23 】来保证副本的一致性。Chubby 提供了一个名字空间,里面包括了目录和小文件。每个目录或者文件可以当成一个锁,读写文件的操作都是原子的。Chubby 客户程序库提供对Chubby 文件的一致性缓存。每个 Chubby 客户程序都维护一个与 Chubby 服务的会话。如果客户程序不能在租约到期的时间内重新签订会话的租约,这个会话就过期失效了(alex 注:又用到了 lease。原文是: A clients session e
22、xpires if it is unable to renew its session lease within the lease expiration time.)。 当一个会话失效时,它拥有的锁和打开的文件句柄都失效了。Chubby 客户程序可以在文件和目录上注册回调函数,当文件或目录改变、或者会话过期时,回调函数会通知客户程序。Bigtable 使用 Chubby 完成以下的几个任务:确保在任何给定的时间内最多只有一个活动的 Master 副本;存储 BigTable 数据的自引导指令的位置(参考 5.1 节);查找 Tablet 服务器,以及在 Tablet 服务器失效时进行善后(5
23、.2 节);存储 BigTable 的模式信息(每张表的列族信息);以及存储访问控制列表。如果 Chubby 长时间无法访问,BigTable 就会失效。最近我们在使用 11 个 Chubby 服务实例的14 个 BigTable 集群上测量了这个影响。由于 Chubby 不可用而导致 BigTable 中的部分数据不能访问的平均比率是 0.0047%(Chubby 不能访问的原因可能是 Chubby 本身失效或者网络问题)。单个集群里,受 Chubby 失效影响最大的百分比是 0.0326%( alex 注:有点莫名其妙,原文是: The percentage for the single
24、cluster that was most affected by Chubby unavailability was 0.0326%.) 。5 介 绍Bigtable 包括了三个主要的组件:链接到客户程序中的库、一个 Master 服务器和多个 Tablet 服务器。针对系统工作负载的变化情况,BigTable 可以动态的向集群中添加(或者删除)Tablet 服务器。Master 服务器主要负责以下工作:为 Tablet 服务器分配 Tablets、检测新加入的或者过期失效的 Table服务器、对 Tablet 服务器进行负载均衡、以及对保存在 GFS 上的文件进行垃圾收集。除此之外,它还处
25、理对模式的相关修改操作,例如建立表和列族。每个 Tablet 服务器都管理一个 Tablet 的集合(通常每个服务器有大约数十个至上千个 Tablet)。每个Tablet 服务器负责处理它所加载的 Tablet 的读写操作,以及在 Tablets 过大时,对其进行分割。和很多 Single-Master 类型的分布式存储系统【17.21 】类似,客户端读取的数据都不经过 Master 服务器:客户程序直接和 Tablet 服务器通信进行读写操作。由于 BigTable 的客户程序不必通过 Master服务器来获取 Tablet 的位置信息,因此,大多数客户程序甚至完全不需要和 Master 服
26、务器通信。在实际应用中,Master 服务器的负载是很轻的。一个 BigTable 集群存储了很多表,每个表包含了一个 Tablet 的集合,而每个 Tablet 包含了某个范围内的行的所有相关数据。初始状态下,一个表只有一个 Tablet。随着表中数据的增长,它被自动分割成多个 Tablet,缺省情况下,每个 Tablet 的尺寸大约是 100MB 到 200MB。5.1 Tablet 的 位 置我们使用一个三层的、类似+树 10的结构存储 Tablet 的位置信息(如图 4)。第一层是一个存储在 Chubby 中的文件,它包含了 Root Tablet 的位置信息。Root Tablet
27、包含了一个特殊的 METADATA 表里所有的 Tablet 的位置信息。METADATA 表的每个 Tablet 包含了一个用户Tablet 的集合。 Root Tablet 实际上是 METADATA 表的第一个 Tablet,只不过对它的处理比较特殊 Root Tablet 永远不会被分割 这就保证了 Tablet 的位置信息存储结构不会超过三层。在 METADATA 表里面,每个 Tablet 的位置信息都存放在一个行关键字下面,而这个行关键字是由Tablet 所在的表的标识符和 Tablet 的最后一行编码而成的。METADATA 的每一行都存储了大约 1KB的内存数据。在一个大小适
28、中的、容量限制为 128MB 的 METADATA Tablet 中,采用这种三层结构的存储模式,可以标识 234 个 Tablet 的地址(如果每个 Tablet 存储 128MB 数据,那么一共可以存储261 字节数据)。客户程序使用的库会缓存 Tablet 的位置信息。如果客户程序没有缓存某个 Tablet 的地址信息,或者发现它缓存的地址信息不正确,客户程序就在树状的存储结构中递归的查询 Tablet 位置信息;如果客户端缓存是空的,那么寻址算法需要通过三次网络来回通信寻址,这其中包括了一次 Chubby 读操作;如果客户端缓存的地址信息过期了,那么寻址算法可能需要最多次网络来回通信才
29、能更新数据,因为只有在缓存中没有查到数据的时候才能发现数据过期 ( alex 注:其中的三次通信发现缓存过期,另外三次更新缓存数据) (假设 METADATA 的 Tablet 没有被频繁的移动)。尽管 Tablet 的地址信息是存放在内存里的,对它的操作不必访问 GFS 文件系统,但是,通常我们会通过预取 Tablet 地址来进一步的减少访问的开销:每次需要从 METADATA 表中读取一个 Tablet 的元数据的时候,它都会多读取几个 Tablet 的元数据。在 METADATA 表中还存储了次级信息(alex 注: secondary information),包括每个 Tablet
30、的事件日志(例如,什么时候一个服务器开始为该 Tablet 提供服务)。这些信息有助于排查错误和性能分析。5.2 Tablet 分 配在任何一个时刻,一个 Tablet 只能分配给一个 Tablet 服务器。Master 服务器记录了当前有哪些活跃的Tablet 服务器、哪些 Tablet 分配给了哪些 Tablet 服务器、哪些 Tablet 还没有被分配。当一个 Tablet还没有被分配、并且刚好有一个 Tablet 服务器有足够的空闲空间装载该 Tablet 时,Master 服务器会给这个 Tablet 服务器发送一个装载请求,把 Tablet 分配给这个服务器。BigTable 使用
31、 Chubby 跟踪记录 Tablet 服务器的状态。当一个 Tablet 服务器启动时,它在 Chubby的一个指定目录下建立一个有唯一性名字的文件,并且获取该文件的独占锁。Master 服务器实时监控着这个目录(服务器目录),因此 Master 服务器能够知道有新的 Tablet 服务器加入了。如果 Tablet 服务器丢失了 Chubby 上的独占锁 比如由于网络断开导致 Tablet 服务器和 Chubby 的会话丢失 它就停止对 Tablet 提供服务。(Chubby 提供了一种高效的机制,利用这种机制, Tablet 服务器能够在不增加网络负担的情况下知道它是否还持有锁)。只要文件
32、还存在,Tablet 服务器就会试图重新获得对该文件的独占锁;如果文件不存在了,那么 Tablet 服务器就不能再提供服务了,它会自行退出 ( alex 注:so it kills itself) 。当 Tablet 服务器终止时(比如,集群的管理系统将运行该 Tablet 服务器的主机从集群中移除),它会尝试释放它持有的文件锁,这样一来,Master 服务器就能尽快把 Tablet 分配到其它的 Tablet 服务器。Master 服务器负责检查一个 Tablet 服务器是否已经不再为它的 Tablet 提供服务了,并且要尽快重新分配它加载的 Tablet。Master 服务器通过轮询 Ta
33、blet 服务器文件锁的状态来检测何时 Tablet 服务器不再为 Tablet 提供服务。如果一个 Tablet 服务器报告它丢失了文件锁,或者 Master 服务器最近几次尝试和它通信都没有得到响应,Master 服务器就会尝试获取该 Tablet 服务器文件的独占锁;如果 Master 服务器成功获取了独占锁,那么就说明 Chubby 是正常运行的,而 Tablet 服务器要么是宕机了、要么是不能和 Chubby 通信了,因此,Master 服务器就删除该 Tablet 服务器在 Chubby 上的服务器文件以确保它不再给 Tablet 提供服务。一旦 Tablet 服务器在 Chubb
34、y 上的服务器文件被删除了,Master 服务器就把之前分配给它的所有的 Tablet 放入未分配的 Tablet 集合中。为了确保 Bigtable 集群在 Master 服务器和 Chubby 之间网络出现故障的时候仍然可以使用,Master 服务器在它的 Chubby 会话过期后主动退出。但是不管怎样,如同我们前面所描述的,Master 服务器的故障不会改变现有 Tablet 在 Tablet 服务器上的分配状态。当集群管理系统启动了一个 Master 服务器之后,Master 服务器首先要了解当前 Tablet 的分配状态,之后才能够修改分配状态。Master 服务器在启动的时候执行以
35、下步骤:( 1)Master 服务器从 Chubby获取一个唯一的 Master 锁,用来阻止创建其它的 Master 服务器实例;( 2)Master 服务器扫描Chubby 的服务器文件锁存储目录,获取当前正在运行的服务器列表;(3)Master 服务器和所有的正在运行的 Tablet 表服务器通信,获取每个 Tablet 服务器上 Tablet 的分配信息;(4 )Master 服务器扫描 METADATA 表获取所有的 Tablet 的集合。在扫描的过程中,当 Master 服务器发现了一个还没有分配的 Tablet,Master 服务器就将这个 Tablet 加入未分配的 Table
36、t 集合等待合适的时机分配。可能会遇到一种复杂的情况:在 METADATA 表的 Tablet 还没有被分配之前是不能够扫描它的。因此,在开始扫描之前(步骤 4),如果在第三步的扫描过程中发现 Root Tablet 还没有分配,Master 服务器就把 Root Tablet 加入到未分配的 Tablet 集合。这个附加操作确保了 Root Tablet 会被分配。由于Root Tablet 包括了所有 METADATA 的 Tablet 的名字,因此 Master 服务器扫描完 Root Tablet 以后,就得到了所有的 METADATA 表的 Tablet 的名字了。保存现有 Tabl
37、et 的集合只有在以下事件发生时才会改变:建立了一个新表或者删除了一个旧表、两个Tablet 被合并了、或者一个 Tablet 被分割成两个小的 Tablet。Master 服务器可以跟踪记录所有这些事件,因为除了最后一个事件外的两个事件都是由它启动的。Tablet 分割事件需要特殊处理,因为它是由Tablet 服务器启动。在分割操作完成之后,Tablet 服务器通过在 METADATA 表中记录新的 Tablet 的信息来提交这个操作;当分割操作提交之后,Tablet 服务器会通知 Master 服务器。如果分割操作已提交的信息没有通知到 Master 服务器(可能两个服务器中有一个宕机了)
38、,Master 服务器在要求 Tablet服务器装载已经被分割的子表的时候会发现一个新的 Tablet。通过对比 METADATA 表中 Tablet 的信息,Tablet 服务器会发现 Master 服务器要求其装载的 Tablet 并不完整,因此,Tablet 服务器会重新向Master 服务器发送通知信息。5.3 Tablet 服 务如图 5 所示,Tablet 的持久化状态信息保存在 GFS 上。更新操作提交到 REDO 日志中 ( alex 注:Updates are committed to a commit log that stores redo records) 。在这些更新
39、操作中,最近提交的那些存放在一个排序的缓存中,我们称这个缓存为 memtable;较早的更新存放在一系列SSTable 中。为了恢复一个 Tablet,Tablet 服务器首先从 METADATA 表中读取它的元数据。Tablet的元数据包含了组成这个 Tablet 的 SSTable 的列表,以及一系列的 Redo Point( alex 注: a set of redo points), 这些 Redo Point 指向可能含有该 Tablet 数据的已提交的日志记录。 Tablet 服务器把SSTable 的索引读进内存,之后通过重复 Redo Point 之后提交的更新来重建 memt
40、able。当对 Tablet 服务器进行写操作时,Tablet 服务器首先要检查这个操作格式是否正确、操作发起者是否有执行这个操作的权限。权限验证的方法是通过从一个 Chubby 文件里读取出来的具有写权限的操作者列表来进行验证(这个文件几乎一定会存放在 Chubby 客户缓存里)。成功的修改操作会记录在提交日志里。可以采用批量提交方式 ( alex 注: group commit) 来提高包含大量小的修改操作的应用程序的吞吐量【13,16】。当一个写操作提交后,写的内容插入到 memtable 里面。当对 Tablet 服务器进行读操作时,Tablet 服务器会作类似的完整性和权限检查。一个
41、有效的读操作在一个由一系列 SSTable 和 memtable 合并的视图里执行。由于 SSTable 和 memtable 是按字典排序的数据结构,因此可以高效生成合并视图。当进行 Tablet 的合并和分割时,正在进行的读写操作能够继续进行。5.4 Compactions(alex 注:这个词挺简单,但是在这节里面挺难翻译的。应该是空间缩减的意思,但是似乎又不能完全概括它在上下文中的意思,干脆,不翻译了 )随着写操作的执行,memtable 的大小不断增加。当 memtable 的尺寸到达一个门限值的时候,这个memtable 就会被冻结,然后创建一个新的 memtable;被冻结住 m
42、emtable 会被转换成 SSTable,然后写入 GFS( alex 注:我们称这种 Compaction 行为为 Minor Compaction)。 Minor Compaction 过程有两个目的:shrink(alex 注: shrink 是数据库用语,表示空间收缩 )Tablet 服务器使用的内存,以及在服务器灾难恢复过程中,减少必须从提交日志里读取的数据量。在 Compaction 过程中,正在进行的读写操作仍能继续。每一次 Minor Compaction 都会创建一个新的 SSTable。如果 Minor Compaction 过程不停滞的持续进行下去,读操作可能需要合并来
43、自多个 SSTable 的更新;否则,我们通过定期在后台执行 Merging Compaction 过程合并文件,限制这类文件的数量。Merging Compaction 过程读取一些 SSTable 和memtable 的内容,合并成一个新的 SSTable。只要 Merging Compaction 过程完成了,输入的这些SSTable 和 memtable 就可以删除了。合并所有的 SSTable 并生成一个新的 SSTable 的 Merging Compaction 过程叫作 Major Compaction。由非 Major Compaction 产生的 SSTable 可能含有特
44、殊的删除条目,这些删除条目能够隐藏在旧的、但是依然有效的 SSTable 中已经删除的数据 ( alex 注:令人费解啊,原文是 SSTables produced by non-major compactions can contain special deletion entries that suppress deleted data in older SSTables that are still live) 。而 Major Compaction 过程生成的 SSTable不包含已经删除的信息或数据。Bigtable 循环扫描它所有的 Tablet,并且定期对它们执行 Major C
45、ompaction。Major Compaction 机制允许 Bigtable 回收已经删除的数据占有的资源,并且确保BigTable 能及时清除已经删除的数据 ( alex 注:实际是回收资源。数据删除后,它占有的空间并不能马上重复利用;只有空间回收后才能重复使用), 这对存放敏感数据的服务是非常重要。6 优 化上一章我们描述了 Bigtable 的实现,我们还需要很多优化工作才能使 Bigtable 到达用户要求的高性能、高可用性和高可靠性。本章描述了 Bigtable 实现的其它部分,为了更好的强调这些优化工作,我们将深入细节。局部性群组客户程序可以将多个列族组合成一个局部性群族。对
46、Tablet 中的每个局部性群组都会生成一个单独的SSTable。将通常不会一起访问的列族分割成不同的局部性群组可以提高读取操作的效率。例如,在Webtable 表中,网页的元数据(比如语言和 Checksum)可以在一个局部性群组中,网页的内容可以在另外一个群组:当一个应用程序要读取网页的元数据的时候,它没有必要去读取所有的页面内容。此外,可以以局部性群组为单位设定一些有用的调试参数。比如,可以把一个局部性群组设定为全部存储在内存中。Tablet 服务器依照惰性加载的策略将设定为放入内存的局部性群组的 SSTable 装载进内存。加载完成之后,访问属于该局部性群组的列族的时候就不必读取硬盘了
47、。这个特性对于需要频繁访问的小块数据特别有用:在 Bigtable 内部,我们利用这个特性提高 METADATA 表中具有位置相关性的列族的访问速度。压缩客户程序可以控制一个局部性群组的 SSTable 是否需要压缩;如果需要压缩,那么以什么格式来压缩。每个 SSTable 的块(块的大小由局部性群组的优化参数指定)都使用用户指定的压缩格式来压缩。虽然分块压缩浪费了少量空间 ( alex 注:相比于对整个 SSTable 进行压缩,分块压缩压缩率较低), 但是,我们在只读取 SSTable 的一小部分数据的时候就不必解压整个文件了。很多客户程序使用了“两遍” 的、可定制的压缩方式。第一遍采用
48、Bentley and McIlroys 方式6,这种方式在一个很大的扫描窗口里对常见的长字符串进行压缩;第二遍是采用快速压缩算法,即在一个 16KB 的小扫描窗口中寻找重复数据。两个压缩的算法都很快,在现在的机器上,压缩的速率达到 100-200MB/s,解压的速率达到 400-1000MB/s。虽然我们在选择压缩算法的时候重点考虑的是速度而不是压缩的空间,但是这种两遍的压缩方式在空间压缩率上的表现也是令人惊叹。比如,在 Webtable 的例子里,我们使用这种压缩方式来存储网页内容。在一次测试中,我们在一个压缩的局部性群组中存储了大量的网页。针对实验的目的,我们没有存储每个文档所有版本的数
49、据,我们仅仅存储了一个版本的数据。该模式的空间压缩比达到了 10:1。这比传统的Gzip 在压缩 HTML 页面时 3:1 或者 4:1 的空间压缩比好的多;“两遍”的压缩模式如此高效的原因是由于Webtable 的行的存放方式:从同一个主机获取的页面都存在临近的地方。利用这个特性,Bentley-McIlroy 算法可以从来自同一个主机的页面里找到大量的重复内容。不仅仅是 Webtable,其它的很多应用程序也通过选择合适的行名来将相似的数据聚簇在一起,以获取较高的压缩率。当我们在 Bigtable 中存储同一份数据的多个版本的时候,压缩效率会更高。通过缓存提高读操作的性能为了提高读操作的性能,Tablet 服务器使用二级缓存的策略。扫描缓存是第一级缓存,主要缓存 Tablet服务器通过 SSTable 接口获取的 Key-Value 对;Block 缓存是二级缓存,缓存的是从 GFS 读取的SSTable 的 Block。对于经常要重复读取相同数据的应用程序来说,扫描缓存非常有效;对于经常要读取刚刚读过的数据附近的数据的应用程序来说,Block 缓存更有用(例如,顺