bigtable论文笔记

2019-08-04

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

-行关键字:最大64K的字符串。

- Bigtable通过行关键字的字典顺序来维护数据

- 表中一定范围的行被动态分区,每个分区叫做一个Tablet

- 单一行关键字下的每一个读或者写操作都是原子的,即支持单行事务
  • 列族(column family):列关键字组成的集合

    • 访问控制、磁盘和内存的计数都是在列族层面进行的

    • 同一列族的所有数据都属于同一类型(同一列族下的数据压缩在一起)

    • 先创建列族,然后才能在列族的任何列关键字下存放数据

    • 一张表中不同的列族数目要尽量小(最多几百个),并且列族在操作中很少改变

    • 列族名字必须是可打印的字符串

    • 列族的存在有助于未来将Bigtable扩展实现为HTAP存储

  • 列关键字:命名语法如下:列族:限定词

    • 列族的名字必须是可打印的字符串,但是限定词可以是任意字符串

    • 一张表可以有无限多的列

  • 时间戳:64位整型数,用时间戳来索引同一数据的不同版本

    • 数据项的不同版本按照时间戳倒序排列,所以最新的版本可以先读到

    • 为了减轻多个版本数据的管理负担,对每一个列族提供两个设置参数,通过这两个参数对废弃版本的数据进行自动垃圾回收(1.只保存最后n个版本数据,2.保存最近XXX时间内的数据)

BigTable构件:BigTable是建立在一些其他google的基础组件之上的

  • SSTable文件格式

    • SSTable使用块索引来定位数据块,在打开SSTable的时候,索引被加载到内存。一次查找通过一次磁盘搜索完成:在内存索引里执行二分查找,找到对应的索引,然后从磁盘中读取合适的数据块,也可以把整个SSTable都映射到内存中,这样便不需要访问硬盘了。
  • Chubby分布式锁: 高可用、持久化的分布式锁服务

    • 一个Chubby服务包括了5个活动的副本,其中一个副本为master,并积极处理请求。

    • 使用paxos算法保证副本的一致性

实现:三个主要的组件

  • 链接到每个客户程序的库

  • 一个master服务器

    • 为tablet服务器分配tablets

    • 检测新加入的或者过期失效的tablet服务器

    • 平衡tablet服务器的负载

    • 对GFS中的文件进行垃圾收集

    • 客户数据不经过master:直接和tablet服务器通信来进行读写操作,不以来master服务器来获取tablet的位置信息。因此大多数客户程序甚至完全不和master通信,所以实际应用中master的负载很轻

  • 多个table服务器,每个tablet服务器都管理一组tablet(数十至上千个tablet)

    • 处理它所加载的tablet的读写操作

    • 分割增长的过大的tablet

每个表包含一组tablet(初始状态下每个表只有一个tablet,随着表中数据增长,被自动分割成多个tablet,默认每个tablet大小约为100M~200M)

上图是一个三层结构,分别为:Chubby file, METADATA, User tablet,其中METADATA包含 Root Tablet和Other METADATA tablets,即图中间两个。 每个tablet包含128M / 1K = 2^17,所以一共组多可以有2^17 * 2^17=2^34个tablet(Root tablet有2^17行,每行代表一个METADATA tablet,每个METADATA tablet也有2^17行,每行代表一个User tablet)

Root Tablet是METADATA的第一个tablet,这个表包含所有的Tablet的位置信息

Chubby file中:

  • 包含了RootTablet的位置信息

  • Tablet服务器文件(代表该tablet服务器的状态)。

    • 当一个Tablet服务器启动时,它在Chubby的一个指定目录下建立一个唯一的文件,并且获取该文件的独占锁。

    • Master服务器实时监控这个目录,因此Master服务器很了解Tablet服务器的状态

    • Master轮询Tablet所有的服务器文件,检查Tablet服务器的状态。如果一个Tablet服务器丢失了文件或者master尝试和它通信都没有回应master则尝试获取其文件独占锁并删除其服务器文件,一旦Tablet服务器在Chubby上的服务器文件被删除了,Master就把分配给他的所有Tablet放入未分配的Tablet集合中

Master服务器记录了:

  • 当前有哪些活跃的Tablet服务器

  • 哪些Tablet分配给了哪些Tablet服务器

  • 哪些Tablet还没有分配

Master服务器启动步骤:

  • Master服务器从Chubby获取一个唯一的Master锁,用来阻止创建其它的Master服务器实例

  • Master服务器扫描Chubby的服务器文件锁存储目录,获取当前正在运行的服务器列表

  • Master服务器和所有的正在运行的Tablet服务器通信,获取每个Tablet服务器上Tablet的分配信息

  • Master服务器扫描METADATA表获取所有的Tablet的集合。在扫描的过程中,当Master服务器发现了一个还没有分配的Tablet,Master服务器就将这个Tablet加入未分配的Tablet集合并等待合适的时机分配

上图中的tablet log就是REDO日志

恢复Tablet操作

  • Tablet服务器从METADATA中读取它的元数据(元数据中包含组成这个Tablet的SSTable列表,以及Redo point)

  • Tablet服务器把SSTable加载入内存

  • 重复Redo之后的提交

