BigTable关键词:可伸缩、结构化数据、PB级、几千台服务器。

超过60个Google产品使用BigTable(包括Web Indexing、Google Earth、Google Finance、Google Analytics, Google Finance, Orkut, Personalized Search, Writely, and Google Earth),这些应用的需求不尽相同:

1、 数据大小:从URL到Web页面到卫星图象。

2、 延迟:从后台批处理应用(要求吞吐量大)到实时数据服务(要求数据延迟小)。

3、 集群规模:从个位数到几千台,存储几百TB的数据。


1 简介:(chapter 1 Introduction)


Bigtable实现了几个目标:

1、广泛的适应性;2、伸缩性;3、高性能;4、高可用性。

数据模型:

不完全支持关系模型、提供一个简单的数据模型,允许用户动态控制数据分布和格式,允许用户考虑数据的locality。数据通过row和column name(可以是任意字符串)索引,BigTable不识别数据的格式。通过选择schema用户可以控制数据的locality。通过schema parameter用户可以动态的控制从内存去数据还是从disk取数据。


2 数据模型:(chapter 2 Data Model)


是一个sparse, distributed, persistent multi-dimensional sorted map。数据通过行关键字(row key)、列关键字(column key)、时间戳(timestamp)定位,BigTable不识别数据格式:

(row:string, column:string, time:int64) -> string


上图是存储Web页面的一个例子:row name是一个反转URL,column family “contents:”存储页面内容,column family “anchor:”存储指向该页面的锚文字,列名”anchor:xxx”中的xxx反映了指向包含该页面连接的页面URL。数据可以有多个版本,通过时间戳来区别。

行:(Row)

行关键字可以是任意字符串,最大支持64KB(实际应用中典型大小是10-100字节)。对行的读写是原子的,不管读写行中的多少个列,在应用需要并发读写一个行时简化了应用的设计。

行通过行关键字的字典序排序,row range称做tablet,是动态划分的,是分布和负载均衡的单位。用户通过选择合适的row关键字可以实现locality(这里想表达的意思其实是把相关的数据连续分布,可以提高读写性能)。

列簇:(column family)

列关键字可以分组成column family,这是访问控制的基本单元。列簇中存储的数据往往是同类型的,我们把列簇中的数据压缩在一起。我们的设计原意是控制列簇的个数,并且列簇在使用中很少变化,而列的个数则是无限的。

列关键字格式如下:family:qualifier,列簇名必须是可打印的,qualifier可以是任意字符串。

访问控制以及磁盘、内存统计都是基于列簇的。

时间戳:(timestamp)

每个数据项可以有多个版本,通过时间戳区分。时间戳是一个64位整数,代表真实时间(毫秒级),可以由bigtable赋值也可以由应用指定,如果应用指定,需要保证时间戳不要冲突。多个版本的数据按时间戳降序排列,保证能够优先读取到最新版本的数据。

可以针对列簇设定GC策略:保留最近n个版本,或最近某段时间的版本。


3 API:(chapter 3 API)


创建删除table、创建删除column family、修改集群/表/列簇的元数据(例如访问权限)。应用可以写数据、删除数据、查找单独行的数据、或遍历一组数据。


在”com.cnn.www”行中增加一个anchor,删除另一个anchor。


遍历”com.cnn.www”行的所有anchor列。


BigTable支持:

1、 单行事务。

2、 允许数据项用作整数记数器(没明白什么意思?)

3、 支持在服务器侧运行client提供的脚本(类似存储过程,通过sawzall语言编写)。

可以和mapreduce一起使用,做为mapreduce的输入或输出。


4 Building Block(chapter 4)


l GFS:用来存储log和data file。

l Cluster Management System:用来执行job调度、管理共享机器的资源(BigTable和其他应用进程共享机器)、处理机器故障、监控机器状态。

l Google SSTable:用来存储Bigtable数据,SSTable提供一个持久化的、排序的、不变的Key-Value Map(persistent ordered immutable map from keys to values),key和value可以是任意字节串。支持通过key查找value,遍历一个key的范围。SSTable内部建立有索引,运行时把索引载入内存,也支持把所有数据载入内存。

l Chubby:分布式锁服务。BigTable通过chubby来保证任何时候最多只有一个activer的master;记录Table的根目录;发现tablet服务器的active状态;存储元数据信息(每个表的column family信息);存储ACL(access control list)。如果chubby不可用超过一段时间,则bigtable也就不可用了。根据我们的测试(14个bigtable集群、基于11个chubby实例),因为chubby不可用(因为chubby故障或网络问题)而导致bigtable不可用的概率为0.0047%。(The percentage for the single cluster that was most affected by Chubby unavailability was 0.0326%.这句怎么理解?)


5 实现(chapter 5 Implementation)


实现分为三个组件:

1、 客户端库:连接到Client应用中;

2、 一个Master Server:负责把tablet分配给tablet server,检测tablet server的加入和退出,平衡tablet server负载,GFS中文件的垃圾回收,处理元数据变化(像表、Column Family的创建)。数据读写过程并不经过master server,所以master server的负载通常很轻。

3、 多个Tablet Server:可以根据负载变化动态添加或删除。管理一组tablet(典型的从10几个到几千个),负责对他管理的tablet的读写请求,在tablet太大时进行切分。

一个Bigtable集群存储一组table,每一张table由一组tablet组成,每个tablet包含一个row range的所有数据。表刚刚创建时,只有一个tablet,随着表的增长,被自动切分成多个tablet,典型的每个tablet有100-200MB大小。


如何定位Tablet(Tablet Location)


使用一个类似B+树的三层目录结构保存tablet的定位信息。

