1 概述
HBase是一个分布式的、面向列的数据库,它和通常关系型数据库的最大区别是:HBase很适合于存储非结构化的数据,还有就是它基于列的而不是基于行的模式。web
既然HBase是采用KeyValue的列存储,那Rowkey就是KeyValue的Key了,表示惟一一行。Rowkey也是一段二进制码流,最大长度为64KB,内容能够由使用的用户自定义。数据加载时,通常也是根据Rowkey的二进制序由小到大进行的。数据库
HBase是根据Rowkey来进行检索的,系统经过找到某个Rowkey (或者某个 Rowkey 范围)所在的Region,而后将查询数据的请求路由到该Region获取数据。HBase的检索支持3种方式:apache
(1) 经过单个Rowkey访问,即按照某个Rowkey键值进行get操做,这样获取惟一一条记录;缓存
(2) 经过Rowkey的range进行scan,即经过设置startRowKey和endRowKey,在这个范围内进行扫描。这样能够按指定的条件获取一批记录;负载均衡
(3) 全表扫描,即直接扫描整张表中全部行记录。分布式
HBASE按单个Rowkey检索的效率是很高的,耗时在1毫秒如下,每秒钟可获取1000~2000条记录,不过非key列的查询很慢。oop
2 HBase的RowKey设计
Rowkey是一个二进制码流,Rowkey的长度被不少开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。性能
缘由以下:spa
(1)数据的持久化文件HFile中是按照KeyValue存储的,若是Rowkey过长好比100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;操作系统
(2)MemStore将缓存部分数据到内存,若是Rowkey字段过长内存的有效利用率会下降,系统将没法缓存更多的数据,这会下降检索效率。所以Rowkey的字节长度越短越好。
(3)目前操做系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操做系统的最佳特性。
若是Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位做为散列字段,由程序循环生成,低位放时间字段,这样将提升数据均衡分布在每一个Regionserver实现负载均衡的概率。若是没有散列字段,首字段直接是时间信息将产生全部新数据都在一个 RegionServer上堆积的热点现象,这样在作数据检索的时候负载将会集中在个别RegionServer,下降查询效率。
必须在设计上保证其惟一性。
基于Rowkey的上述3个原则,应对不一样应用场景有不一样的Rowkey设计建议。
事务数据是带时间属性的,建议将时间信息存入到Rowkey中,这有助于提示查询检索速度。对于事务数据建议缺省就按天为数据建表,这样设计的好处是多方面的。按天分表后,时间信息就能够去掉日期部分只保留小时分钟毫秒,这样4个字节便可搞定。加上散列字段2个字节一共6个字节便可组成惟一 Rowkey。以下图所示:
事务数据Rowkey设计 | ||||||
第0字节 | 第1字节 | 第2字节 | 第3字节 | 第4字节 | 第5字节 | … |
散列字段 | 时间字段(毫秒) | 扩展字段 | ||||
0~65535(0x0000~0xFFFF) | 0~86399999(0x00000000~0x05265BFF) |
这样的设计从操做系统内存管理层面没法节省开销,由于64位操做系统是必须8字节对齐。可是对于持久化存储中Rowkey部分能够节省25%的开销。也许有人要问为何不将时间字段以主机字节序保存,这样它也能够做为散列字段了。这是由于时间范围内的数据仍是尽可能保证连续,相同时间范围内的数据查找的几率很大,对查询检索有好的效果,所以使用独立的散列字段效果更好,对于某些应用,咱们能够考虑利用散列字段所有或者部分来存储某些数据的字段信息,只要保证相同散列值在同一时间(毫秒)惟一。
统计数据也是带时间属性的,统计数据最小单位只会到分钟(到秒预统计就没意义了)。同时对于统计数据咱们也缺省采用按天数据分表,这样设计的好处无需多说。按天分表后,时间信息只须要保留小时分钟,那么0~1400只需占用两个字节便可保存时间信息。因为统计数据某些维度数量很是庞大,所以须要4个字节做为序列字段,所以将散列字段同时做为序列字段使用也是6个字节组成惟一Rowkey。以下图所示:
统计数据Rowkey设计 | ||||||
第0字节 | 第1字节 | 第2字节 | 第3字节 | 第4字节 | 第5字节 | … |
散列字段(序列字段) | 时间字段(分钟) | 扩展字段 | ||||
0x00000000~0xFFFFFFFF) | 0~1439(0x0000~0x059F) |
一样这样的设计从操做系统内存管理层面没法节省开销,由于64位操做系统是必须8字节对齐。可是对于持久化存储中Rowkey部分能够节省25%的开销。预统计数据可能涉及到屡次反复的重计算要求,需确保做废的数据能有效删除,同时不能影响散列的均衡效果,所以要特殊处理。
通用数据采用自增序列做为惟一主键,用户能够选择按天建分表也能够选择单表模式。这种模式须要确保同时多个入库加载模块运行时散列字段(序列字段)的惟一性。能够考虑给不一样的加载模块赋予惟一因子区别。设计结构以下图所示。
通用数据Rowkey设计 | ||||
第0字节 | 第1字节 | 第2字节 | 第3字节 | … |
散列字段(序列字段) | 扩展字段(控制在12字节内) | |||
0x00000000~0xFFFFFFFF) | 可由多个用户字段组成 |
HBase按指定的条件获取一批记录时,使用的就是scan方法。 scan方法有如下特色:
(1)scan能够经过setCaching与setBatch方法提升速度(以空间换时间);
(2)scan能够经过setStartRow与setEndRow来限定范围。范围越小,性能越高。
经过巧妙的RowKey设计使咱们批量获取记录集合中的元素挨在一块儿(应该在同一个Region下),能够在遍历结果时得到很好的性能。
(3)scan能够经过setFilter方法添加过滤器,这也是分页、多条件查询的基础。
在知足长度、三列、惟一原则后,咱们须要考虑如何经过巧妙设计RowKey以利用scan方法的范围功能,使得获取一批记录的查询速度能提升。下例就描述如何将多个列组合成一个RowKey,使用scan的range来达到较快查询速度。
例子:
咱们在表中存储的是文件信息,每一个文件有5个属性:文件id(long,全局惟一)、建立时间(long)、文件名(String)、分类名(String)、全部者(User)。
咱们能够输入的查询条件:文件建立时间区间(好比从20120901到20120914期间建立的文件),文件名(“中国好声音”),分类(“综艺”),全部者(“浙江卫视”)。
假设当前咱们一共有以下文件:
ID | CreateTime | Name | Category | UserID |
1 | 20120902 | 中国好声音第1期 | 综艺 | 1 |
2 | 20120904 | 中国好声音第2期 | 综艺 | 1 |
3 | 20120906 | 中国好声音外卡赛 | 综艺 | 1 |
4 | 20120908 | 中国好声音第3期 | 综艺 | 1 |
5 | 20120910 | 中国好声音第4期 | 综艺 | 1 |
6 | 20120912 | 中国好声音选手采访 | 综艺花絮 | 2 |
7 | 20120914 | 中国好声音第5期 | 综艺 | 1 |
8 | 20120916 | 中国好声音录制花絮 | 综艺花絮 | 2 |
9 | 20120918 | 张玮独家专访 | 花絮 | 3 |
10 | 20120920 | 加多宝凉茶广告 | 综艺广告 | 4 |
这里UserID应该对应另外一张User表,暂不列出。咱们只需知道UserID的含义:
1表明 浙江卫视; 2表明 好声音剧组; 3表明 XX微博; 4表明赞助商。调用查询接口的时候将上述5个条件同时输入find(20120901,20121001,”中国好声音”,”综艺”,”浙江卫视”)。此时咱们应该获得记录应该有第一、二、三、四、五、7条。第6条因为不属于“浙江卫视”应该不被选中。咱们在设计RowKey时能够这样作:采用 UserID + CreateTime + FileID组成RowKey,这样既能知足多条件查询,又能有很快的查询速度。
须要注意如下几点:
(1)每条记录的RowKey,每一个字段都须要填充到相同长度。假如预期咱们最多有10万量级的用户,则userID应该统一填充至6位,如000001,000002…
(2)结尾添加全局惟一的FileID的用意也是使每一个文件对应的记录全局惟一。避免当UserID与CreateTime相同时的两个不一样文件记录相互覆盖。
按照这种RowKey存储上述文件记录,在HBase表中是下面的结构:
rowKey(userID 6 + time 8 + fileID 6) name category ….
00000120120902000001
00000120120904000002
00000120120906000003
00000120120908000004
00000120120910000005
00000120120914000007
00000220120912000006
00000220120916000008
00000320120918000009
00000420120920000010
怎样用这张表?
在创建一个scan对象后,咱们setStartRow(00000120120901),setEndRow(00000120120914)。
这样,scan时只扫描userID=1的数据,且时间范围限定在这个指定的时间段内,知足了按用户以及按时间范围对结果的筛选。而且因为记录集中存储,性能很好。
而后使用 SingleColumnValueFilter(org.apache.hadoop.hbase.filter.SingleColumnValueFilter),共4个,分别约束name的上下限,与category的上下限。知足按同时按文件名以及分类名的前缀匹配。
(注意:使用SingleColumnValueFilter会影响查询性能,在真正处理海量数据时会消耗很大的资源,且须要较长的时间)
若是须要分页还能够再加一个PageFilter限制返回记录的个数。
以上,咱们完成了高性能的支持多条件查询的HBase表结构设计。