HBase 数据模型

By timebusker on January 14, 2019

从使用角度来看,HBase包含了大量关系型数据库的基本概念——表、行、列,但在BigTable的论文中又称 HBase为“sparse,distributed,persistent multidimensional sorted map”,即HBase本质来看是一个Map。 那HBase到底是一个什么样的数据库呢?

实际上,从逻辑视图来看,HBase中的数据是以表形式进行组织的,而且和关系型数据库中的表一样, HBase中的表也由行和列构成,因此HBase非常容易理解。但从物理视图来看,HBase是一个Map,由键值(KeyValue,KV)构成, 不过与普通的Map不同,HBase是一个稀疏的、分布式的、多维排序的Map。接下来,笔者首先从逻辑视图层面对HBase中的基本概念进行介绍, 接着从稀疏多维排序Map这个视角进行深入解析,最后从物理视图层面说明HBase中的数据如何存储。

逻辑视图

在具体了解逻辑视图之前有必要先看看HBase中的基本概念。

  • table:表,一个表包含多行数据。

  • row:行,一行数据包含一个唯一标识rowkey、多个column以及对应的值。在HBase中,一张表中所有row都按照rowkey的字典序(二进制位移计算)由小到大排序。

  • column:列,与关系型数据库中的列不同,HBase中的column由column family(列簇)以及qualifier(列名)两部分组成,两者中间使用”:”相连。 比如contents:html,其中contents为列簇,html为列簇下具体的一列。column family在表创建的时候需要指定,用户不能随意增减。 一个column family下可以设置任意多个qualifier,因此可以理解为HBase中的列可以动态增加,理论上甚至可以扩展到上百万列。

  • timestamp:时间戳,每个cell在写入HBase的时候都会默认分配一个时间戳作为该cell的版本,当然,用户也可以在写入的时候自带时间戳。 HBase支持多版本特性,即同一rowkey、column下可以有多个value存在,这些value使用timestamp作为版本号,版本越大,表示数据越新。

  • ·cell:单元格,由五元组(row,column,timestamp,type,value)组成的结构,其中type表示Put/Delete这样的操作类型, timestamp代表这个cell的版本。这个结构在数据库中实际是以KV结构存储的,其中(row,column,timestamp,type)是K,value字段对应KV结构的V。

数据模型

