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多列范围查找(效率)的更多相关文章

  1. 何在mysql查找效率慢的SQL语句?

    如何在mysql查找效率慢的SQL语句呢?这可能是困然很多人的一个问题,MySQL通过慢查询日志定位那些执行效率较低的SQL 语句,用--log-slow-queries[=file_name]选项启 ...

  2. HBase与列存储

    传统的行存储和(HBase)列存储的区别 1.为什么要按列存储 列式存储(Columnar or column-based)是相对于传统关系型数据库的行式存储(Row-basedstorage)来说的 ...

  3. List和Dictionary泛型类查找效率浅析

    List和Dictionary泛型类查找效率存在巨大差异,前段时间亲历了一次.事情的背景是开发一个匹配程序,将书籍(BookID)推荐给网友(UserID),生成今日推荐数据时,有条规则是同一书籍七日 ...

  4. python 字典有序无序及查找效率,hash表

    刚学python的时候认为字典是无序,通过多次插入,如di = {}, 多次di['testkey']='testvalue' 这样测试来证明无序的.后来接触到了字典查找效率这个东西,查了一下,原来字 ...

  5. HBase 是列式存储数据库吗

    在介绍 HBase 是不是列式存储数据库之前,我们先来了解一下什么是行式数据库和列式数据库. 行式数据库和列式数据库 在维基百科里面,对行式数据库和列式数据库的定义为:列式数据库是以列相关存储架构进行 ...

  6. MySQL数据库中的字段类型varchar和char的主要区别是什么?哪种字段查找效率要高?

    1,varchar与char的区别?(1)区别一,定长和变长,char表示定长,长度固定:varchar表示变长,长度可变.当插入字符串超出长度时,视情况来处理,如果是严格模式,则会拒绝插入并提示错误 ...

  7. python中in在list和dict中查找效率比较

    转载自:http://blog.csdn.net/wzgbm/article/details/54691615 首先给一个简单的例子,测测list和dict查找的时间: ,-,-,-,-,,,,,,] ...

  8. MySQL select * 和把所有的字段都列出来,哪个效率更高?

    MySQL select * 和把所有的字段都列出来,哪个效率更高 答案是:如何,都不推荐使用 SELECT * FROM (1)SELECT *,需要数据库先 Query Table Metadat ...

  9. 五分钟轻松了解Hbase面向列的存储

    说明:从严格的列式存储的定义来看,Hbase并不属于列式存储,有人称它为面向列的存储,请各位看官注意这一点. 行式存储 传统的数据库是关系型的,且是按行来存储的.如下图: 其中只有张三把一行数据填满了 ...

随机推荐

  1. z-index在IE中的坑

    在CSS2.1规范中,每个盒模型的位置是三维的,分别是平面画布上的x轴,y轴以及表示层叠的z轴.对于每个html元素,都可以通过设置z-index属性来设置该元素在视觉渲染模型中的层叠顺序. z-in ...

  2. js二维码插件总结

    jquery.qrcode.js生成二维码插件&转成图片格式 http://blog.csdn.net/u011127019/article/details/51226104

  3. 【转】sed正则表达式

    1 正则表达式简介 正则表达式(Regular Expression) 是一种描述文本(或字符串)模式的工具.正则表达式常用于查找文本的场合.想想一下我们日常生活中的例子,假如你想从电话本里找一个联系 ...

  4. YII关联字段并带搜索排序功能

    1.简介 从接触yii框架到现在已经快有两个月了,但是自己对yii框架的了解程度并不是很深,并没有系统地去学习,仅仅只是在做项目的时候遇到不懂得知识才去翻手册. 在上一个项目中因为需要将关联的表的字段 ...

  5. C#基础(六)--枚举的一些常用操作

    本章将介绍以下几点: 1.如何把其它类型转换为枚举类型? 2.如何把枚举中的值添加到下拉菜单中? 一.如何把其它类型转换为枚举类型?        我们回顾一下有关字符串与数字之间的转换,如:     ...

  6. 基于 React + Webpack 的音乐相册项目(上)

    笔记仓库:https://github.com/nnngu/LearningNotes 上一篇文章用爬虫自动下载了一些图片,这一篇就用这些图片做一个音乐相册吧! 效果预览 点击图片,切换到背面: 演示 ...

  7. linux中vim常用的快捷键

    移动光标的方法 h或者向左箭头:光标向左移动一个字符 j或者向下箭头:光标向下移动一个字符 k或者向上箭头:光标向上移动一个字符 i或者向右箭头:光标向右移动一个字符 Ctrl+f:屏幕向下移动一页[ ...

  8. java 虚拟机--新生代与老年代GC [转]

    原文链接:http://www.360doc.com/content/12/1023/16/9615799_243296263.shtml 1. Java堆中各代分布: 图1:Java堆中各代分布 Y ...

  9. C# 使用GDI制作垂直进度条(由下往上)

    使用GDI+绘进度条的方式多种多样,可以封装一个UserControl,也可以直接使用一个控件来绘制(Label.Image.Panel等),甚至可以直接在winForm上画一个,关键代码没几行(这里 ...

  10. 2018/1/27 Zookeeper实现分布式锁

    public class DistributedClient { // 超时时间 private static final int SESSION_TIMEOUT = 5000; // zookeep ...