使用HBase存储中国好声音数据的案例,业务描述如下:
为了能高效的查询到我们需要的数据,我们在RowKey的设计上下了不少功夫,因为过滤RowKey或者根据RowKey查询数据的效率是最高的,我们的RowKey的设计是:UserID + CreateTime + FileID,那么我们在HBase中的数据格式如下:
每一行数据中包含两个Column:f:c和f:n
我们在查询的时候还是用了SingleColumnValueFilter这个Filter来过滤单个的Column的Value的值,我们说如果在海量数据的时候使用这个SingleColumnValueFilter来过滤数据的话是非常耗时的事情,那么现在问题来了:
问题:
假设针对这张sound的表,我们需要查询包含“中国好声音”以及包含“综艺”的数据,也就是说我们的业务查询是:
2个条件同时输入find(“中国好声音”,“综艺”)
这个时候我们该怎么查询呢?
解决方案:
首先,我们现在的查询条件中没有对RowKey的过滤了,如果我们直接使用SingleColumnValueFilter这个Filter来过滤查询数据的话是可以达到目的,但是非常的耗时,所以我们不能使用这种方式
那么,我们现在就使用HBase中的二级索引来解决这个问题,我们先不解释二级索引是什么,我们先看下解决上面问题的过程,如下:
第一步:创建两张HBase表
第一张HBase表的RowKey是数据中的Name字段的值,这张表可以有不定数量的Column,每一个Column的值就是sound表的RowKey(和Name对应的RowKey),这张表我们称之为name_indexer表。create 'name_indexer','f'
第二张HBase表的RowKey是数据中的Category字段的值,这张表可以有不定数量的Column,每一个Column的值就是sound表的RowKey(和Category对应的RowKey),这张表我们称之为category_indexer表。create 'category_indexer','f'
 
