BigTable架构

本文为阅读BigTable论文后的总结, 因为加入了个人理解. 所以不一定与原文一致. 可能有部分对原文的误解. 欢迎指正.

1 数据模型

1.1 数据

简单理解, Bigtable存储的结构类似 'key-value'.

  • 'key' 称为RowKey
  • 'value'可以分多列(Column)存储多个Value
  • 相关的Column(一般数据类型相同)被聚为列族(ColumnFamily)
  • Value可以存储多个版本. 每个版本有Timestamp标识

可以总结为如下关系:
(RowKey, ColumnFamily:Column, Timestamp) → Value

类似如下表格:


BigTable架构_第1张图片
image.png

1.2 操作

支持通过RowKey获取, 或者Scan RowKey范围操作

2 实现

每一个RowKey范围, 会对应一个数据块. 这个数据块被称作Tablet.

需要注意的是: 这里的Tablet是一个逻辑上的概念, 对应着一块数据. 一般保存在GFS上, 一般会用HDFS替代. 但这个块具体如何保存, 是否是一个文件来保存. 将在后面介绍.

所以BigTable的主要目标就是设计一套. 能够支撑在近实时时间内, 根据RowKey范围, 从大量Tablet中找到指定Tablet, 并读取其中数据的系统.
这其中的两个要点:

  • a. 如何根据RowKey. 迅速找到Tablet的位置.
  • b. 如何在近实时的时间内, 读取Tablet数据.
    此外还需要考虑:
  • c. 如何调度多个机器. 当机器挂掉时, 如何保证数据仍然可以访问.

2.1 Tablet的存储

这一部分可以看做是对问题a的解答.

Server端的数据组织

  • 用户定义了若干张表. 每张表都有若干数据块Tablet. 从哪去查找这些Tablet的位置?
    BigTable会为这些Tablet建立一个B+树的索引(哪些RowKey范围去找哪个Tablet). 这些索引成为MetaData. 用若干数据块来存储, 称为MetaDataTablet.

  • MetaDataTablet也很多. 从哪去查找MetaDataTablet的位置?
    所以在MetaDataTablet的上层又建立了一个索引, 用来快速查找MetaDataTablet. 这个索引保存的数据块称为RootTablet.
    RootTablet和MetaDataTablet很像, 都可以看做是保存了索引的数据块. 但是不同点在于RootTablet只会有一个, 不会再分割.

  • RootTablet只有一个, 但从哪去查找它的位置?
    BigTable会从Chubby上查找. Chubby是Google内部使用的高可用性分布式锁服务, 一般会用zookeeper, etcd来替代.

ok. 这样形成了一个三层结构. 用论文原图表示如下:


BigTable架构_第2张图片
image.png

Tablet的查找

  • ClientLibrary(由于后面会讲到, 真正去查找Tablet位置的并不是客户端Client, 而是Tablet Server. 因此这里称为ClientLibrary)第一次查找时, 从Chubby逐层查找. 共查找三次.

  • ClientLibrary将这三层关系Tablet的位置缓存起来. 查找一个Tablet时, 根据自己的缓存直接找到对应Tablet的位置.

  • 一段时间后, Client缓存失效. Client按照失效的缓存去查找. 发现根据缓存查找Tablet不存在, 或者错误的时候. 会根据缓存找到上一层, 即MetaDataTablet, 根据MetaDataTablet重新查找, 并更新缓存.

  • 如果根据缓存位置查找MetaDataTablet仍然找不到或者找错. 那么会如法炮制, 在从更上一层查找. 直到最终, 通过Chubby找到最新数据.

2.2 Tablet服务

Tablet是一个逻辑上的数据块概念, 你要的数据就在这块, 但具体怎么存储的,
要怎么取走这些数据. Client作为一个小白, 表示有点难办.
这一部分可以看做是对问题b的解答.

因此, BigTable使用若干服务器来帮助Client处理对Tablet的访问(可以理解为代理服务器). 这些服务器称为Tablet Server

每个Tablet Server负责几个Tablet的查找. 客户端只要知道要找的Tablet该找哪个Tablet Server取就行(具体要哪段数据找哪个Server将在后面介绍).

一个Tablet Server采取如下结构:


BigTable架构_第3张图片
image.png