一般来说,METADATA表项大概1KB,那么按照每个METADATA表128MB算,三层目录总共能索引2^34个数据Tablet,假设每个数据Tablet也是128MB,则总共能存储2^61字节数据(2EB)。

Client侧库会缓存元数据信息,并且会预取元数据信息;在tablet server侧,元数据信息是存储在内存中的。通过上述手段,我们能够加快tablet的定位过程。

元数据中也会存储一些其他信息,包括 a log of all events pertaining to each tablet。

Tablet分配(Tablet assignment)

每个tablet都会被指定一个tablet服务器。该工作是由master服务器完成的。

每个Tablet服务器会在Chubby中获取一个文件锁,Master服务器通过扫描chubby文件来发现Tablet服务器。Master服务器通过心跳来发现Tablet服务器故障,当tablet服务器心跳丢失时,Master服务器尝试去获取tablet服务器的文件锁,如果获取成功,就说明tablet服务器发生了故障,Master就重新故障Tablet服务器上的所有tablet。


当Master被CMS启动时,它通过如下步骤进行Tablet分配:

1、 从Chubby服务中获取Master锁,避免Master的多主现象。

2、 通过扫描Chubby中的文件发现所有Tablet Server。

3、 和所有Tablet服务器通信,发现已经分配的Tablet。

4、 (如果Root Metadata表还未分配则先分配Root Metadata)扫描元数据发现所有的Tablet,发现未分配的Tablet并进行分配。


在实际运行过程中,Tablet可能发生变化,Master通过如下方式跟踪Tablet的变化,并进行分配。在如下场景会发生Tablet的变化:

1、 创建、删除Tablet。

2、 合并Tablet。

3、 切分Tablet。

其中第1、2两种情况都是由Master发起的,Master自然知道这个变化信息。第三种情况是Tablet服务器发起的,需要由Tablet服务器通知Master服务器;如果该通知丢失,则在下一次Master通知Tablet服务器加载Tablet时会发现。


数据操作和合并(Tablet Servering & Compactions)

写操作:用户更新数据时,被写入memtable,同时记录在commit log中;当memtable大小增长到一定程度时,写入到磁盘的sstable中;当SSTable个数太多时,会进行多个SSTable的合并。

读操作:读取所有的SSTable和memtable,把数据合并之后给用户返回。

系统启动时:把SSTable的索引信息读入内存;并且根据commit log重建memtable。


6 细化设计(Refinements)


Locality Group

用户可以把多个列簇组成一个locality group,BigTable为每个Tablet每个locality group保存一个SSTable文件。

一般locality group的划分原则为,把不会一起读取的列簇划分到不同的locality group中,有利于提高读取效率。

还可以针对locality group指定一些优化参数,例如是否缓存到内存。


压缩(Compression)

用户可以控制SSTable是否压缩,以及压缩格式。压缩应用在一个SSTable Block上,SSTable Block的大小用户可以指定,之所以不对整个SSTable进行压缩,是为了在读取时避免解压整个SSTable,提高读取效率。

常用的是两遍压缩法,第一遍采用“Bentley and McIlroy’s scheme”在一个大窗口中发现公共子串,第二遍采用一个快速压缩算法在16k的窗口中发现重复子串。这样速度很快,压缩100-200MB/s,解压400-1000MB/s。

文中举了个例子,它们对WebTable的压缩比达到了10:1,而Gzip的典型的压缩率为3:1-4:1。为什么能够达到这么高的压缩率呢?这个跟Row的排序很相关,尽量选择一种合适的Row的编码格式,以便能够把相似的内容排到一起。


通过缓存提高读取性能(Caching for read performance)

使用两级缓存,Scan Cache和Block Cache。

l Scan Cache用于缓存BigTable接口返回的Key-Value对。这对于频繁读取的内容非常有用。

l Block Cache用于缓存读取GFS时返回的SSTable Block。这对于读取内容和最近读取内容很近的场景效果很好。


布隆过滤器(Bloom Filter)

布隆过滤器可以针对一个locality group指定是否创建,通过布隆过滤器可以发现一个row/column上是否有值,减少访问磁盘的个数。


Commit-log实现(Commit-log implementation)

如果为每个tablet记录一个Commit log,则Commit log的个数会太多,所以BigTable采用的方案是为每个Tablet Server记录一个Commit log。这带来了一个问题,不同的Tablet的commit log共存于一个物理文件,在Tablet Server故障恢复时,接替工作的多个Tablet Server都要读取commit log进行数据恢复,效率很低。所以在恢复时,先对commit log进行排序,把单个Tablet的commit log聚合成连续的块,然后在进行恢复。使用类似于map-reduce的方式加速排序过程。

日志文件有两个,每个通过一个线程写,发现写性能下降时,进行切换。


加速Tablet恢复(Speeding up tablet recovery)

当把Tablet从一个Tablet Server迁移到另一个Server时,如果通过commit log进行数据恢复,则效率过低。所以在迁移之前,先把数据全部保存到SSTable中,便于快速恢复。保存到SSTable中采用两遍写入磁盘法,第一遍写SSTable时同时提供BigTable服务,第二遍写SSTable时不提供BigTable服务,减少写SSTable过程中对业务的冲击。


采用只读SSTable(Exploiting immutability)

SSTable是只读的,一旦写入磁盘就不再变化,这简化了很多方面的设计:

l 简化了并发控制,变化的只有memtable,采用写时复制(copy-on-write)策略降低并发冲突。

l SSTable垃圾回收采用mark¬-and-sweep方式。

l Tablet拆分很容易,让拆分后的child tablet的SSTable共享parent tablet的SSTable