对Tablet服务器进行写操作

  • 检查格式是否正确、权限校验(权限验证通过从Chubby文件中读取的操作者列表来验证)

  • 成功的修改会记录在提交日志里。并通过group commit(批量提交)的方式来提交吞吐量

  • 当写操作提交后,写的内容插入到MemTable里

对Tablet服务器进行读操作

  • 类似于写操作的完整性和权限检查

  • 从SSTable和MemTable的合并视图里执行读操作

Compactions(空间压缩)

分为三类:

  • Minor Compaction. 随着写操作的执行,MemTable的大小不断增加,当MemTable大小到达一个阈值的时候,这个MemTable就会被冻结并创建一个新的MemTable,被冻结住的MemTable会被转换成SSTable(创建一个新的SSTable),然后写入GFS

  • Merging Compaction. Merge compaction会读取一些SSTable和MemTable,合并成一个新的SSTable

  • Major Compaction. 会合并所有的SSTable并生成一个新的SSTable, Bigtable定期扫描它所有的Tablet,并定期对他们执行Major Compaction.

Major Compaction生成的SSTable不包含已删除的信息和数据,而非Major Compaction产生的SSTable可能含有特殊的删除条目。

共同点:都会产生新的SSTable

优化

  • 局部性群组(Locality groups): 客户程序可以将多个列族组合成一个局部性群组

    • 对Tablet中的每个局部性群组都会生成一个单独的SSTable

    • 不会一起访问的列族分割成不同的局部性群组,可以提高读取操作的效率(避免了读取无用数据)

    • 可以以局部性群组为单位设定一些有用的调试参数,比如,可以把一个局部性群组声明为全部存储在内存中(在BigTable内部,我们利用这个特性提高METADATA表中具有位置相关性的列族的访问速度)

  • 压缩:客户程序可以控制一个局部性群组的SSTable是否需要压缩

    • 以SSTable块为单位进行压缩。优点:读取少量数据的时候,不需解压整个文件

    • 两阶段压缩:1-Bentley and Mcllroy’s方式(在一个很大的扫描窗口里对常见的长字符串进行压缩)、2-快速压缩算法(在一个16KB的扫描窗口里寻找重复数据 )

  • 缓存:采用二级缓存策略

    • 第一级缓存(扫描缓存):缓存key-value对,对于重复读非常有效

    • 第二级缓存(块缓存):对于经常读取临近数据的情况非常有效

  • Bloom过滤器:用于检索一个数据是否在集合中(内存中执行检索,而无需去硬盘中逐个查找所有SSTable), 其空间效率和查询时间比一般算法好的多,但是有一定的误识别率和删除困难。

  • commit日志的实现

    假设: 每个tablet一个单独的日志文件,并且多个tablet会并行的写入

    问题:那么将会导致大量的磁盘seek操作。

    解决方法:对每个tablet服务器采用一个日志文件

    缺点:这种解决方法提高了普通操作的性能,但是将恢复操作复杂化了。例如:tablet服务器A宕机了,服务器B/C/D分别接管服务器A的部分tablet,一个普遍的做法是B/C/D分别执行A的commit日志文件。但是由于对所有tablet都写在同一个commit日志文件中,所以B/C/D需要分别读取对所有的tablet的写入日志,然后挑选出自己需要接管的tablet,无疑性能低了很多(假如有100个Tablet服务器接管,那么需要读取100次)

    改进:按照关键字(tablet, row name, log sequence number)排序,排序后同一个tablet的日志就连续存放在一起了。因此只需要一次磁盘seek操作之后顺序读取就可以了。

    在向GFS中写入commit日志时可能会引起系统颠簸。为了确保在颠簸时仍能顺利进行,每个tablet服务器有两个日志写入线程,分别写入不同的日志文件(不同日志文件处于不同的GFS服务器上),如果在一个线程写入效率低的时候,则使用另外一个线程进行写入(因为每个日子都有序号,所以可以根据需要忽略掉由于线程切换导致的重复写入)

  • tablet恢复提速

    当master将一个tablet从一个tablet服务器转移到另一个tablet服务器时,源tablet服务器先对其进行一次minor compaction(减少其没有归并的记录,从而减少恢复时间),compaction完成之后,该服务器就停止为该tablet服务。在写在tablet之前,再做一次minor compaction, 以消除前面一次minor compaction过程中产生的未归并记录(通常会很快,这也是为什么执行两次minor compaction的原因,如果为了防止漏掉记录而在执行第一次minor compaction之前就停止为该tablet服务,第一次minor compactioin执行的时间会比较长,导致该tablet长时间得不到服务),第二次minor compaction之后,tablet就可以被装在到新的tablet服务器上了,并且不需要从日志中进行恢复

  • 利用不变性

    除了SSTable缓存之外其他部分产生的SSTable都是不变的,可以利用这一点,例如:

    • 从SSTable中读数据的时候,不需要进行同步操作,效率更高。

    • MemTable是唯一能被读写同时访问的可变数据结构,采用COW(Copy-on-Write)机制,这样也可以读写并行

    • 由于SSTable不变,因此可以把移除已删除数据的问题,转换成对垃圾进行回收的方式(标记-删除的方式)

    • Tablet分割操作变得简单,因为SSTable不变,所以分割的Tablet可以使用原有的SSTable,而不必重新建立新的SSTable