经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》

一 概述

BigTable是以大神Jeffrey Dean为首的Google团队在2006年公开的分布式存储系统,是Google“三驾马车”论文中(GFS、MapReduce、BigTable)中最后公开的。在BigTable论文中,Google构思、设计并实现了一套支持结构化数据存储的超大容量分布式存储系统。BigTable中关于数据模型、底层存储技术和架构模型的设计思路直到今日仍被奉为经典,下面我们就按照这三个维度来看下BigTable的设计细节。

二 数据模型

BigTable本质上是一个为稀疏结构化数据设计的分布式多维排序Map。这里的“稀疏”指的是对于某一个结构化对象,有大量属性是缺失的,且多个对象之间缺失的属性分布也很分散。Map的Key由三个部分组成,分别是Row、Column Family:Column、Timestamp。
在这里插入图片描述
经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第1张图片

  • Row(行)
    行是结构化对象的一个实例。在BigTable中行是根据Key用字典序排序存储的。BigTable支持单行级别的原子更新操作,客户端无须在并行调用场景关心行操作的原子性。
  • Column Family(列簇):Column(列)
    列无须过多解释,此处重点介绍列簇。列簇是多个列的集合。一般情况下,存储在同一个列簇中的数据具有同样的类型,并且会压缩在一起。列簇还是访问权限控制的最小单元。
  • Timestamp(时间/版本)
    Timestamp是一个64-bit的时间版本号。BigTable可以存储同一份数据的多个版本,并支持按照版本进行访问。Timestamp可以由客户端自行指定,或由BigTable实时生成。
    经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第2张图片
  • Tablet
    Tablet是一个表区间,是按Key字典序连续存储的一个片段。Tablet的每行中包含了行的所有数据(即所有列簇/列和版本)。同时,Tablet是BigTable进行集群间数据调度的最小单位。随着Tablet中的数据增长,Tablet会发生Split(分裂);反之,随着Tablet中的数据变少,Tablet间会进行Merge(合并)。

三 底层存储技术

上一章介绍了BigTable的数据模型,本章重点介绍BigTable的底层数据存储技术。由于Tablet是数据调度的最小单位,就从Tablet开始拆解,展开看下BigTable的数据是如何存放的。
Tablet有一个Key Range组成,包含了这个Key Range中的所有数据。每一行Key又包含了多个列簇,同一个列簇内的数据被压缩并存放在SSTable中。
SSTable是Google内部的持久化KV存储结构,将数据按Key排序存储实现高效检索。SSTable被创建后,不可对其中内容更改,属于“只读结构”。SSTable内部由多个Block组成,每个Block默认大小64KB(支持配置),在SSTable的最后存放了Block的索引信息。使用SSTable时,首先将Block索引信息加载到内存中;检索时,首先通过二分查找检索Block索引确定Block,再从磁盘读取该Block的数据。
这样设计的好处是可以利用磁盘的顺序读取特性,提高查找性能。在2012年Google开源的检索组件LevelDB中,也用到了这样的设计。此外SSTable也可以直接映射到内存中,从而减少磁盘访问。
经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第3张图片
SSTable的“只读”设计,是BigTable很关键的一个设计,它带来了很多好处:

  • 高效实现了行粒度的的并发控制:并发读时,无需关心数据会正在被修改,无需加锁
  • 高效删除:将数据的删除转化为了SSTable的垃圾回收,通过“标记过期SSTable”和“回收SSTable”实现数据删除
  • 高效分裂:由于SSTable不可变更,当Tablet发生分裂时,子节点可以共享父节点的SSTable,无需进行拷贝或者计算

看到这里,会发现一个问题:如果SSTable是不可变的,那么BigTable是如何支持增删改操作的呢?
答案是通过MemTable。MemTable是存放在内存中的数据结构,当发生变更操作时,首先对MemTable进行写入;对应的,当读取数据时,会同时读取SSTable和MemTable,将结果合并。随着数据持续写入,MemTable不断增长,被写满后,会重新创建一个新的MemTable,老的MemTable会被锁定,然后merge到新的SSTable中。
但是,这种方案会引入一个新问题,就是当机器发生故障进程突然被kill时,MemTable中的数据会丢失。为了保证系统的稳定高可用,BigTable通过磁盘Log记录了所有数据commit操作,当发生内存数据丢失时,可以通过Log和SSTable的时间戳,恢复MemTable中的数据。
经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第4张图片

四 架构模型

至此已经详细说明了数据相关的概念与实现,接下来重点介绍下BigTable在集群层面做了哪些设计。
首先,一个BigTable集群由多个Tablet Server组成,并且在提供服务期间最多有一个Master节点。Master节点负责分配Tablet到指定Tablet Server、检测Tablet Server的加入和退出、负载均衡、垃圾回收。
经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第5张图片

