概述
BigTable 一定程度上是 NoSQL 的前身,也就是键值型数据库,存储的数据本质上就是键值对,所以 BigTable 顾名思义,就可以理解成一个支持分布式和巨量数据的 Map。在 BigTable 中键为行键(Row Key)、值为列族(Column Family)中的列元素,时间戳(Timestamp)用于支持值的多版本。
具体来讲,在 BigTable 中,其数据主要包含以下几个部分:
- 行键(Row Key):唯一标识每一行数据,是一个字符串,不可重复,BigTable 根据行键字典序进行排序
- 列族(Column Family):每一行可以包含多个列族,每个列族可以包含多个列,每个列族的数据结构是一样的,通过列限定符指定,可以包含不等数量的列值
- 时间戳(Timestamp):用于标识同一行不同版本的列值,根据策略,可以选择保留最近的 n 个版本,或保留限定时间内的所有版本
举个例子,在谷歌服务中,需要存储一个网页的数据,其中行键是网站的域名,例如 com.cnn.www
,倒序的原因是为了相关网站在行键上接近从而排在一起。设定两个列族,其中第一个是 content
,第二个是 tags
。比如第一个列族中,content
可以包含一个网站的 html 数据列和其他相关数据,第二个 tags
可以包含网站的相关标签。其中每一列的数据都可以指定一个时间戳,用于区分不同时间的网站版本。
需要注意每一个列族内的列值是可以不定数量的,例如某个网站可能有 3 个标签值,另一个网站可能有 5 个标签值,并不需要固定。
架构
在 BigTable 系统中,主要有以下几个角色:
- Client:客户端,对系统的数据进行增删改查
- Master:管理所有的数据,监控其他节点,负责全局系统的管理和统筹
- Tablet Server:表数据服务器,负责实际存储一部分的表数据,是被 Master 管理的节点
基于以上几个角色,BigTable 还设计了三种类型的表:具体的表数据是存储中 GFS 中的
- User Table:用于存储实际的用户数据,也就是键值对
- Meta Table:管理 User Table 的一些元数据,例如某个 User Table 在哪个服务器上
- Root Table:管理 Meta Table 的一些元数据,例如某个 Meta Table 在哪个服务器上
实际上三种类型的表可以看作是固定层级的索引关系,Meta Table 和 Root Table 就是两层的索引。由于每个 Table 可以支持数百兆的数据,两层索引就可以达到上千 PB 的数据量,也就不需要更高层的索引了。
我们可以进一步对 Table 这个概念进行拆分,分为逻辑 Table(Table) 和物理 Table(Tablet)。Table 是我们根据实际情况对一系列数据的划分,例如谷歌所有的网站数据作为一个表,所有的用户数据作为另一个表。而 Tablet 则是对 Table 的实现,例如所有网站数据的这个表可能达到 PB 的量级,任何一个单机系统都是存不下的,所以我们要把这个 Table 分散到各个服务器上存储,而这些服务器上实际存储的表就叫做物理 Tablet。
对于一个 Table 来说,刚开始是只会存储到一个 Tablet 上,也就是一台机器上,但当数据规模增大时,超出一个阈值,这个表就会进行分裂,将数据分散到多个机器上。根据 Row Key 值进行分裂,连续有序的 Row Key 会储存在一个 Tablet 中。
所以 Bigtable 集群会管理若干个 Table,每个 Table 由若干个 Tablet 组成,每个 Tablet 都会关联一个指定的 Row Key 范围,那么这个 Tablet 就包含了该 Table 在该范围内的所有数据。初始时,Table 会只有一个 Tablet,随着 Tablet 增大被 Tablet Server 自动切分,Table 就会包含越来越多的 Tablet。
需要注意的是 Root Table 只会有一份,不会进行分割,当要查询某个信息时,会先从 Root Table 开始一级一级往下查。
Master
Master 负责检测集群中 Tablet Server 的组成以及它们动态的加入和退出,负责 Tablet 至 Tablet Server 的分配,并负责均衡 Tablet Server 间的存储负载以及从 GFS 上回收无用的文件。除此之外,Master 还负责管理如 Table、Column Family 的创建和删除等操作。
Chubby 是一个基于 Paxos 的粗粒度分布式锁,每个 Tablet Server 都会在 Chubby 中持有一个对应文件的锁,Master 通过检测这个文件的互斥锁来判断 Tablet Server 加入和离开集群的事件。也就是 Tablet Server 会对该文件上锁,而 Master 通过能不能拿到这个锁来判断 Tablet Server 是否存在。如果 Tablet Server 退出集群,该锁会被释放,从而被认为 Tablet Server 已退出集群。在 Master 获得该 Tablet Server 对应文件的互斥锁时,会删除该文件,并在之后将该 Tablet Server 负责的 Table 重新分配到其他 Tablet Server 中。
同一时间,一个 Tablet 只能被分配给一个 Tablet Server。当一个 Tablet 还没有被分配、并且刚好有一个 Tablet 服务器有足够的空闲空间装载该 Tablet 时,Master 服务器会给这个 Tablet 服务器发送一个装载请求,把 Tablet 分配给这个服务器。需要注意,虽然一个 Tablet 被分配到一个 Tablet Server 中,但是储存 Tablet 的底层是 GFS,所以可用性是得到保证的。装载的并不是底层数据,最终的数据还是要从 GFS 获取。
Master 失效怎么办?
如果 Master 与 Chubby 之间的通信连接断开,新 Master 恢复的过程如下:
- 在 Chubby 上获取 Master 独有的锁,确保不会有另一个 Master 同时启动
- 利用 Chubby 获取仍有效的 Tablet Server
- 从各个 Tablet Server 处获取其所负责的 Tablet 列表,并向其表明自己作为新 Master 的身份,确保 Tablet Server 的后续通信能发往这个新 Master
- Master 确保 Root Tablet 及 Meta Table 的 Tablet 已完成分配
- Master 扫描 Meta Table 表获取集群中的所有 Tablet,并对未分配的 Tablet 重新进行分配
新老 Master 的交替可以有多种方案,例如选举一个新的 Master 节点。
Tablet Server
每个 Tablet Server 会负责管理若干个由 Master 指定的 Tablet,负责处理针对这些 Tablet 的读写请求,并负责在 Tablet 变得过大时对其进行切分。
在 Tablet Server 发现表过大时,会告知 Master 进行分裂操作,从而将一个大的 Tablet 分散到两个小的 Tablet 中。中 Tablet Server 发现表过小时,会告知 Master 进行合并操作,首先需要将两个表迁移到同一个 Tablet Server 中,然后再执行合并。
Tablet Server 会定期主动向 Master 汇报负载状态,Master 根据负载状态自动对整个集群进行负载均衡,其中涉及到 Tablet 的转移。
Tablet Server 底层的存储方式时 LSM-Tree,最终的数据落盘是在 GFS 中。所以一个 Tablet 由若干个位于 GFS 上的 sstable 文件和位于内存中的 memtable 组成。
性能优化
由于 Tablet 底层使用的是 LSM-Tree,为了支持写入性能牺牲了一定的读取性能,所以为了支持更好的读取性能,引入了缓存机制,分别为 Scan Cache 和 Block Cache。
Scan Cache 缓存了 Tablet 服务器锁获取的键值对,也就是对一些频繁访问的键值对做缓存。Block Cache 则是对 GFS 读取的 sstable 文件缓存,考虑了数据临近性。