第二步:将sound中的数据导入到name_indexer和category_indexer两张表中
使用Spark程序来实现索引表数据的导入,
import org.apache.hadoop.hbase.client.{ConnectionFactory, Put, Scan}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.util.Bytes
import org.apache.hadoop.hbase.{HBaseConfiguration, TableName}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession /**
* 使用Spark来建立HBase中表sound的二级索引
*/
object MyIndexBuilder {
def main(args: Array[String]): Unit = {
val spark = SparkSession
.builder()
.appName("MyIndexBuilder")
.master("local")
.getOrCreate() // 1、创建HBaseContext
val configuration = HBaseConfiguration.create()
configuration.set("hbase.zookeeper.quorum", "master,slave1,slave2")
val hBaseContext = new HBaseContext(spark.sparkContext, configuration) // 2、读取HBase表sound中的f:n和f:c两个列的值以及他们对应的rowKey的值
// 并且需要区分开是哪一个列的值
val soundRDD = hBaseContext.hbaseRDD(TableName.valueOf("sound"), new Scan())
val indexerRDD: RDD[((String, Array[Byte]), ImmutableBytesWritable)] = soundRDD.flatMap { case (byteRowKey, result) =>
val nameValue = result.getValue(Bytes.toBytes("f"), Bytes.toBytes("n"))
val categoryValue = result.getValue(Bytes.toBytes("f"), Bytes.toBytes("c"))
// 区分开是哪一个列的值,使用key来区分
// 返回key是(tableName,列值), value是这个列对应的rowKey的值
Seq((("name_indexer", nameValue), byteRowKey), (("category_indexer", categoryValue), byteRowKey))
} // 3、按照key进行分组,拿到相同列值对应的所有的rowKeys(因为在原表sound中多个rowKey的值可能会对应着相同的列值)
val groupedIndexerRDD: RDD[((String, Array[Byte]), Iterable[ImmutableBytesWritable])] = indexerRDD.groupByKey() // 4、将不同的列值以及对应的rowKeys写入到相对应的indexer表中
groupedIndexerRDD.foreachPartition { partitionIterator =>
val conf = HBaseConfiguration.create()
conf.set("hbase.zookeeper.quorum", "master,slave1,slave2")
val conn = ConnectionFactory.createConnection(conf) val nameIndexerTable = conn.getTable(TableName.valueOf("name_indexer"))
val categoryIndexerTable = conn.getTable(TableName.valueOf("category_indexer")) try {
val nameIndexerTablePuts = new util.ArrayList[Put]()
val categoryIndexerTablePuts = new util.ArrayList[Put]()
partitionIterator.map { case ((tableName, indexerValue), rowKeys) =>
val put = new Put(indexerValue) // 将列值作为索引表的rowKey
rowKeys.foreach(rowKey => {
put.addColumn(Bytes.toBytes("f"), null, rowKey.get())
})
if (tableName.equals("name_indexer")) {
nameIndexerTablePuts.add(put) // 需要写入到表name_indexer中的数据
} else {
categoryIndexerTablePuts.add(put) // 需要写入到表category_indexer中的数据
}
}
nameIndexerTable.put(nameIndexerTablePuts)
categoryIndexerTable.put(categoryIndexerTablePuts)
} finally {
nameIndexerTable.close()
categoryIndexerTable.close()
conn.close()
}
} spark.stop()
}
}

  

 
第三步:查询结果
我们先从name_indexer这张表中按照RowKey查询属于“中国好声音”的记录,这些记录中的所有的列的值就是需要在sound中查询的RowKey的值
然后从category_indexer这张表中按照RowKey查询属于“综艺”的记录,这些记录中的所有的列的值就是需要在sound中查询的RowKey的值
最后将上面两步查询出来的结果做一个合并,就是将查询出来的结果做一次去重,得到了所有在sound中符合需求的RowKey,然后在根据这些RowKey去sound表中查询相应的数据
我们每一步查询都是根据HBase中的一级索引RowKey来查询的,所以查询速度会非常的快
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; public class SecondaryIndexSearcher {
public static void main(String[] args) throws IOException {
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", "master,slave1,slave2");
try(Connection connection = ConnectionFactory.createConnection(config)) {
Table nameIndexer = connection.getTable(TableName.valueOf("name_indexer"));
Table categoryIndexer = connection.getTable(TableName.valueOf("category_indexer"));
Table sound = connection.getTable(TableName.valueOf("sound")); // 1、先从表name_indexer中找到rowKey包含“中国好声音”对应的所有的column值
Scan nameIndexerScan = new Scan();
SubstringComparator nameComp = new SubstringComparator("中国好声音");
RowFilter nameRowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, nameComp);
nameIndexerScan.setFilter(nameRowFilter); Set<String> soundRowKeySetOne = new HashSet<>();
ResultScanner rsOne = nameIndexer.getScanner(nameIndexerScan);
try {
for (Result r = rsOne.next(); r != null; r = rsOne.next()) {
for (Cell cell : r.listCells()) {
soundRowKeySetOne.add(Bytes.toString(CellUtil.cloneValue(cell)));
}
}
} finally {
rsOne.close(); // always close the ResultScanner!
} // 2、再从表category_indexer中找到rowKey包含“综艺”对应的所有的column值
Scan categoryIndexerScan = new Scan();
SubstringComparator categoryComp = new SubstringComparator("综艺");
RowFilter categoryRowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, categoryComp);
nameIndexerScan.setFilter(categoryRowFilter); Set<String> soundRowKeySetTwo = new HashSet<>();
ResultScanner rsTwo = categoryIndexer.getScanner(categoryIndexerScan);
try {
for (Result r = rsTwo.next(); r != null; r = rsTwo.next()) {
for (Cell cell : r.listCells()) {
soundRowKeySetTwo.add(Bytes.toString(CellUtil.cloneValue(cell)));
}
}
} finally {
rsTwo.close(); // always close the ResultScanner!
} // 3、合并并去重上面两步查询的结果
soundRowKeySetOne.addAll(soundRowKeySetTwo); // 4、根据soundRowKeySetOne中所有的rowKeys去sound表中查询数据
List<Get> gets = new ArrayList<>();
for (String rowKey : soundRowKeySetOne) {
Get get = new Get(Bytes.toBytes(rowKey));
gets.add(get);
}
Result[] results = sound.get(gets);
for (Result result : results) {
for (Cell cell : result.listCells()) {
System.out.println(Bytes.toString(CellUtil.cloneRow(cell)) + "===> " +
Bytes.toString(CellUtil.cloneFamily(cell)) + ":" +
Bytes.toString(CellUtil.cloneQualifier(cell)) + "{" +
Bytes.toString(CellUtil.cloneValue(cell)) + "}");
}
}
}
}
}

  

结论:
那么表name_indexer和category_indexer中的RowKey就是我们解决问题的二级索引,
所以二级索引的本质就是:建立各列值与行键之间的映射关系
 
最后,我们需要知道创建HBase二级索引的方式
1、Spark来实现二级索引的建立
我们前面使用的是Spark来实现二级索引的建立,但是这种方式适用于离线批处理,这些二级索引是每天或者每段时间执行一次的建立的
2、使用HBase的协处理器(coprocessor)
对于如果数据是实时更新的话,则这种离线批处理的方式是不行的,这个时候我们可以使用HBase的协处理器(coprocessor)
HBase的协处理器(Coprocessor)的介绍可以参考:https://www.cnblogs.com/small-k/p/9648453.html
 
3、HBase + Solr其实也是一个二级索引实现,只不过是把二级索引存储在Solr中