为了实现这些能力,BigTable使用Google内部的Chubby进行集群管理。Chubby提供高可用、可持久化的分布式锁服务。Chubby在BigTabl中起到了很多关键性的作用:

  • 发现新机器和失效机器
  • 为集群管理提供分布式锁
  • 提供数据访问权限信息
  • 提供Root Tablet位置信息( Root Tablet 是第一个Meta Tablet,它从不Split)
  • 提供Table的Schema信息(主要是每个表中各个Column Family的信息)
  • 确保集群中最多只有一个Master节点

在设计中,为了避免单机容量不足、单机故障等稳定性风险,同时做到自动灵活负载均衡,BigTable中的Tablet可以在各个Tablet Server中来回穿梭,以保证各个机器之间存储负载均衡。那么在检索时,如何确定我要检索的Key所对应的Tablet在哪台机器上呢?下面就来回答这个问题。
经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第6张图片

BigTable采用了类B+树的三级索引:

  • 首先通过Chubby找到索引入口——Root Tablet——所在的机器
  • 然后到这台机器中查找对应的Meta Tablet所在的位置
  • Meta Tablet中存放了Tablet的机器分布情况
    其中Root Tablet实际上就是第0个Meta Tablet,它唯一特殊的地方就在不会发生分裂,这样做是为了保证这颗“分布式B+树”最多只有3层。
    当需要进行Tablet定位时,只需要从Chubby开始“自上而下”访问“分布式B+树”,即可获得Tablet->IP的映射信息。每次获取后,映射信息会缓存在客户端,以便高效访问。当Tablet位置发生变化,客户端缓存失效,则客户端会“自下而上”逐层申请获取Tablet最新地址。这样一来,服务端路由信息便可以和客户端有效同步。

五 性能表现

经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第7张图片
从论文中公布的数据来看,BigTable在性能方面是首屈一指的。1KB数据单机内存随机读可以支持1w+ qps,平均单次访问0.1ms,这个数据基本和当前流行的Redis持平。不过,论文中没有给出TP99、TP999等数据,服务的抖动情况不得而知。

六 思考

6.1 CAP视角下的BigTable

CAP约束是经典的数据库理论,我们从CAP的视角看下BigTable做了怎样的取舍。
CAP指的是Consistency(一致性,这里特指外部一致性,即强一致)、Availability(可用性)和Partition tolerance(分区容忍性)。Tablet在机器间穿梭的设计使BigTable满足了分区容忍性;同一时间最多只有一组Tablet对外提供服务的设计使其具备了数据的一致性;当Tablet切换的过程中或者某台Tablet Server宕机时,部分数据不能对外提供服务,因此BigTable没有满足高可用。
满足CP,不满足A,适用于很多离线检索和非稳定性敏感的在线场景。

6.2 SSTable内数据存储格式

对于BigTable中SSTable的实现论文并没有解释透彻,不禁让人引发好奇的思考:SSTable内部是如何存储数据的呢?
根据论文中的描述,我们可以总结如下关键点:

  • 同一个列簇的数据是存放在一起的
  • API接口设计中,首先锁定列簇,然后检索列,最后遍历所有行
  • API接口支持返回一行的指定版本或所有版本,最后遍历所有行
    经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第8张图片

通过这些信息,我们可以做出如下推测:

  • 在检索时,每个SSTable中有至少一个列簇的全部信息
  • 在确定存储指定列簇的SSTable后,其中的数据按照“列->版本->行”的优先级进行排序存储
    这里特别解释下为什么没有推测是按照“列->行->版本”的版本进行存储。个人猜想在实际使用场景中,更多的是对最新版本的所有行进行遍历,按照“列->版本->行”在遍历时能有效利用磁盘高效顺序读的特性。
    经典论文研读:《Bigtable: A Distributed Storage System for Structured Data》_第9张图片

6.3 BigTable设计中的一些取舍

BigTable在设计中是否做了一些取舍呢?
我目前了解到,由于BigTable中Key直接按照字典序的方式存放,当在实际使用中对前缀相同的大量Key进行检索时,会命中同一台Tablet Server,从而引发热点,影响服务性能。我猜测,BigTable设计者在进行取舍时,更多的倾向于让BigTable拥有更好的性能,当任务落在同一台机器上时无疑对单任务性能是最优的;反之,若将Key打散存储,同一个检索任务会被分散到很多Tablet Server上,虽然热点问题解决了,但是单任务的性能也得不到保障。
在具体场景中,可以利用BigTable的主体思想,对细节进行改造。

你可能感兴趣的:(论文研读,检索技术,论文研读,数据存储,原力计划)