HBase HFile与Prefix Compression内部实现全解–KeyValue格式
1.引子
HFile(HBaseFile)是HBase使用的一种文件存储格式的抽象,
目前存在两种版本的HFile:HFileV1和HFileV2
HBase0.92之前的版本仅支持HFileV1,
HBase0.92/0.94同时支持HFileV1和HFileV2。
以下分别是HFileV1/V2的结构图:
HFileV1
HFileV2
图中的数据块(Datablock)正是实际存放应用数据的地方,
每个数据块又由一系列的KeyValue组成,并且这些KeyValue之间是按Key升序排列的,
本文将说明KeyValue到底是什么以及当KeyValue越来越多时出现大量类似的数据有哪些算法能减少重复?
首先来看一个例子:
假设需要将用户的基本信息以及正在参与的开源项目的有关信息存入HBase:
Java代码用户基本信息参与的开源项目
-----------------
用户Id职业性别tomcathbase
-----------------
zhh3009码农男提patch打酱油提patch打酱油
用户Id职业性别tomcatant
-----------------
jdd1999码神男创始人创始人
-------------------
表1.1
用户基本信息 参与的开源项目--------------------- ----------------------------用户Id 职业 性别 tomcat hbase--------------------- ----------------------------zhh3009 码农 男 提patch打酱油 提patch打酱油用户Id 职业 性别 tomcat ant--------------------- ----------------------------jdd1999 码神 男 创始人 创始人---------------------------------------------------------表1.1
从这个例子来看,用户的基本信息比较好确定,但是参与的开源项目不确定且在开源项目中扮演的角色也不确定,
所以用关系数据库不太好建表,因为不知道具体有多少列,也无法把相关的列归成一个组。
1.1列族
HBase是一种基于列的数据库,相关的列可以归到一个列族(ColumnFamily),
每个列族中具体有哪些列不必事先知道,可以在需要的时候添加,比如在用户基本信息中为zhh3009加入email这样的列,
上例中"用户基本信息"和"参与的开源项目"可以作为两个列族,
不同的列族在HBase内部通常对应一个目录,这样不同的列值只会放到它所属的列族目录下。
1.2rowKey
我们希望通过查询某个列就能把同一个列族或多个列族中的信息取出来,用户Id就是这样的列,
比如当我们要查询zhh3009的邮箱和参与的开源项目时,根据用户Id来查就不会查到jdd1999的信息,
在HBase中称这样的列为rowKey。
HBase是如何存放上例中的信息呢?
将用户Id这一列抽出来作为rowKey,把上面的信息按如下格式扁平化:
Java代码列值>------------------ 码农> 男> 提patch打酱油> 提patch打酱油> 码神> 男> 创始人> 创始人>------------------ 表1.2
<rowKey, 列族名称, 列名 => 列值>-----------------------------------------------------<zhh3009, 用户基本信息, 职业 => 码农><zhh3009, 用户基本信息, 性别 => 男><zhh3009, 参与的开源项目, tomcat => 提patch打酱油><zhh3009, 参与的开源项目, hbase => 提patch打酱油><jdd1999, 用户基本信息, 职业 => 码神><jdd1999, 用户基本信息, 性别 => 男><jdd1999, 参与的开源项目, tomcat => 创始人><jdd1999, 参与的开源项目, ant => 创始人>-----------------------------------------------------表1.2
表1.2中的每一行在HBase中对应一个KeyValue,
"=>"左边的是KeyValue中的"Key","=>"右边对应KeyValue中的"Value"。
当然这只是KeyValue的一个简化格式,内部格式并非那么简单,我们接下来看看真实的KeyValue是怎样的?
2.KeyValue内部格式
KeyValue内部格式可以分成三部份:头、Key、Value,如表2.1所示
Java代码名称 字节数说明
-----------------------
keyLength 4表示Key所占的总字节数
valueLength4表示Value所占的总字节数
rowKeyLength2表示rowKey所占的字节数
rowKeyrowKeyLengthrowKey
columnFamilyLength1表示列族名称所占的字节数
columnFamilycolumnFamilyLength列族名称
columnNamecolumnNameLength列名
timestamp8时间戳
type1Key类型,比如是新增(Put),还是删除(Delete)
valuevalueLength列值
-----------------------
表2.1
名称 字节数 说明--------------------------------------------------------------------keyLength 4 表示Key所占的总字节数valueLength 4 表示Value所占的总字节数rowKeyLength 2 表示rowKey所占的字节数rowKey rowKeyLength rowKeycolumnFamilyLength 1 表示列族名称所占的字节数columnFamily columnFamilyLength 列族名称columnName columnNameLength 列名timestamp 8 时间戳type 1 Key类型,比如是新增(Put),还是删除(Delete)value valueLength 列值--------------------------------------------------------------------表2.1
keyLength和valueLength组成头部,
rowKeyLength到type这7项组成Key,最后一项value代表第三部份:Value,
上面有个地方值得注意,在columnFamily前面有columnFamilyLength,
但是在columnName之前并没有columnNameLength这一项,为了节省空间,这不是必需的,
当在解析KeyValue时,通过keyLength-8(timestamp)-1(type)就可以确定columnName在此KeyValue中的结束位置。
把表1.2中的前两行按表2.1中的格式生成两个KeyValue:
KeyValueA代表:
KeyValueB代表:
名称 字节数 KeyValue A KeyValue B--------------------------keyLength 4 35 35valueLength 4 4 2rowKeyLength 2 7 7rowKey rowKeyLength zhh3009 zhh3009columnFamilyLength 1 12 12columnFamily columnFamilyLength 用户基本信息 用户基本信息columnName columnNameLength 职业 性别timestamp 8 1329663787364 1329663787364type 1 4(Put) 4(Put)value valueLength 码农 男-------------------------- 表2.2
名称 字节数 KeyValue A KeyValue B----------------------------------------------------------------------------keyLength 4 35 35valueLength 4 4 2rowKeyLength377
rowKeyrowKeyLengthzhh3009zhh3009
columnFamilyLength21212
columnFamilycolumnFamilyLength用户%E