HBase的数据结构原理与使用
一、HBase简介
HBase是一个开源的、分布式的、版本化的NoSQL数据库(即非关系型数据库),依托Hadoop分布式文件系统HDFS提供分布式数据存储,利用MapReduce来处理海量数据,用Zookeeper作为其分布式协同服务,一般用于存储海量数据。HDFS和HBase的区别在于,HDFS是文件系统,而HBase是数据库。HBase只是一个NoSQL数据库,把数据存在HDFS上。可以把HBase当做是MySQL,把HDFS当做是硬盘。
二、HBase的数据结构
1、索引结构:LSM树
传统关系型数据普通索引采用B+树。B+树最大的性能问题是会产生大量的随机IO,随着新数据的插入,叶子节点会慢慢分裂,逻辑上连续的叶子节点在磁盘存储上往往不连续,分离得很远,随机读写概率会变大,做范围查询时,会产生大量读随机IO。为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees,直译为日志结构合并树。基于LSM树实现的HBase的写性能相比Mysql放弃部分磁盘读性能,换取写性能的大幅提升。
LSM树严格来说不是一个具体的数据结构,更多是一种数据结构的设计思想。LSM树不是一棵树,而是由至少两个存储结构构成。假设这两颗树分别为C0和C1,C0比较小,全部驻于内存之中,具体可以是任何方便健值查找的数据结构。而C1则驻于机械硬盘。一条新的记录先是从C0中插入,如果这一次的插入造成了C0数据量超出了阀值,那么C0中的部分些数据片段则会直接合并到C1树中。如果有多级树,当C1体量越来越大就向C2合并,低级的树在达到大小阈值后也会在磁盘中进行合并,以此类推,一直往上合并Ck。
LSM树的设计思想:
划分不同等级的树。将对数据的修改增量保持在内存中,数据更新只在内存中操作,没有磁盘访问。达到指定的大小限制后将这些修改操作批量写入磁盘。由于内存的读写速率都比磁盘要快非常多,因此数据写入内存的效率很高。随着小树越来越大,达到指定的阀值限制后将这些修改操作批量写入磁盘,磁盘中的树定期做多路归并操作,合并成一棵大树,以优化读性能。随机读写比顺序读写慢很多,为了提升IO性能,需要将随机操作变为顺序操作。LSM树使用日志文件和一个内存存储结构把随机写转化成顺序写,读写独立,数据从内存刷入磁盘时是预排序的,写性能大幅提升。读取的时候稍微麻烦,需要先看是否命中内存,如果读取的是最近访问过的数据则可以命中,否则需要访问较多的磁盘文件。
使用LSM树的数据库除了HBase,还有nessDB、levelDB、TiDB、RocksDB等。
(图中MongoDB只有WiredTiger(WT)存储引擎既支持B-树,又支持LSM树存储索引。)
2、存储结构
HBase的LSM树中存储的是多个Key-Value结构组成的集合,每一个Key-Value一般都会用一个字节数组来表示。这个字节数组串设计如图所示:
(图源:胡争,范欣欣《HBase原理与实践》第二章《基础数据结构与算法》)
字节数组主要分为以下几个字段。其中Rowkey、Family、Qualifier、Timestamp、Type这5个字段组成KeyValue中的key部分。
• keyLen:用来存储KeyValue结构中Key所占用的字节长度。
• valueLen:用来存储KeyValue结构中Value所占用的字节长度。
• rowkeyLen:用来存储rowkey占用的字节长度。
• rowkeyBytes:用来存储rowkey的二进制内容。
• familyLen:用来存储Family占用的字节长度。
• familyBytes:用来存储Family的二进制内容。
• qualif ierBytes:用来存储Qualif ier的二进制内。注意,HBase并没有单独分配字节用来存储qualif ierLen,因为可以通过keyLen和其他字段的长度计算出qualif ierLen。
• timestamp:表示timestamp对应的long值。
• type:表示这个KeyValue操作的类型,HBase内有Put、Delete、Delete Column、DeleteFamily,等等。
HBase的LSM树在内存一般采用跳跃表存储,跳跃表的查找、删除、插入的复杂度都是O(logN)。
LSM树在磁盘中的数据结构也不是树结构,而是Key-Value结构组成的序列,称为SSTable(Sorted String Table)有序字符串表。当SSTable太大时,为了加快SSTable的读取,可以将其划分为多个块,通过记录每个块的起始位置,构建每个SSTable的稀疏索引。这样在读SSTable前,通过索引就知道要读取的数据块磁盘位置了。SSTable索引需要永远加载在内存里。写是写内存,因此随机写十分快。读也是读内存里的 SSTable的索引,并且这里每一个SSTable索引如果用二分法查找,算法复杂度大致在O(lg(n))与O(n)之间,因此随机读也不慢。
3、表结构
与传统的关系型数据库类似,HBase也以表的形式组织数据,表也由行和列组成,不同的是,HBase采用列式存储。
如上图所示的表,如果采用列式存储,会存成下图的结构:
可以发现,列式存储就是把每列抽出来,然后关联上ID,实际上是用Key-Value结构保存的。这样的优点在于,当表格中有空缺时,可以充分利用存储空间。
对HBase来说,一行数据由一个行键(RowKey)和一个或多个相关的列以及它的值所组成。列的组成都是灵活的,行与行之间的列不需要相同。行键(RowKey)就是SSTable的key。
在HBase里边,先有列族(也叫“列簇”,Column Family),后有列。列族将一列或者多列组织在一起,HBase的每一个列都必须属于某个列族。HBase的列都得归属到列族中,如图所示:
数据写到HBase的时候都会被记录一个时间戳,这个时间戳被我们当做一个版本。比如说,我们修改或者删除某一条的时候,本质上是往里边新增一条数据,记录的版本加一了而已。如图所示:
被更新和删除的数据不会直接从磁盘上删除,而是为数据添加一个删除标记,查找时会跳过被删除的键,DBA运维会定期删除被标记删除的数据。因此,如果存在频繁覆盖删除需要提前向运维报备以免影响数据库性能。
三、HBase的使用
1、HBase的读写
HBase提供了多种模式、多种语言的访问接口。目前常用的包括Native Java API,Thrift和MapReduce模式。
(1)Java API是HBase提供的原生接口,具备完善的客户端处理逻辑,直接与HBase Server进行通信,效率最高,但受限于语言限制;
(2)Thrift不受语言限制,但会占用额外的网络带宽和处理时间;
(3)HBase还支持了MapReduce,可以通过编写MapReduce任务进行批量数据操作。
使用GoLang和PHP语言搭建的项目显然得用Thrift接口。
常用的HBase的数据操作get、scan和put三种。
(1)get实现随机读取功能,根据指定RowKey获取惟一一条记录。
(2)scan提供批量查询功能,按照指定的条件获取一批记录。通过指定起始和中止的key,即可获取所有包含在内的key对应的数据。可以通过setStartRow与setEndRow来限定范围,也可以通过setFilter方法添加过滤器,这也是分页、多条件查询的基础,用setCaching和setBatch方法能提高速度。
(3)put实现写入,如果要批量导入大规模数据,还可以采用bulkimport的方式。
2、行键(RowKey)设计
Rowkey相当于HBase中数据的主键。HBase中的数据是按照RowKey的ASCII字典顺序进行全局排序。可以使相关行彼此靠近存储。如果Rowkey设计不当会引发热点问题,即客户端大量的读写请求都集中在一个或几个节点上。从而导致性能下降。为防止数据写入时出现热点,数据被写入时应写入集群中的多个区域,而不是一次写入一个区域(Hregion)。
设计原则:
1、唯一原则,要保证Rowkey的唯一性。若HBase中同一表插入相同Rowkey,则原先的数据会被覆盖掉。设计Rowkey的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
2、长度原则。Rowkey长度越短越好,一般不要超过16字节。因为RowKey是一个二进制码流,可以是任意字符串,最大长度64KB,实际应用中一般为10-100字节,以byte[]形式保存。如果RowKey过长比如500个字节,1000万列数据仅RowKey就要占用5GB空间,非常影响HFile的存储效率。
3、散列原则。用时间戳作为Rowkey的前缀会导致大量数据堆积在一个区域进而导致热点问题。如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,低位放时间字段。
3、列族(Column Family)的设计
设计原则:
1、列族的名称尽可能短,甚至可以是一个字符。例如,“d”表示数据/默认值。
2、HBase当前不能很好地处理超过两个或三个列族的数据,因此请保持列族的数量较少。最好使用一个列族。仅在数据访问通常是列范围的情况下才引入第二和第三列族。即,一次只查询一个列族,通常不会查询两个列族。
3、将相同IO特性的列放入同一列族。
4、多个列族中的数据(行数)分布大致均匀。
5、对于临时性的列族可以设置失效时间。一旦达到到期时间,HBase将自动删除行。
4、HBase Shell的安装和使用
HBase自带的操作工具只有HBase Shell这一命令行终端。通过HBase Shell工具,可以交互式地进行数据管理,包括插入数据、删除数据等。虽然也有一些第三方图形界面客户端支持HBase,如DBeaver、BigInsights、HbaseGUI,但系统部的HBase只支持HBase Shell。
安装HBase Shell需要先挑选一台用于安装的虚拟机,为该虚拟机安装Java环境。之后在系统部奇麟大数据的客户端管理页面选择“添加客户端账号”,申请为该虚拟机添加项目账号。申请通过后勾选机器,单击“部署Hadoop环境”在该机器上安装HBase Shell。
安装成功后,到虚拟机上使用sudo -iu命令先切换到项目账号。然后切换到目录cd $HBASE_HOME/bin,运行hbase shell,即可进入HBase Shell程序。
这里列出几个常用的HBase Shell命令:
名称 | 命令表达式 |
---|---|
查看存在哪些表 | list |
添加数据 | put '表名称', '行键', '列族 : 列名', '值' |
查看一行数据 | get '表名称', '行键' |
查看指定列族的一行数据 | get '表名称', '行键', '列族' |
查看指定列族及列名的数据 | get '表名称', '行键', '列族 : 列名', |
查看表中的数据总量 | count '表名' |
删除一个单元格的数据 | delete '表名' ,'行键' , '列族 : 列名' |
删除一行所有数据 | delete '表名' ,'行键' |
查看表的所有数据 | scan '表名'。注意,一般不应直接使用scan扫描整个表的海量数据。 |
查看一列数据 | scan '表名' , '列族 : 列名' |
查看帮助信息 | help |
5、MongoDB数据迁移HBase
使用kettle等工具可以把MongoDB数据库迁移到HBase。也可以使用MapReduce处理,速度远快于Java API和Thrift。
参考文献:
胡争,范欣欣.HBase原理与实践M.北京:机械工业出版社,2019
O’Neil, P., Cheng, E., Gawlick, D., & O’Neil, E. (1996). The log-structured merge-tree (LSM-tree). Acta Informatica, 33(4), 351-385.