上图是BigTable中一张示例表的逻辑视图,表中主要存储网页信息。示例表中包含两行数据,两个rowkey分别为com.cnn.www (http://com.cnn.www) 和com.example.www (http://com.example.www),按照字典序由小到大排列。每行数据有三个列簇,分别为anchor、contents以及people,其中列簇anchor下有两列, 分别为cnnsi.com以及my.look.ca,其他两个列簇都仅有一列。可以看出,根据行com.cnn.www (http://com.cnn.www)以及列anchor:nnsi.com (http://anchor:nnsi.com) 可以定位到数据CNN,对应的时间戳信息是t9。而同一行的另一列contents:html下却有三个版本的数据,版本号分别为t5、t6和t7。

总体来看,HBase的逻辑视图是比较容易理解的,需要注意的是,HBase引入了列簇的概念,列簇下的列可以动态扩展;另外,HBase使用时间戳实现了数据的多版本支持。

多维稀疏排序Map

使用关系型数据库中表的概念来描述HBase,对于HBase的入门使用大有裨益,然而,对于理解HBase的工作原理意义不大。要真正理解HBase的 工作原理,需要从KV数据库这个视角重新对其审视。BigTable论文中称BigTable为”sparse,distributed,persistent multidimensional sorted map”,可见BigTable本质上是一个Map结构数据库,HBase亦然,也是由一系列KV构成的。

然而HBase这个Map系统却并不简单,有很多限定词——稀疏的、分布式的、持久性的、多维的以及排序的。接下来,我们先对这个Map进行解析,这对于之后理解HBase的工作原理非常重要。 大家都知道Map由key和value组成,那组成HBase Map的key和value分别是什么?和普通Map的KV不同,HBase中Map的key是一个复合键, 由rowkey、column family、qualifier、type以及timestamp组成,value即为cell的值。举个例子,上节逻辑视图中行”com.cnn.www”以及列”anchor:cnnsi.com”对应的数值”CNN”实际上 在HBase中存储为如下KV结构:{"com.cnn.www","anchor","cnnsi.com","put","t9"} -> "CNN"

同理,其他的KV还有:

{"com.cnn.www","anchor","my.look.ca","put","t8"} -> "CNN.com"
{"com.cnn.www","contents","html","put","t7"} -> "<html>..."
{"com.cnn.www","contents","html","put","t6"} -> "<html>..."
{"com.cnn.www","contents","html","put","t5"} -> "<html>..."
{"com.example.www","people","author","put","t5"} -> "John Doe"

至此,读者对HBase中数据的存储形式有了初步的了解,在此基础上再来介绍多维、稀疏、排序等关键词。

  • 多维:这个特性比较容易理解。HBase中的Map与普通Map最大的不同在于,key是一个复合数据结构,由多维元素构成,包括rowkey、column family、qualifier、type以及timestamp。

  • 稀疏:稀疏性是HBase一个突出特点。从上图的逻辑表中行”com.example.www”可以看出,整整一行仅有一列(people:author)有值,其他列都为空值。 在其他数据库中,对于空值的处理一般都会填充null,而对于HBase,空值不需要任何填充。这个特性为什么重要?因为HBase的列 在理论上是允许无限扩展的,对于成百万列的表来说,通常都会存在大量的空值,如果使用填充null的策略,势必会造成大量空间的浪费。 因此稀疏性是HBase的列可以无限扩展的一个重要条件。

  • 排序:构成HBase的KV在同一个文件中都是有序的,但规则并不是仅仅按照rowkey排序,而是按照KV中的key进行排序——先比较rowkey,rowkey小的排在前面; 如果rowkey相同,再比较column,即column family:qualifier,column小的排在前面;如果column还相同,再比较时间戳timestamp,即版本信息,timestamp大的排在前面。 这样的多维元素排序规则对于提升HBase的读取性能至关重要,在后面读取章节会详细分析。

  • 分布式:很容易理解,构成HBase的所有Map并不集中在某台机器上,而是分布在整个集群中。

物理视图

与大多数数据库系统不同,HBase中的数据是按照列簇存储的,即将数据按照列簇分别存储在不同的目录中。

列簇anchor的所有数据存储在一起形成:

数据模型

列簇contents的所有数据存储在一起形成:

数据模型

列簇people的所有数据存储在一起形成:

数据模型

行式存储、列式存储、列簇式存储

为什么HBase要将数据按照列簇分别存储?回答这个问题之前需要先了解两个非常常见的概念:行式存储、列式存储,这是数据存储领域比较常见的两种数据存储方式。

  • 行式存储:行式存储系统会将一行数据存储在一起,一行数据写完之后再接着写下一行,最典型的如MySQL这类关系型数据库。

数据模型

行式存储在获取一行数据时是很高效的,但是如果某个查询只需要读取表中指定列对应的数据,那么行式存储会先取出一行行数据, 再在每一行数据中截取待查找目标列。这种处理方式在查找过程中引入了大量无用列信息,从而导致大量内存占用。因此,这类系统仅适合于处理OLTP类型的负载, 对于OLAP这类分析型负载并不擅长。

  • 列式存储:列式存储理论上会将一列数据存储在一起,不同列的数据分别集中存储,最典型的如Kudu、Parquet on HDFS等系统(文件格式)。

数据模型

列式存储对于只查找某些列数据的请求非常高效,只需要连续读出所有待查目标列,然后遍历处理即可; 但是反过来,列式存储对于获取一行的请求就不那么高效了,需要多次IO读多个列数据,最终合并得到一行数据。 另外,因为同一列的数据通常都具有相同的数据类型,因此列式存储具有天然的高压缩特性。

列簇式存储:从概念上来说,列簇式存储介于行式存储和列式存储之间,可以通过不同的设计思路在行式存储和列式存储两者之间相互切换。 比如,一张表只设置一个列簇,这个列簇包含所有用户的列。HBase中一个列簇的数据是存储在一起的,因此这种设计模式就等同于行式存储。 再比如,一张表设置大量列簇,每个列簇下仅有一列,很显然这种设计模式就等同于列式存储。 上面两例当然是两种极端的情况,在当前体系中不建议设置太多列簇,但是这种架构为HBase将来演变成HTAP(Hybrid Transactional and Analytical Processing) 系统提供了最核心的基础。