HBase的二级索引的更多相关文章

  1. HBase的二级索引,以及phoenix的安装(需再做一次)

    一:HBase的二级索引 1.讲解 uid+ts 11111_20161126111111:查询某一uid的某一个时间段内的数据 查询某一时间段内所有用户的数据:按照时间 索引表 rowkey:ts+ ...

  2. 085 HBase的二级索引,以及phoenix的安装(需再做一次)

    一:问题由来 1.举例 有A列与B列,分别是年龄与姓名. 如果想通过年龄查询姓名. 正常的检索是通过rowkey进行检索. 根据年龄查询rowkey,然后根据rowkey进行查找姓名. 这样的效率不高 ...

  3. HBase建立二级索引的一些解决方式

    HBase的一级索引就是rowkey,我们仅仅能通过rowkey进行检索. 假设我们相对hbase里面列族的列列进行一些组合查询.就须要採用HBase的二级索引方案来进行多条件的查询. 常见的二级索引 ...

  4. 基于Solr实现HBase的二级索引

    文章来源:http://www.open-open.com/lib/view/open1421501717312.html 实现目的: 由于hbase基于行健有序存储,在查询时使用行健十分高效,然后想 ...

  5. hbase coprocessor 二级索引

    Coprocessor方式二级索引 1. Coprocessor提供了一种机制可以让开发者直接在RegionServer上运行自定义代码来管理数据.通常我们使用get或者scan来从Hbase中获取数 ...

  6. [How to] MapReduce on HBase ----- 简单二级索引的实现

    1.简介 MapReduce计算框架是二代hadoop的YARN一部分,能够提供大数据量的平行批处理.MR只提供了基本的计算方法,之所以能够使用在不用的数据格式上包括HBase表上是因为特定格式上的数 ...

  7. 利用Phoenix为HBase创建二级索引

    为什么需要Secondary Index 对于Hbase而言,如果想精确地定位到某行记录,唯一的办法是通过rowkey来查询.如果不通过rowkey来查找数据,就必须逐行地比较每一列的值,即全表扫瞄. ...

  8. hbase构建二级索引解决方案

    关注公众号:大数据技术派,回复"资料",领取1024G资料. 1 为什么需要二级索引 HBase的一级索引就是rowkey,我们仅仅能通过rowkey进行检索.假设我们相对Hbas ...

  9. HBase二级索引的设计(案例讲解)

    摘要 最近做的一个项目涉及到了多条件的组合查询,数据存储用的是HBase,恰恰HBase对于这种场景的查询特别不给力,一般HBase的查询都是通过RowKey(要把多条件组合查询的字段都拼接在RowK ...

随机推荐

  1. 2019 CSP-J 游记(CQ LNBS考场 的退役之战)

    T0.10 爆零之战 已经不是第一次参加NOIP了(哦,关于兔子也NOIP了) 这次比赛的话,感觉考场很温馨,键盘很舒适,老师很友善,下次还会来.(哦不,下次来不了了,即将提前退役[大雾]) 刚刚文化 ...

  2. 第N个丑数

    #include <bits/stdc++.h> using namespace std; #define ll long long /* 把只包含质因子2.3和5的数称作丑数(Ugly ...

  3. Python有用的内置函数divmod,id,sorted,enumerate,input,oct,eval,exec,isinstance,ord,chr,filter,vars,zip

    divmod(a, b) 函数接收两个数字类型(非复数)参数,返回一个包含商和余数的元组(a // b, a % b) id() 函数用于获取对象的内存地址. sorted(iterable, key ...

  4. python递归函数和河内塔问题

    关于递归函数: 函数内部调用自身的函数. 以n阶乘为例: f(n) = n ! = 1 x 2 x 3 x 4 x...x(n-1)x(n) = n x (n-1) ! def factorial(n ...

  5. IDEA中使用git

    这篇文章写的很好,记录参考 [转载] https://blog.csdn.net/autfish/article/details/52513465 https://www.jianshu.com/p/ ...

  6. Idea中类实现Serializable接口 引入 serialVersionUID

    idea实现Serializable接口,然后打出serialVersionUID的办法 setting>editor>Inspection>Java>Serializatio ...

  7. .net core获取数据库连接 抛出The type initializer to throw an exception

    原文:https://www.cnblogs.com/pudefu/p/7580722.html 在.NET Framework框架时代我们的应用配置内容一般都是写在Web.config或者App.c ...

  8. Java之路---Day07

    2019-10-21-23:30:24 ArrayList类[集合] What:java.util.ArrayList是大小可变的数组的实现,存储在内的数据称为元元素,此类提供一些方法来操作内部存储的 ...

  9. Spring对于事务的控制@Transactional注解详解

    引用自:https://blog.csdn.net/fanxb92/article/details/81296005 先简单介绍一下Spring事务的传播行为: 所谓事务的传播行为是指,如果在开始当前 ...

  10. python day19 : 购物商城作业,进程与多线程

    目录 python day 19 1. 购物商城作业要求 2. 多进程 2.1 简述多进程 2.2 multiprocessing模块,创建多进程程序 2.3 if name=='main'的说明 2 ...