Hbase多列范围查找(效率)
Hbase索引表的结构
Hbase Rowkey 设计
Hbase Filter
Hbase二级索引
Hbase索引表的结构
在HBase中,表格的Rowkey按照字典排序,Region按照RowKey设置split point进行shard,通过这种方式实现的全局、分布式索引,成为了其成功的最大的砝码
每一个索引建立一个表,然后依靠表的row key来实现范围检索。row key在HBase中是以B+ tree结构化有序存储的,所以scan起来会比较效率。
单表以row key存储索引,column value存储id值或其他数据 ,这就是Hbase索引表的结构。
Hbase QualifierFilter用于过滤qualifier,也就是一个列族里面data:xxx,冒号后面的字符串
Hbase Rowkey 设计
大数据最好从rowkey入手,ColumnValueFilter的数度是很慢的,hbase查询速度还是要依靠rowkey,所以根据业务逻辑把rowkey设计好,之后所有的查询都通过rowkey,是会非常快。 批量查询最好是用 scan的startkey endkey来做查询条件
rowkey是hbase中很重要的一个设计,如果你把它当成普通字段那你的设计就有点失败了。它的设计可以说是一门艺术。你的查询如果不能把rowkey加入进来,那你的设计基本是失败的。加上rowkey,hbase可以快速地定位到具体的region去取你要的数据,否则就会满上遍野的找数据。
设计原则:
1. 长度越短越好
Rowkey是一个二进制码流,Rowkey的长度被很多开发者建议说设计在10~100个字节,不过建议是越短越好,不要超过16个字节。
原因如下:
(1)数据的持久化文件HFile中是按照KeyValue存储的,如果Rowkey过长比如100个字节,1000万列数据光Rowkey就要占用100*1000万=10亿个字节,将近1G数据,这会极大影响HFile的存储效率;
(2)MemStore将缓存部分数据到内存,如果Rowkey字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率。因此Rowkey的字节长度越短越好。
(3)目前操作系统是都是64位系统,内存8字节对齐。控制在16个字节,8字节的整数倍利用操作系统的最佳特性。
2. 散列原则:如果Rowkey是按时间戳的方式递增,不要将时间放在二进制码的前面,建议将Rowkey的高位作为散列字段,由程序循环生成,低位放时间字段,这样将提高数据均衡分布在每个Regionserver实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息将产生所有新数据都在一个 RegionServer上堆积的热点现象,这样在做数据检索的时候负载将会集中在个别RegionServer,降低查询效率。
3. 唯一性
HBase按指定的条件获取一批记录时,使用的就是scan方法。 scan方法有以下特点:
(1)scan可以通过setCaching与setBatch方法提高速度(以空间换时间);
(2)scan可以通过setStartRow与setEndRow来限定范围。范围越小,性能越高。
通过巧妙的RowKey设计使我们批量获取记录集合中的元素挨在一起(应该在同一个Region下),可以在遍历结果时获得很好的性能。
(3)scan可以通过setFilter方法添加过滤器,这也是分页、多条件查询的基础。
设计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
Hbase Filter
应用实例
- //时间范围的查找, 比如是2012-12-12到2013-01-23日之间的数据
- FilterList filter = new FilterList();
- if (timeFrom != null) {
- String sDate = String.valueOf(timeFrom.getTime());
- SingleColumnValueFilter scvf = new SingleColumnValueFilter(Bytes.toBytes("CF"), Bytes.toBytes("Date"), CompareOp.GREATER_OR_EQUAL,
- Bytes.toBytes(String.valueOf(sDate)));
- filter.addFilter(scvf);
- }
- if (timeTo != null) {
- String sDate = String.valueOf(timeTo.getTime());
- SingleColumnValueFilter scvf = new SingleColumnValueFilter(Bytes.toBytes("CF"), Bytes.toBytes("Date"), CompareOp.LESS_OR_EQUAL,
- Bytes.toBytes(String.valueOf(sDate)));
- filter.addFilter(scvf);
- }
HBase(0.96以上版本)过滤器Filter详解及实例代码
Hbase二级索引
HBase在0.92之后引入了coprocessors,提供了一系列的钩子,让我们能够轻易实现访问控制和二级索引的特性。下面简单介绍下两种coprocessors,第一种是Observers,它实际类似于触发器,第二种是Endpoint,它类似与存储过程。由于这里只用到了Observers,所以只介绍Observers,想要更详细的介绍请查阅(https://blogs.apache.org/hbase/entry/coprocessor_introduction)。observers分为三种:
RegionObserver:提供数据操作事件钩子;
WALObserver:提供WAL(write ahead log)相关操作事件钩子;
MasterObserver:提供DDL操作事件钩子。
在二级索引的实现技术上一般有几个方案:
1. 表索引
使用单独的hbase表存储索引数据,业务表的索引列值做为索引表的rowkey,业务表的rowkey做为索引表的qualifier或value。
问题:对数据更新性能影响较大;无法保证一致性;Client查询需要2次RPC(先索引表再数据表)。
2. 列索引
与业务表使用相同表,使用单独列族存储索引,用户数据列值做为索引列族的Qualifier,用户数据Qualifier做为索引列族的列值。适用于单行有上百万Qualifier的数据模型,如网盘应用中网盘ID做为rowkey,网盘的目录元数据都存储在一个hbase row内。(facebook消息模型也是此方案)
可保证事务性
为了实现像SQL一样检索数据,select * from table where col=val。针对HBase Secondary Indexing的方案,成为HBase新版本(0.96)呼声最高的一项Feature。
粗略分析了当前的技术,大概的方案可以总结为这样两类:
1、使用HBase的coprocessor。CoProcessor相当于HBase的Observer+hook,目前支持MasterObserver、RegionObserver和WALObserver,基本上对于HBase Table的管理、数据的Put、Delete、Get等操作都可以找到对应的pre***和post***。这样如果需要对于某一项Column建立Secondary Indexing,就可以在Put、Delete的时候,将其信息更新到另外一张索引表中。如图二所示,对于Indexing里面的value值是否存储的问题,可以根据需要进行控制,如果value的空间开销不大,逆向的检索又比较频繁,可以直接存储在Indexing Table中,反之则避免这种情况。
图2 使用HBase Coprocessor实现Secondary Indexing
2、由客户端发起对于主表和索引表的Put、Delete操作的双重操作。源自:http://hadoop-hbase.blogspot.com/2012/10/musings-on-secondary-indexes.html 【墙外】
它具体的做法总结起来有:
- 设置主表的TTL(Time To Live)比索引表小一点,让其略早一点消亡。
- 不要在IndexingTable存储Value值,即删除如图2所示的val列。
- Put操作时,对于操作的主表的所有列,使用同一的Local TimeStamp的值,更新到Indexing Table,然后使用该TimeStamp插入主表数据。
- Delete操作时,首先操作主表的数据,然后再去更新Indexing Table的数据。
虽然在这种方案里无法保证原子性和一致性,但是通过TimeStamp的设置,No Locks和 No Server-side codes,使其在二级索引上有着较大的优势。至于中间出错的环节,我们看看是否可以容忍:
1)Put索引表成功,Put主表失败。由于Indexing Table不存储val值,仍需要跳转到Main Table,所以这样的错误相当于拿一个Stale index去访问对应Rowkey吧了,对结果正确性没有影响。
2)Delete主表成功,Delete索引表失败。都是索引表的内容>=主表的内容而已,而实际返回值需要通过主表进行。
应用场景:
1、主表服务在线业务,它的性能需要保证。使用coprocessor和客户端的封装也好,都会影响其性能,所以在正常情况下,直接操作都不太合适。如果想使用方案二,我倒是感觉,可以调整Indexing Table的操作方式,去除保证其安全性的内容,比如可以关闭写HLOG,这样会进一步减低其操作的延迟。
2、离线更新索引表。在真正需要二级索引的场景内,其时效性要求往往不高。可以将索引实时更新到Redis等KV系统中,定时从KV更新索引到Hbase的Indexing Table中。PS:Redis里面有DB设置的概念,可以按照时间段进行隔离,这样某段时间内的数据会更新到Redis上,保证Redis导入MapReduce之后仍然可以进行update操作。
coprocessor代码实现 ??
- We have been working on implementing secondary index in HBase and open sourced on hbase 0.94.8 version.
- The project is available on github.
- https://github.com/Huawei-Hadoop/hindex
- This Jira is to support secondary index on trunk(0.98).
- Following features will be supported.
- multiple indexes on table,
- multi column index,
- index based on part of a column value,
- equals and range condition scans using index, and
- bulk loading data to indexed table (Indexing done with bulk load)
- Most of the kernel changes needed for secondary index is available in trunk. Very minimal changes needed for it.
首先在HBase-0.19.3中必须设置参数,使得Hbase可以使用索引,修改$HBASE_INSTALL_DIR/conf/hbase-site.xml:
- <property>
- <name>hbase.regionserver.class</name>
- <value>org.apache.hadoop.hbase.ipc.IndexedRegionInterface</value>
- </property>
- <property>
- <name>hbase.regionserver.impl</name>
- <value>
- org.apache.hadoop.hbase.regionserver.tableindexed.IndexedRegionServer
- </value>
- </property>
(1)创建表时,增加二级索引:
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- HTableDescriptor desc = new HTableDescriptor("test_table");
- desc.addFamily(new HColumnDescriptor("columnfamily1:"));
- desc.addFamily(new HColumnDescriptor("columnfamily2:"));
- desc.addIndex(new IndexSpecification("column1",
- Bytes.toBytes("columnfamily1:column1")));
- desc.addIndex(new IndexSpecification("column2",
- Bytes.toBytes("columnfamily1:column2")));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- admin.createTable(desc);
(2)在已经存在的表中,增加索引
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- admin.addIndex(Bytes.toBytes("test_table"), new IndexSpecification("column2",
- Bytes.toBytes("columnfamily1:column2")));
(3)删除存在的索引
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- admin.removeIndex(Bytes.toBytes("test_table"), "column2");
(4)通过索引scan所有数据
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTable table = new IndexedTable(conf, Bytes.toBytes("test_table"));
- // You need to specify which columns to get
- Scanner scanner = table.getIndexedScanner("column1",
- HConstants.EMPTY_START_ROW, null, null, new byte[][] {
- Bytes.toBytes("columnfamily1:column1"),
- Bytes.toBytes("columnfamily1:column2") });
- for (RowResult rowResult : scanner) {
- String value1 = new String(
- rowResult.get(Bytes.toBytes("columnfamily1:column1")).getValue());
- String value2 = new String(
- rowResult.get(Bytes.toBytes("columnfamily1:column2")).getValue());
- System.out.println(value1 + ", " + value2);
- }
- table.close();
(5)通过索引scan一部分子集,通过ColumnValueFilter过滤。
使用SingleColumnValueFilter会影响查询性能,在真正处理海量数据时会消耗很大的资源,且需要较长的时间
- ColumnValueFilter filter =
- new ColumnValueFilter(Bytes.toBytes("columnfamily1:column1"),
- CompareOp.LESS, Bytes.toBytes("value1-10"));
- scanner = table.getIndexedScanner("column1", HConstants.EMPTY_START_ROW,
- null, filter, new byte[][] { Bytes.toBytes("columnfamily1:column1"),
- Bytes.toBytes("columnfamily1:column2"));
- for (RowResult rowResult : scanner) {
- String value1 = new String(
- rowResult.get(Bytes.toBytes("columnfamily1:column1")).getValue());
- String value2 = new String(
- rowResult.get(Bytes.toBytes("columnfamily1:column2")).getValue());
- System.out.println(value1 + ", " + value2);
- }
一般不建议用Filter,scan.setFilters(),通过filter设置的条件查不到数据时,响应速度非常慢,大概在十几秒,有时会超时,
但可以查到数据时,响应速度只有几百ms,差距非常大
- Scan scan = new Scan();
- FilterList filters = new FilterList();
- for (String[] param : params)
- {
- //param[0]为列名,param[1]为相应的值
- filters.addFilter(new SingleColumnValueFilter("INFO".getBytes(), param[0].getBytes(), CompareOp.EQUAL, param[1].getBytes()));
- }
- scan.setFilter(filters);
(6)一个完全的例子
- import java.io.IOException;
- import java.util.Date;
- import org.apache.hadoop.fs.Path;
- import org.apache.hadoop.hbase.HBaseConfiguration;
- import org.apache.hadoop.hbase.HColumnDescriptor;
- import org.apache.hadoop.hbase.HConstants;
- import org.apache.hadoop.hbase.HTableDescriptor;
- import org.apache.hadoop.hbase.client.Scanner;
- import org.apache.hadoop.hbase.client.tableindexed.IndexSpecification;
- import org.apache.hadoop.hbase.client.tableindexed.IndexedTable;
- import org.apache.hadoop.hbase.client.tableindexed.IndexedTableAdmin;
- import org.apache.hadoop.hbase.filter.ColumnValueFilter;
- import org.apache.hadoop.hbase.filter.ColumnValueFilter.CompareOp;
- import org.apache.hadoop.hbase.io.BatchUpdate;
- import org.apache.hadoop.hbase.io.RowResult;
- import org.apache.hadoop.hbase.util.Bytes;
- public class SecondaryIndexTest {
- public void writeToTable() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTable table = new IndexedTable(conf, Bytes.toBytes("test_table"));
- String row = "test_row";
- BatchUpdate update = null;
- for (int i = 0; i < 100; i++) {
- update = new BatchUpdate(row + i);
- update.put("columnfamily1:column1", Bytes.toBytes("value1-" + i));
- update.put("columnfamily1:column2", Bytes.toBytes("value2-" + i));
- table.commit(update);
- }
- table.close();
- }
- public void readAllRowsFromSecondaryIndex() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTable table = new IndexedTable(conf, Bytes.toBytes("test_table"));
- Scanner scanner = table.getIndexedScanner("column1",
- HConstants.EMPTY_START_ROW, null, null, new byte[][] {
- Bytes.toBytes("columnfamily1:column1"),
- Bytes.toBytes("columnfamily1:column2") });
- for (RowResult rowResult : scanner) {
- System.out.println(Bytes.toString(
- rowResult.get(Bytes.toBytes("columnfamily1:column1")).getValue())
- + ", " + Bytes.toString(rowResult.get(
- Bytes.toBytes("columnfamily1:column2")).getValue()
- ));
- }
- table.close();
- }
- public void readFilteredRowsFromSecondaryIndex() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTable table = new IndexedTable(conf, Bytes.toBytes("test_table"));
- ColumnValueFilter filter =
- new ColumnValueFilter(Bytes.toBytes("columnfamily1:column1"),
- CompareOp.LESS, Bytes.toBytes("value1-40"));
- Scanner scanner = table.getIndexedScanner("column1",
- HConstants.EMPTY_START_ROW, null, filter,
- new byte[][] { Bytes.toBytes("columnfamily1:column1"),
- Bytes.toBytes("columnfamily1:column2")
- });
- for (RowResult rowResult : scanner) {
- System.out.println(Bytes.toString(
- rowResult.get(Bytes.toBytes("columnfamily1:column1")).getValue())
- + ", " + Bytes.toString(rowResult.get(
- Bytes.toBytes("columnfamily1:column2")).getValue()
- ));
- }
- table.close();
- }
- public void createTableWithSecondaryIndexes() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- HTableDescriptor desc = new HTableDescriptor("test_table");
- desc.addFamily(new HColumnDescriptor("columnfamily1:column1"));
- desc.addFamily(new HColumnDescriptor("columnfamily1:column2"));
- desc.addIndex(new IndexSpecification("column1",
- Bytes.toBytes("columnfamily1:column1")));
- desc.addIndex(new IndexSpecification("column2",
- Bytes.toBytes("columnfamily1:column2")));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- if (admin.tableExists(Bytes.toBytes("test_table"))) {
- if (admin.isTableEnabled("test_table")) {
- admin.disableTable(Bytes.toBytes("test_table"));
- }
- admin.deleteTable(Bytes.toBytes("test_table"));
- }
- if (admin.tableExists(Bytes.toBytes("test_table-column1"))) {
- if (admin.isTableEnabled("test_table-column1")) {
- admin.disableTable(Bytes.toBytes("test_table-column1"));
- }
- admin.deleteTable(Bytes.toBytes("test_table-column1"));
- }
- admin.createTable(desc);
- }
- public void addSecondaryIndexToExistingTable() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- admin.addIndex(Bytes.toBytes("test_table"),
- new IndexSpecification("column2",
- Bytes.toBytes("columnfamily1:column2")));
- }
- public void removeSecondaryIndexToExistingTable() throws IOException {
- HBaseConfiguration conf = new HBaseConfiguration();
- conf.addResource(new Path("/opt/hbase-0.19.3/conf/hbase-site.xml"));
- IndexedTableAdmin admin = null;
- admin = new IndexedTableAdmin(conf);
- admin.removeIndex(Bytes.toBytes("test_table"), "column2");
- }
- public static void main(String[] args) throws IOException {
- SecondaryIndexTest test = new SecondaryIndexTest();
- test.createTableWithSecondaryIndexes();
- test.writeToTable();
- test.addSecondaryIndexToExistingTable();
- test.removeSecondaryIndexToExistingTable();
- test.readAllRowsFromSecondaryIndex();
- test.readFilteredRowsFromSecondaryIndex();
- System.out.println("Done!");
- }
- }
Hbase多列范围查找(效率)的更多相关文章
- 何在mysql查找效率慢的SQL语句?
如何在mysql查找效率慢的SQL语句呢?这可能是困然很多人的一个问题,MySQL通过慢查询日志定位那些执行效率较低的SQL 语句,用--log-slow-queries[=file_name]选项启 ...
- HBase与列存储
传统的行存储和(HBase)列存储的区别 1.为什么要按列存储 列式存储(Columnar or column-based)是相对于传统关系型数据库的行式存储(Row-basedstorage)来说的 ...
- List和Dictionary泛型类查找效率浅析
List和Dictionary泛型类查找效率存在巨大差异,前段时间亲历了一次.事情的背景是开发一个匹配程序,将书籍(BookID)推荐给网友(UserID),生成今日推荐数据时,有条规则是同一书籍七日 ...
- python 字典有序无序及查找效率,hash表
刚学python的时候认为字典是无序,通过多次插入,如di = {}, 多次di['testkey']='testvalue' 这样测试来证明无序的.后来接触到了字典查找效率这个东西,查了一下,原来字 ...
- HBase 是列式存储数据库吗
在介绍 HBase 是不是列式存储数据库之前,我们先来了解一下什么是行式数据库和列式数据库. 行式数据库和列式数据库 在维基百科里面,对行式数据库和列式数据库的定义为:列式数据库是以列相关存储架构进行 ...
- MySQL数据库中的字段类型varchar和char的主要区别是什么?哪种字段查找效率要高?
1,varchar与char的区别?(1)区别一,定长和变长,char表示定长,长度固定:varchar表示变长,长度可变.当插入字符串超出长度时,视情况来处理,如果是严格模式,则会拒绝插入并提示错误 ...
- python中in在list和dict中查找效率比较
转载自:http://blog.csdn.net/wzgbm/article/details/54691615 首先给一个简单的例子,测测list和dict查找的时间: ,-,-,-,-,,,,,,] ...
- MySQL select * 和把所有的字段都列出来,哪个效率更高?
MySQL select * 和把所有的字段都列出来,哪个效率更高 答案是:如何,都不推荐使用 SELECT * FROM (1)SELECT *,需要数据库先 Query Table Metadat ...
- 五分钟轻松了解Hbase面向列的存储
说明:从严格的列式存储的定义来看,Hbase并不属于列式存储,有人称它为面向列的存储,请各位看官注意这一点. 行式存储 传统的数据库是关系型的,且是按行来存储的.如下图: 其中只有张三把一行数据填满了 ...
随机推荐
- 解决IE中placeholder的兼容问题
定义和用法 placeholder 属性提供可描述输入字段预期值的提示信息(hint). 该提示会在输入字段为空时显示,并会在字段获得焦点时消失. 注释:placeholder 属性适用于以下的 &l ...
- 手机文件夹的emulated什么意思
词典翻译是仿真,就是自带的存储卡, 手机的储存方式有两种,一种是手机内存 ,一种是SD卡内存.
- js中的监听事件总结
javascript事件与功能说明大全:http://tools.jb51.net/table/javascript_event 1.滚动条监听事件 例1:监听滚动条距离页面顶端距离 <scri ...
- Cacti在selinux开启的情况下使用
# chcon -R -t httpd_sys_content_t /var/www/html/cacti
- 【转】sed命令n,N,d,D,p,P,h,H,g,G,x解析
1. sed执行模板=sed '模式{命令1;命令2}' 即逐行读入模式空间,执行命令,最后输出打印出来 2. 为方便下面,先说下p和P,p打印当前模式空间内容,追加到默认输出之后,P打印当前模式空间 ...
- 百度网盘不限速下载软件 Pan Download
百度网盘不限速下载软件 Pan Download Pan Download下载软件是一款电脑端的快速下载器软件,您可以通过Pan Download直接下载百度网盘中的资源,此款软件下载速度快,下载压缩 ...
- Redis进阶实践之九 独立封装的RedisClient客户端工具类
一.引言 今天开始有关Redis学习的第九篇文章了,以后肯定会大量系统使用Redis作为缓存介质,为了更好的更好的Redis,自己写了两个工具类,但是这两个工具类,没有提供一致的接口,是为了使用的独立 ...
- wpf阻止键盘快捷键alt+space,alt+F4
/// <summary> /// 阻止 alt+f4和alt+space 按键 /// </summary> /// <par ...
- 4.C++中的函数重载,C++调用C代码,new/delete关键字,namespace(命名空间)
本章主要内容: 1)函数重载 2)C++调用C代码 3)new/delete关键字实现动态内存分配 4)namespace命名空间 大家都知道,在生活中,动词和不同的名词搭配一起,意义都会大有不同,比 ...
- Ansible进阶--playbook的使用
一.什么是playbooksplaybooks是ansible的脚本.如同shell脚本一样,它是控制远程主机的一系列命令的集合,通过YAML语言编写.执行一些简单的任务,我们可以使用ad-hoc命令 ...