Google Bigtable 分布式数据存储系统

1 概述

GFS的出现虽然解决了海量数据的存储问题,但是还是存在一个问题就是如果需要存储的数据是结构化的,对结构化数据的使用往往是希望和关系型数据库一样,进行复杂的数据操作的。但是GFS并没有支持基于特定属性(如行键、列名、时间戳)的高效查询、更新、聚合等操作。自然就需要发数据版本的关系型数据库,这就是分布式数据库

Bigtable作为一个管理大规模结构化数据而设计的分布式存储系统,可以扩展到PB级数据和上千台服务器。很多Google的项目使用Bigtable存储数据,这些应用对Bigtable提出了不同的挑战,比如数据规模的要求、延迟的要求。Bigtable能满足这些多变的要求,为这些产品成功地提供了灵活、高性能的存储解决方案。

Bigtable看起来像一个数据库,采用了很多数据库的实现策略。但是Bigtable并不支持完整的关系数据库模型,而是为客户端提供了一个简单的数据模型,客户端可以动态的控制数据的布局和格式,并且采用底层数据存储的局部性特征。Bigtable将数据统统看成无意义的字符串,客户端需要将结构化和非结构化数据串行化再存入Bigtable。

  • Bigtable是基于GFS而来的,底层使用GFS进行数据存储,所以满足了海量数据的存取要求。
  • 但是对数据进行灵活操作,需要在中间层里使用巧妙的数据组织逻辑进行数据管理。

总的来说,Bigtable依托于Google的GFS、Chubby及SSTable而生,用于解决Google内部不同产品在对数据存储的容量和响应时延需求的差异化,力求在确保能够容纳大量数据的同时减少数据的查询耗时。需要能够处理海量数据、提供灵活的、高性能的数据存储方案、使用类似于数据库的实现策略却不支持完整的数据库模型。

  • SSTable的全称是Sorted Strings Table,是一种不可修改的有序的键值映射,提供了查询、遍历等工嗯呢。每个SSTable由一系列的块组成,Bigtable将块设置为64KB。在SSTable的尾部存储块索引。
  • Chubby是一种高可用的分布式锁服务,Chubby有5个活跃副本,同时只有一个主副本提供服务,副本之间用Paxos算法维持一致性,Chubby提供了一个命名空间(包括一些目录和文件),每个目录和文件就是一个锁,Chubby的客户端必须和Chubby保持会话,客户端的会话若过期则会丢失所有的锁。

2 数据模型

Bigtable把数据存储在若干个table中,其数据特征是一个稀疏的、分布式的、持久化存储多维度排序映射。

  • 键有三维:
    • 行键字典序列组织,提供行级事务的支持,即程序在对某一行进行并发操作更新都是原子的,Tablet每一行都可以参与动态分区,一个分区称为一个Tablet,Tablet是Bigtable中数据分布和负载均衡的最小单位
    • 列键:Bigtable列列关键字组成的集合称为列族,它是对表进行访问控制的基本单位,访问权限包括增加新的基本数据、读取基本数据并创建继承的列族和浏览数据等,除了访问控制之外,磁盘和内存的使用统计也是在列族层面进行的。
    • 时间戳:区分数据的不同版本
  • 访问一条记录可以表示为:

3 Bigtable集群

Bigtable集群包括三个主要部分:一个供客户端使用的库、一个主服务器(master)、许多片服务器(tablet server)

Bigtable会将表(table)分片,片的大小维持在100-200MB范围。一旦超出范围就会分裂成更小的片,或者合并成更大的片。每个片服务器负责一定量的片,处理对其片的读写请求,以及片的分裂或合并。片服务器可以根据负载随时添加和删除。这里片服务器并不真实存储数据,而相当于一个连接Bigtable和GFS的代理,客户端的一些数据操作都通过片服务器代理间接访问GFS。

主服务器负责将片分配给片服务器,监控片服务器的添加和删除,平衡片服务器的负载,处理表和列族的创建等。注意,主服务器不存储任何片,不提供任何数据服务,也不提供片的定位信息。

客户端需要读写数据时,直接与片服务器联系。因为客户端并不需要从主服务器获取片的位置信息,所以大多数客户端从来不需要访问主服务器,主服务器的负载一般很轻。

4 片的定位

前面提到主服务器不提供片的信息,那么客户端是如何访问片的呢?Bigtable使用一个类似B+树的数据结构存储片的位置信息。

首先是第一层,Chubby file。这一层是一个Chubby文件,它保存着root tablet的位置。这个Chubby文件属于Chubby服务的一部分,一旦Chubby不可用,就意味着丢失了root tablet的位置,整个Bigtable也就不可用了。