写操作

  • 首先, Write操作被写进位于GFS的Tablet Log. 写操作就完成了.
    然后Tablet Server会将Log里的内容逐一异步处理. 处理的方式是把更新保存到一段内存空间中, 称为MemTable.

  • 当Write操作进行多次后, MemTable越来越大, 当达到一定阈值的时候.
    原有的MemTable被冻结, 新生成一个新的MemTable. 被冻结的MemTable持久化存储到GFS上, 称为一个SSTable. 这个过程称为Minor Compaction

  • 每次Minor Compaction生成一个SSTable. 长此以往, 会有大量SSTable生成. 导致读操作需要检查大量的SSTable. 因此, Tablet Server会将多个SSTableMemTable中的数据Merge起来, 生成一个新的SSTable. 这个过程称为Major Compaction

  • 当进行delete操作的时候, 也同样只是向Log中写入了一条delete记录. 这条记录被加载到MemTable后, 代表操作已经生效. 但并不会覆盖原先的update记录. 当用户读取数据时, 会取最新的操作得知数据已经被delete.
    而当进行Major Compaction时, 会对记录进行merge. 删去删除的数据.

读操作

  • 读操作会将MemTable和SSTable的存储内容组成一个大的view. 从中进行读取.

重建Tablet

  • 一个Tablet指一段RowKey范围对应的逻辑数据块. 而实际存储在一个个SSTable中. 在MetaTablet中存储的就是这些SSTable的列表, 以及一系列的redo points(可以理解为存档点).
  • 当Tablet Server恢复一个Tablet数据的时候, 会通过ClientLibrary从MetaTablet中加载SSTable的位置, 然后将SSTable读入内存中, 根据redo points把redo points之后的更新一步步应用过来.

2.3 Tablet分配

一个Tablet Server负责多个Tablet. 那么具体负责哪几个. 如何保证每个Tablet都有Server负责. Server之间不会处理同一个Server.
当有Server挂掉的时候, 如何发现Server挂了. 又如何把它的数据重新分配给其他Server.
这一部分可以看做是对问题c的解答.

BigTable设计了一个Master服务来解决这一问题.

  • 通过Chubby的锁机制保证只有一个Master.
  • Master通过Chubby锁来检查哪些Server仍然存活.
  • Master和所有活着的Server进行通信, 了解他们都负责哪些Tablet.
  • Master扫描MetaData Tablet. 看看总共有哪些Tablet.
  • 如果有unassigned Tablet. Master会将这些Tablet分给Tablet Server. Tablet Server通过2.2章节的 重建Tablet 过程加载Tablet.
  • 当Master通过Chubby发现某个Tablet Server挂掉, 并且断定服务无法继续时. 会将这个Tablet Server负责的Tablet标识为unassigned. 并重新分配给其他Tablet Server
  • 此外, 当某个Tablet过大的时候, Tablet会将这个Tablet分为多个. 这时候Tablet Server也会通知Master

Client端通过Master知道哪个RowKey范围应该找哪个Tablet Server. 但是, 数据直接与Tablet Server通信获得. 并且这个关系会缓存在Client端. 因此Master的负载并不重.

3 优化

3.1 Locality groups

  • client端可以将多个Column Family聚合成一个Locality groups. 不同Locality groups的数据通常不一同读取. 因此在底层存储的时候可以把不同Locality groups的数据分在不同的SSTable上.

  • 另外, 某些Locality groups也可以声明为in-memory, 这样读取的时候可以避免磁盘操作, 提高性能.

3.2 压缩

3.3 缓存

BigTable有两层缓存:

  • higher-level cache: SSTablet接口返回的Key-Value结果会缓存.
  • lower-level cache: 缓存从GFS取出的SSTable.

3.4 BoomFilter

  • 当判断一个SSTable是否有指定RowKey的数据时, 使用BoomFilter. 减少不必要的磁盘操作.

3.5 Commit-log实现

  • 为了优化性能, 每个Tablet Server有一个Log. 这个Server负责的Tablet公用这个Log.
  • 当恢复数据的时候, Tablet Server负责的Tablet会被分配到不同的Server上. 每个Server都读入这个Log并从中取出属于分配给自己的Tablet相关的log.
  • 为了优化上述恢复数据过程. 首先会对Log进行排序, 这样每个其他的Server只需要加载其中属于的一段Log即可.
  • 此外, 为了避免GFS的抖动. 一个Server会维护两个写队列. 同时只使用一个, 当抖动的时候切换另一个队列.

3.6 加速Tablet恢复

当Master把一个Tablet从Server迁移到另一个Server的时候.

  • 原Server会先进行一次Minor Compaction.
  • Minor Compaction进行完后, 停止服务.
  • 原Server再进行一次Minor Compaction. 把第一次Compaction到停止服务之间的commit添加进来.
  • 原Server unloads Tablet

3.7 利用不变性

  • BigTable进行了如下简化约定: 每个SSTablet都是immutable.
  • 唯一mutable的数据是MemTable中的数据, 这部分数据会被同时读和写. 为此, BigTable使用Copy-on-Write策略保证读写能够并行.

你可能感兴趣的:(BigTable架构)