第二层是root tablet。root tablet其实是元数据表(METADATA table)的第一个分片,它保存着元数据表其它片的位置。root tablet很特别,为了保证树的深度不变,root tablet从不分裂。

第三层是其它的元数据片,它们和root tablet一起组成完整的元数据表。每个元数据片都包含了许多用户片的位置信息。

可以看出整个定位系统其实只是两部分,一个Chubby文件,一个元数据表。注意元数据表虽然特殊,但也仍然服从前文的数据模型,每个分片也都是由专门的片服务器负责,这就是不需要主服务器提供位置信息的原因。客户端会缓存片的位置信息,如果在缓存里找不到一个片的位置信息,就需要查找这个三层结构了,包括访问一次Chubby服务,访问两次片服务器。

5 片的存储和访问

片的数据最终还是写到GFS里的,片在GFS里的物理形态就是若干个SSTable文件。图5展示了读写操作基本情况。

当片服务器收到一个写请求,片服务器首先检查请求是否合法。如果合法,先将写请求提交到日志去,然后将数据写入内存中的memtable。memtable相当于SSTable的缓存,当memtable成长到一定规模会被冻结,Bigtable随之创建一个新的memtable,并且将冻结的memtable转换为SSTable格式写入GFS,这个操作称为minor compaction。

当片服务器收到一个读请求,同样要检查请求是否合法。如果合法,这个读操作会查看所有SSTable文件和memtable的合并视图,因为SSTable和memtable本身都是已排序的,所以合并相当快。

每一次minor compaction都会产生一个新的SSTable文件,SSTable文件太多读操作的效率就降低了,所以Bigtable定期执行merging compaction操作,将几个SSTable和memtable合并为一个新的SSTable。BigTable还有个更厉害的叫major compaction,它将所有SSTable合并为一个新的SSTable。

遗憾的是,BigTable作者没有介绍memtable和SSTable的详细数据结构。

6 BigTable和GFS的关系

集群包括主服务器和片服务器,主服务器负责将片分配给片服务器,而具体的数据服务则全权由片服务器负责。但是不要误以为片服务器真的存储了数据(除了内存中memtable的数据),数据的真实位置只有GFS才知道,主服务器将片分配给片服务器的意思应该是,片服务器获取了片的所有SSTable文件名,片服务器通过一些索引机制可以知道所需要的数据在哪个SSTable文件,然后从GFS中读取SSTable文件的数据,这个SSTable文件可能分布在好几台chunkserver上。

7 元数据表的结构

元数据表(METADATA table)是一张特殊的表,它被用于数据的定位以及一些元数据服务,不可谓不重要。但是Bigtable论文里只给出了少量线索,而对表的具体结构没有说明。这里我试图根据论文的一些线索,猜测一下表的结构。首先列出论文中的线索:

The METADATA table stores the location of a tablet under a row key that is an encoding of the tablet's table identifier and its end row.
Each METADATA row stores approximately 1KB of data in memory(因为访问量比较大,元数据表是放在内存里的,这个优化在论文的locality groups中提到).This feature(将locality group放到内存中的特性) is useful for small pieces of data that are accessed frequently: we use it internally for the location column family in the METADATA table.
We also store secondary information in the METADATA table, including a log of all events pertaining to each tablet(such as when a server begins
serving it).
第一条线索,元数据表的行键是由片所属表名的id和片最后一行编码而成,所以每个片在元数据表中占据一条记录(一行),而且行键既包含了其所属表的信息也包含了其所拥有的行的范围。譬如采取最简单的编码方式,元数据表的行键等于strcat(表名,片最后一行的行键)。

第二点线索,除了知道元数据表的地址部分是常驻内存以外,还可以发现元数据表有一个列族称为location,我们已经知道元数据表每一行代表一个片,那么为什么需要一个列族来存储地址呢?因为每个片都可能由多个SSTable文件组成,列族可以用来存储任意多个SSTable文件的位置。一个合理的假设就是每个SSTable文件的位置信息占据一列,列名为location:filename。当然不一定非得用列键存储完整文件名,更大的可能性是把SSTable文件名存在值里。获取了文件名就可以向GFS索要数据了。

第三个线索告诉我们元数据表不止存储位置信息,也就是说列族不止location,这些数据暂时不是咱们关心的。

通过以上信息,我画了一个简化的Bigtable结构图:

结构图以Webtable表为例,表中存储了网易、百度和豆瓣的几个网页。当我们想查找百度贴吧昨天的网页内容,可以向Bigtable发出查询Webtable表的(com.baidu.tieba, contents:, yesterday)。

假设客户端没有该缓存,那么Bigtable访问root tablet的片服务器,希望得到该网页所属的片的位置信息在哪个元数据片中。使用METADATA.Webtable.com.baidu.tieba为行键在root tablet中查找,定位到最后一个比它大的是METADATA.Webtable.com.baidu.www,于是确定需要的就是元数据表的片A。访问片A的片服务器,继续查找Webtable.com.baidu.tieba,定位到Webtable.com.baidu.www是比它大的,确定需要的是Webtable表的片B。访问片B的片服务器,获得数据。

这里需要注意的是,每个片实际都由若干SSTable文件和memtable组成,而且这些SSTable和memtable都是已排序的。这就导致查找片B时,可能需要将所有SSTable和memtable都查找一遍;另外客户端应该不会直接从元数据表获得SSTable的文件名,而只是获得片属于片服务器的信息,通过片服务器为代理访问SSTable。

PS

API

由于数据是分布式存储的,所以其实Bigtable还是没办法办到像SQL一样灵活的对数据进行操作,其只能尽力在GFS上封装出一套完整的增删改查操作。Bigtable支持以下类型的API:

  • 建表、删表
  • 单行数据的增删查,以及用删除和增加组合出来的修改效果,只对单行数据的增删具有ACID特性。
  • 范围查询

架构

Bigtable包含了三个主要的组件,链接到客户程序中的库、一个Master服务器和多个Tablet服务器。

  • Master服务器主要负责以下工作

一张大表肯定是不能在一个服务器上的,而是被分成多份存在多个服务器上,一份就是一个逻辑单位Tablet。Bigtable架构中最核心的概念是Tablet。存放Tablet的节点在Bigtable体系中叫做tablet server,一个tablet server中存放多个Tablet。Bigtable在最底层把数据按照key进行排序后,进行分区,一个分区就是一个Tablet,而一个Tablet就是GFS中的一个文件。

tablet只是一个逻辑概念,指代特定范围的数据行。真正干活的是memtable和sstable以及下面的GFS。memtable和sstable可以理解为索引,存放数据的key以及数据存在GFS的哪个DataNode上。memtable是内存中的索引,sstable是一段时间后memtable落盘存储在磁盘上的索引。

tablet只是一个逻辑概念,指代特定范围的数据行。真正干活的是memtable和sstable以及下面的GFSmemtable和sstable可以理解为索引。一个tablet对应着一套memtable和sstable。由于大数据中数据是海量的,所以将索引结构存在磁盘,每次对数据的更新(写入、删除)都要去更新磁盘,肯定是扛不住的。所以将索引放在内存中是明智的。memtable是缓存,可以理解为是内存中的索引,一个tablet对应着一个memtable,其中记录着当前节点的tablet中的所有数据(key值+数据指针)。为了容错和可靠,memtable每隔一段时间或者到了一定的阈值都会落磁盘进行持久化,持久化为SSTable,一个tablet存在多个SSTable,这样设计的目的是省去了新老SSTable合并带来的额外磁盘IO拉低吞吐量,也可以起到数据版本记录的目的。

所以tablet server我们可以理解为长这个样子:

 memtable和SSTable中存放的什么内容:数据的key+数据存在GFS的哪个DataNode上

各个tablet server各自管理着一部分tablet信息,所以还需要一个全局的协调者(master节点)来负责记录下全局的:

哪些key在哪个tablet中,以及哪些tablet在哪些节点上。

一个完整的读写过程

读取过程

  1. 客户端发起读请求:客户端应用程序指定要读取的表名、行键以及列族、列限定符、时间戳范围等参数,构造一个读请求。
  2. 查找Tablet位置:客户端将读请求发送给Bigtable的Master节点。Master节点根据行键在Tablet分布图中查找对应的Tablet信息(包括TabletID和负责的Tablet Server地址)
  3. 转发读请求:

    Master 节点将查找到的 Tablet 位置信息返回给客户端。 客户端直接将读请求发送给对应的 Tablet Server。

  4. Tablet Server 处理读请求: Tablet Server 接收到读请求后,根据请求参数在本地存储的 SSTable 文件和 Memtable 中查找数据。 若数据存在于 SSTable 文件: Tablet Server 通过 GFS API 查询 SSTable 文件的元数据,获取其内部数据块(chunk)在 GFS 集群中的分布信息。 根据数据块位置信息,通过 GFS API 从相应的 DataNode 读取所需数据块内容。 将读取到的数据块内容拼接成完整的数据项,返回给客户端。 如果数据存在于多个版本(不同时间戳),按需选择合适的版本返回。 如果数据跨越多个 SSTable 或 Memtable,可能需要进行多版本合并或筛选。

  5. 响应客户端: Tablet Server 将查询结果打包成响应消息,发送回客户端。 客户端接收到响应后,解析并使用读取到的数据。

写入过程:

  1. 客户端发起写请求: 客户端应用程序指定要写入的表名、行键、列族、列限定符以及值(Cell Value)和时间戳(默认为当前时间),构造一个写请求
  2. 查找 Tablet 位置: 类似于读取过程,客户端首先将写请求发送给 Master 节点。 Master 节点查找对应的 Tablet 信息并返回给客户端。
  3. 转发写请求: 客户端直接将写请求发送给对应的 Tablet Server。
  4. Tablet Server 处理写请求: Tablet Server 接收到写请求后,将其写入内存中的 Memtable。 Memtable 刷写到 SSTable: 当 Memtable 达到一定大小或达到其他触发条件,Tablet Server 会触发 Memtable 刷写到本地磁盘,生成新的 SSTable 文件。 生成 SSTable 文件: Tablet Server 通过 GFS API 创建一个新的 SSTable 文件,并写入文件头、索引等元数据。 将 Memtable 中的数据按需排序,并组织成 SSTable 文件格式的数据块。 分散存储数据块: 将 SSTable 文件内部数据块(chunk)分散存储在 GFS 集群中: Tablet Server 通过 GFS API 将 SSTable 文件的数据块上传到 GFS 集群中的多个 DataNode。 GFS 根据其数据分布策略(如复制因子)自动将数据块复制到其他 DataNode,确保数据冗余和高可用。
  5. 响应客户端: Tablet Server 完成写入操作后,向客户端发送确认消息,表示写入成功。

如何查找到要的Tablet

在上一章节(第5章节)中我们大致聊了聊bigtable一次完整的读写过程,整个过程聊的粒度比较粗,这里面值得我们展开聊一聊的是在读写的时候如何准确的找到要的tablet。我们在读写的时候都是持有key值然后进来找其对应的tablet。这个找的过程是顺序遍历吗?肯定不是,大数据系统里面数据量这么多,顺序遍历性能肯定扛不住,所以在master上是维护着一个类树型的层级结构的:

树形结构与排列顺序:

  • 根节点:Root Tablet: 作为树的根节点,root tablet 是整个元数据层次结构的起始点。 它固定存储在一个已知的位置,如高度可靠的分布式锁服务(如 Chubby)中。
  • 中间节点:Metadata Tablets: 除 root tablet 外的 metadata tablets 可以看作是树的中间节点。 这些节点按照某种规则(如基于 tablet 行键范围的划分)组织成多层结构,形成一个分层的索引系统。 每个中间节点(metadata tablet)负责存储其子节点(通常是更低层级的 metadata tablets 或 user table tablets)的位置信息。
  • 叶子节点:User Table Tablets: User table tablets 作为树的叶子节点,代表实际存储用户数据的tablet。 它们没有进一步的子节点,每个叶子节点直接关联到一个具体的 user table tablet,包含其行键范围和 TabletServer 地址。

Metadata Tablet可以理解为专门用来在这个类树形结构里维护这个类树形结构的,用来维护好真正负责数据存储的user tablet在整个类树形结构中所处位置,便于进行高效的读写。

层级遍历:

  • 从根出发: 客户端首先访问 root tablet,获取到第一级 metadata tablets 的位置信息。
  • 逐层深入: 对于每一级 metadata tablets,客户端按照某种顺序(如行键范围的字典序)遍历它们,查询其中存储的子节点(下一级 metadata tablets 或 user table tablets)的位置信息。 如果子节点是 user table tablets,则定位完成;如果子节点是下一级 metadata tablets,则继续深入下一层进行遍历。
  • 定位目标: 在遍历过程中,客户端根据待查询或写入的行键,判断其所属的行键范围是否与当前遍历到的 tablet 匹配。 当找到包含目标行键范围的 user table tablet 时,遍历结束,此时客户端已经确定了目标tablet的位置。

LSM树

这里留个尾巴,前面一个章节我们说了在找具体的tablet的时候为了查找效率会维护着一个类树形的结构,那么作为直接负责数据存储的user tablet,其内部在进行数据查找的时候肯定也不是通过顺序遍历来实现的,也是有一定的数据结构来保证查找效率的,这就是LSM树,下一篇文章我们将聊一聊LSM树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值