HBase在idea中(JAVA API)、过滤器
JAVA API
pom文件
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>1.4.6</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.7.6</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.6</version> </dependency>
代码
package hbaseapi;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 1.获取与hbase的连接对象
* 2.操作命名空间和表的创建删除修改等操作
* 3.对表中数据进行操作
*
* 操作:创建、删除、添加、查询(一条、多条、有需求);
*
* 预分region
*/
public class HBaseClintApi {
HConnection conn=null;
HBaseAdmin hadmin=null;
@Before
public void init(){
try {
//1、获取hadoop的相关环境
Configuration conf = new Configuration();
//2、获取zookeeper集群的配置
conf.set("hbase.zookeeper.quorum","node1,node2,master");
//3、获取hbase的连接对象
conn = HConnectionManager.createConnection(conf);
//4、获取HMaster的连接对象
hadmin = new HBaseAdmin(conf);
System.out.println("获取连接成功:"+hadmin);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 创建表
*/
@Test
public void createTable(){
try {
//创建表的必要因素表名和列簇
HTableDescriptor students = new HTableDescriptor("students");
//设置列簇,创建java语言对应的列簇对象
HColumnDescriptor columnDescriptor = new HColumnDescriptor("info");
//将列簇加入到列表中
students.addFamily(columnDescriptor);
//创建表
hadmin.createTable(students);
//Hbase中获取字节流的特定方式:Bytes
System.out.println(Bytes.toString(students.getName())+"表创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 在学习hbase的时候,我们知道数据的写入实际上是往region中写的,一开始,只有一个region,被一个regionsever所管理,
* 那么如果这时候,有大量的数据插入到表中,对该regionsever进行大量的通信,对这个rs造成很大的压力,而其他的rs没有数据的到来
* 造成数据热点问题
* 我们所学习的解决办法是 预分region:在建表的时候,提供切割点
*/
@Test
public void preRegionTable() {
try {
//判断表是否存在
boolean b = hadmin.tableExists("pre_table");
if (b) {
hadmin.disableTable("pre_table");
hadmin.deleteTable("pre_table");
} else {
//创建表描述器
HTableDescriptor pre_table = new HTableDescriptor("pre_table");
//创建列簇描述器
HColumnDescriptor info = new HColumnDescriptor("info");
//设置列簇的过期时间,单位为秒s
info.setTimeToLive(60 * 60 * 24);
//设置版本的个数
info.setMaxVersions(3);
//将列簇添加到表中
pre_table.addFamily(info);
// hadmin.createTable(pre_table);
//创建一个二维数组,表示我们的切割点
byte[][] splitKeys = {
"a".getBytes(),
"g".getBytes(),
"k".getBytes()
};
hadmin.createTable(pre_table, splitKeys);
System.out.println("预分region表创建成功!!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* hbase是一个列式存储数据库
* 插入数据
*
* put '表名','行键','列名','值'
* 例如:put 'students','1001','info:name','abc'
*/
@Test
public void insertTable(){
try {
//获取表
HTableInterface students = conn.getTable("students");
//将数据封装成一个put对象
Put put = new Put("1001".getBytes());
//用老版本的写法传入列簇、列名和列值
put.add("info".getBytes(),"name".getBytes(),"abc".getBytes());
//调用表实例中的put方法添加数据
students.put(put);
System.out.println("添加数据成功!!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取一条数据
*/
@Test
public void getTable(){
try {
//获取students表实例
HTableInterface students = conn.getTable("students");
//创建get对象
Get get = new Get("1001".getBytes());
Result result = students.get(get);
String name = Bytes.toString(result.getValue("info".getBytes(), "name".getBytes()));
System.out.println(name);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将学生表的1000条数据加进去
* 1500100011,宰运华,21,男,理科三班
* 以逗号分割,需要封装成五个put对象
*/
@Test
public void putTable(){
try {
HTableInterface students = conn.getTable("students");
ArrayList<Put> puts = new ArrayList<>();
BufferedReader br = new BufferedReader(new FileReader("D:\\soft\\projects\\bigdata19-project\\bigdata19-hbase\\data\\students.txt"));
String line=null;
while((line=br.readLine())!=null){
//根据逗号分割
String[] strings = line.split(",");
String id=strings[0];
String name = strings[1];
String age = strings[2];
String gender = strings[3];
String calzz = strings[4];
//将每一列封装成put对象
Put put=new Put(id.getBytes());
put.add("info".getBytes(),"name".getBytes(),name.getBytes());
put.add("info".getBytes(),"age".getBytes(),age.getBytes());
put.add("info".getBytes(),"gender".getBytes(),gender.getBytes());
put.add("info".getBytes(),"calzz".getBytes(),calzz.getBytes());
puts.add(put);
}
students.put(puts);
System.out.println("学生添加完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取批量数据
* <p>
* scan 'students'
* <p>
* hbase是大数据环境下的数据库,将来一张表的数据量肯定是上万条记录,一般情况下,我们更多的是查部分数据(想要的数据)
*/
@Test
public void scanMoreData(){
try {
HTableInterface students = conn.getTable("students");
//创建一个scan对象
Scan scan = new Scan();//如果没有其它条件说明是全表扫描
scan.setStartRow("1500100001".getBytes());
scan.setStopRow("1500100011".getBytes());
//开始位置和停止位置
ResultScanner scanner = students.getScanner(scan);
Result rs=null;
while((rs=scanner.next())!=null){
String id = Bytes.toString(rs.getRow());
// String name = Bytes.toString(rs.getValue("info".getBytes(), "name".getBytes()));
// String age = Bytes.toString(rs.getValue("info".getBytes(), "age".getBytes()));
// String gender = Bytes.toString(rs.getValue("info".getBytes(), "gender".getBytes()));
// String clazz = Bytes.toString(rs.getValue("info".getBytes(), "clazz".getBytes()));
//
// System.out.println("id:" + id + ",name:" + name + ",age:" + age + ",gender:" + gender + ",clazz:" + clazz);
//上面的获取操作,是在我们知道一行列的个数以及列的名字的前提下使用的方式
//当我们不知道列的个数以及列的名字的时候,该如何遍历呢?
//因为hbase是列式存储数据库,每一行中的列的个数不一样也是很正常的情况
//获取一行中的所有单元格
List<Cell> cells = rs.listCells();
StringBuilder sb = new StringBuilder();
sb.append("id:").append(id).append(",");
for (Cell cell : cells) {
//每个单元格中包含了rowkey,列簇,列名,版本号,列值
String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
sb.append(qualifier).append(":").append(value).append("\t");
String s = sb.toString();
sb = new StringBuilder();
System.out.print(s);
}
//遍历一行后换行
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除表
*/
@Test
public void deleteTable(){
try {
//删除之前判断表存不存在
boolean b=false;
b=hadmin.tableExists("students");
if(b){
String name = conn.getTable("students").getName().toString();
//hbase中的表,删除之前需要先禁用该表才能删除
hadmin.disableTable("students");
hadmin.deleteTable("students");
System.out.println(name+"表 删除成功");
}else{
System.out.println("该表不存在");
}
} catch (IOException e) {
e.printStackTrace();
}
}
@After
public void close(){
if(conn!=null){
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(hadmin!=null){
try {
hadmin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
过滤器
过滤器可以根据列族、列、版本等更多的条件来对数据进行过滤,
基于 HBase 本身提供的三维有序(行键,列,版本有序),这些过滤器可以高效地完成查询过滤的任务,带有过滤器条件的 RPC 查询请求会把过滤器分发到各个 RegionServer(这是一个服务端过滤器),这样也可以降低网络传输的压力。
使用过滤器至少需要两类参数:
过滤器作用
-
-
过滤器的类型很多,但是可以分为三大类:
-
比较过滤器:可应用于rowkey、列簇、列、列值过滤器
-
专用过滤器:只能适用于特定的过滤器
-
-
比较过滤器
public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) { this.compareOp = compareOp; this.comparator = comparator; }
比较远算符
-
-
LESS_OR_EQUAL <=
-
EQUAL =
-
NOT_EQUAL <>
-
GREATER_OR_EQUAL >=
-
GREATER >
-
常见的六大比较器
BinaryComparator (二进制比较器)
按字节索引顺序比较指定字节数组,采用Bytes.compareTo(byte[])
BinaryPrefixComparator(二进制前缀比较器)
通BinaryComparator,只是比较左端前缀的数据是否相同
NullComparator
判断给定的是否为空
BitComparator
按位比较
RegexStringComparator(正则比较器)
提供一个正则的比较器,仅支持 EQUAL 和非EQUAL
SubstringComparator(包含比较比较器)
判断提供的子串是否出现在中
代码演示
after、before、print代码:
@Before public void init() { try { //1、获取hadoop的相关环境 Configuration conf = new Configuration(); //2、设置zookeeper集群的配置 conf.set("hbase.zookeeper.quorum", "node1,node2,master"); //3、获取hbase的连接对象 conn = HConnectionManager.createConnection(conf); //4、获取HMaster的对象 hadmin = new HBaseAdmin(conf); System.out.println("获取连接成功:" + hadmin); } catch (IOException e) { e.printStackTrace(); } }
@After
public void close() {
if (hadmin != null) {
try {
hadmin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void print(ResultScanner scanner) {
try {
Result rs = null;
while ((rs = scanner.next()) != null) {
String id = Bytes.toString(rs.getRow());
// String name = Bytes.toString(rs.getValue("info".getBytes(), "name".getBytes()));
// String age = Bytes.toString(rs.getValue("info".getBytes(), "age".getBytes()));
// String gender = Bytes.toString(rs.getValue("info".getBytes(), "gender".getBytes()));
// String clazz = Bytes.toString(rs.getValue("info".getBytes(), "clazz".getBytes()));
//
// System.out.println("id:" + id + ",name:" + name + ",age:" + age + ",gender:" + gender + ",clazz:" + clazz);、
//上面的获取操作,是在我们知道一行列的个数以及列的名字的前提下使用的方式
//当我们不知道列的个数以及列的名字的时候,该如何遍历呢?
//因为hbase是列式存储数据库,每一行中的列的个数不一样也是很正常的情况
//获取一行中的所有单元格
List<Cell> cells = rs.listCells();
StringBuilder sb = new StringBuilder();
sb.append("行键:").append(id).append("\t");
for (Cell cell : cells) {
//每个单元格中包含了rowkey,列簇,列名,版本号,列值
String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
sb.append(qualifier).append(":").append(value).append("\t");
String s = sb.toString();
sb = new StringBuilder();
System.out.print(s);
}
//遍历一行后换行
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
}
rowKey过滤器:RowFilter 行键过滤器
通过RowFilter与BinaryComparator过滤比rowKey 1500100010小的所有值出来
package com.shujia.hbaseapi; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.CellUtil; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.filter.BinaryComparator; import org.apache.hadoop.hbase.filter.CompareFilter; import org.apache.hadoop.hbase.filter.RowFilter; import org.apache.hadoop.hbase.util.Bytes; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.util.List; public class HbaseComparatorFilter { HConnection conn = null; HBaseAdmin hadmin = null; @Before public void init() { try { //1、获取hadoop的相关环境 Configuration conf = new Configuration(); //2、设置zookeeper集群的配置 conf.set("hbase.zookeeper.quorum", "node1,node2,master"); //3、获取hbase的连接对象 conn = HConnectionManager.createConnection(conf); //4、获取HMaster的对象 hadmin = new HBaseAdmin(conf); System.out.println("获取连接成功:" + hadmin); } catch (IOException e) { e.printStackTrace(); } } /** * 通过RowFilter与BinaryComparator过滤比rowKey 1500100010小的所有值出来 */ @Test public void rowFilerTest() { try { //获取表的实例 HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); //创建BinaryComparator比较器 BinaryComparator binaryComparator = new BinaryComparator("1500100010".getBytes()); //创建行键过滤器 RowFilter RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.LESS_OR_EQUAL, binaryComparator); scan.setFilter(rowFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } } @After public void close() { if (hadmin != null) { try { hadmin.close(); } catch (IOException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (IOException e) { e.printStackTrace(); } } } public void print(ResultScanner scanner) { try { Result rs = null; while ((rs = scanner.next()) != null) { String id = Bytes.toString(rs.getRow()); // String name = Bytes.toString(rs.getValue("info".getBytes(), "name".getBytes())); // String age = Bytes.toString(rs.getValue("info".getBytes(), "age".getBytes())); // String gender = Bytes.toString(rs.getValue("info".getBytes(), "gender".getBytes())); // String clazz = Bytes.toString(rs.getValue("info".getBytes(), "clazz".getBytes())); // // System.out.println("id:" + id + ",name:" + name + ",age:" + age + ",gender:" + gender + ",clazz:" + clazz);、 //上面的获取操作,是在我们知道一行列的个数以及列的名字的前提下使用的方式 //当我们不知道列的个数以及列的名字的时候,该如何遍历呢? //因为hbase是列式存储数据库,每一行中的列的个数不一样也是很正常的情况 //获取一行中的所有单元格 List<Cell> cells = rs.listCells(); StringBuilder sb = new StringBuilder(); sb.append("行键:").append(id).append("\t"); for (Cell cell : cells) { //每个单元格中包含了rowkey,列簇,列名,版本号,列值 String qualifier = Bytes.toString(CellUtil.cloneQualifier(cell)); String value = Bytes.toString(CellUtil.cloneValue(cell)); sb.append(qualifier).append(":").append(value).append("\t"); String s = sb.toString(); sb = new StringBuilder(); System.out.print(s); } //遍历一行后换行 System.out.println(); } } catch (IOException e) { e.printStackTrace(); } } }
通过FamilyFilter与SubstringComparator查询列簇名包含in的所有列簇下面的数据
/** * 通过FamilyFilter与SubstringComparator查询列簇名包含in的所有列簇下面的数据 */ @Test public void familyFilterTest(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); SubstringComparator substringComparator = new SubstringComparator("in"); FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, substringComparator); scan.setFilter(familyFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
通过FamilyFilter与 BinaryPrefixComparator 过滤出列簇以i开头的列簇下的所有数据
/** * 通过FamilyFilter与 BinaryPrefixComparator 过滤出列簇以i开头的列簇下的所有数据 */ @Test public void familyFilterTest2(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("i".getBytes()); FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.EQUAL, binaryPrefixComparator); scan.setFilter(familyFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
列名过滤器:QualifierFilter
通过QualifierFilter与SubstringComparator查询列名包含ge的列的值
/** * 通过QualifierFilter与SubstringComparator查询列名包含ge的列的值 */ @Test public void qualifierFilterTest(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); SubstringComparator ge = new SubstringComparator("ge"); QualifierFilter qualifierFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL, ge); scan.setFilter(qualifierFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); }
过滤出列的名字中 包含 "a" 所有的列及列的值
@Test public void qualifierFilterTest(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); SubstringComparator ge = new SubstringComparator("a"); QualifierFilter qualifierFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL, ge); scan.setFilter(qualifierFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
列值过滤器:ValueFilter
通过ValueFilter与BinaryPrefixComparator过滤出所有的cell中值以 "张" 开头的学生
/**
* ###### 列值过滤器:ValueFilter
*
* > 通过ValueFilter与BinaryPrefixComparator过滤出所有的cell中值以 "张" 开头的学生
*/
@Test
public void valueFilteTest(){
try {
HTableInterface students = conn.getTable("students");
Scan scan = new Scan();
BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("张".getBytes());
ValueFilter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, binaryPrefixComparator);
scan.setFilter(valueFilter);
ResultScanner scanner = students.getScanner(scan);
print(scanner);
} catch (IOException e) {
e.printStackTrace();
}
}
过滤出文科的学生,只会返回以文科开头的数据列,其他列的数据不符合条件,不会返回
@Test public void valueFilterTest1() { try { //获取表的实例 HTableInterface students = conn.getTable("students"); //创建扫描器对象 Scan scan = new Scan(); //创建二进制前缀比较器 BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("文科".getBytes()); //创建列值过滤器 ValueFilter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, binaryPrefixComparator); scan.setFilter(valueFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
专用过滤器
单列值过滤器:SingleColumnValueFilter
SingleColumnValueFilter会返回满足条件的cell所在行的所有cell的值(即会返回一行数据)
/** * #### 专用过滤器 * * ###### 单列值过滤器:SingleColumnValueFilter * * > SingleColumnValueFilter会返回满足条件的cell所在行的所有cell的值(即会返回一行数据) * > * > 通过SingleColumnValueFilter与查询文科班所有学生信息 */ @Test public void singleColumnValueFilterTest(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("文科".getBytes()); SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter("info".getBytes(), "clazz".getBytes(), CompareFilter.CompareOp.EQUAL, binaryPrefixComparator); scan.setFilter(singleColumnValueFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
与SingleColumnValueFilter相反,会排除掉指定的列,其他的列全部返回
通过SingleColumnValueExcludeFilter与BinaryComparator查询文科一班所有学生信息,最终不返回clazz列
/** * ###### 列值排除过滤器:SingleColumnValueExcludeFilter * * > 与SingleColumnValueFilter相反,会排除掉指定的列,其他的列全部返回 * > * > 通过SingleColumnValueExcludeFilter与BinaryComparator查询文科一班所有学生信息,最终不返回clazz列 */ @Test public void singleColumnValueExcludeFilter(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("文科".getBytes()); SingleColumnValueExcludeFilter singleColumnValueExcludeFilter = new SingleColumnValueExcludeFilter("info".getBytes(), "clazz".getBytes(), CompareFilter.CompareOp.EQUAL, binaryPrefixComparator); scan.setFilter(singleColumnValueExcludeFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
通过PrefixFilter查询以15001001开头的所有前缀的rowkey
/** * ###### rowkey前缀过滤器:PrefixFilter * * > 通过PrefixFilter查询以15001001开头的所有前缀的rowkey */ public void prefixFilterTest(){ try { HTableInterface students = conn.getTable("students"); Scan scan = new Scan(); //创建行键前缀过滤器 PrefixFilter prefixFilter = new PrefixFilter("15001001".getBytes()); scan.setFilter(prefixFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
通过PageFilter查询三页的数据,每页10条
使用PageFilter分页效率比较低,每次都需要扫描前面的数据,直到扫描到所需要查的数据
可设计一个合理的rowkey来实现分页需求
# 注意事项: 客户端进行分页查询,需要传递 startRow(起始 RowKey),知道起始 startRow 后,就可以返回对应的 pageSize 行数据。这里唯一的问题就是,对于第一次查询,显然 startRow 就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道 startRow,只能知道上一次查询的最后一条数据的 RowKey(简单称之为 lastRow)。 我们不能将 lastRow 作为新一次查询的 startRow 传入,因为 scan 的查询区间是[startRow,endRow) ,即前开后闭区间,这样 startRow 在新的查询也会被返回,这条数据就重复了。 同时在不使用第三方数据库存储 RowKey 的情况下,我们是无法通过知道 lastRow 的下一个 RowKey 的,因为 RowKey 的设计可能是连续的也有可能是不连续的。 由于 Hbase 的 RowKey 是按照字典序进行排序的。这种情况下,就可以在 lastRow 后面加上 0 ,作为 startRow 传入,因为按照字典序的规则,某个值加上 0 后的新值,在字典序上一定是这个值的下一个值,对于 HBase 来说下一个 RowKey 在字典序上一定也是等于或者大于这个新值的。 所以最后传入 lastRow+0,如果等于这个值的 RowKey 存在就从这个值开始 scan,否则从字典序的下一个 RowKey 开始 scan。 25 个字母以及数字字符,字典排序如下: '0' < '1' < '2' < ... < '9' < 'a' < 'b' < ... < 'z'
需要注意的是在多台 Regin Services 上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了 PageCount 行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。
包装过滤器
SkipFilter过滤器
SkipFilter包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。下面是一个使用示例:
// 定义 ValueFilter 过滤器 Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL, new BinaryComparator(Bytes.toBytes("xxx"))); // 使用 SkipFilter 进行包装 Filter filter2 = new SkipFilter(filter1);
WhileMatchFilter 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,WhileMatchFilter 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:
/** * 包装过滤器,--> 行键过滤器 */ @Test public void packFilterTest(){ try { //获取表的实例 HTableInterface students = conn.getTable("students"); //需求:除了09的学生,都获取 Scan scan = new Scan(); SubstringComparator substringComparator = new SubstringComparator("1500100009"); //创建行键过滤器 RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.NOT_EQUAL, substringComparator); scan.setFilter(rowFilter); ResultScanner scanner = students.getScanner(scan); print(scanner); System.out.println("=============================使用包装过滤器========================================"); //该过滤器当扫描到符合条件的值的时候,就停止扫描了 WhileMatchFilter whileMatchFilter = new WhileMatchFilter(rowFilter); Scan scan1 = new Scan(); scan1.setFilter(whileMatchFilter); ResultScanner scanner1 = students.getScanner(scan1); print(scanner1); } catch (IOException e) { e.printStackTrace(); } }
以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 FilterList
。FilterList
支持通过构造器或者 addFilter
方法传入多个过滤器。
通过运用4种比较器过滤出姓于,年纪大于23岁,性别为女,且是理科的学生。
/** * 通过运用4种比较器过滤出姓于,年纪小于等于23岁,性别为女,且是理科的学生。 * * BinaryComparator 性别为女 * BinaryPrefixComparator 姓于 * RegexStringComparator 理科的学生 * SubstringComparator 23岁 * * */ @Test public void test1(){ try { //获取表的实例 HTableInterface students = conn.getTable("students"); //通过运用4种比较器过滤出姓于,年纪大于23岁,性别为女,且是理科的学生。 //由于需求都是列值,所以我们用列值过滤器 //SingleColumnValueFilter //BinaryPrefixComparator /** * 编写第一个过滤器: */ BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("于".getBytes()); SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( "info".getBytes(), "name".getBytes(), CompareFilter.CompareOp.EQUAL, binaryPrefixComparator ); /** * 编写第二个过滤器:年纪大于23岁 * SubstringComparator */ SubstringComparator substringComparator = new SubstringComparator("23"); SingleColumnValueFilter singleColumnValueFilter2 = new SingleColumnValueFilter( "info".getBytes(), "age".getBytes(), CompareFilter.CompareOp.LESS_OR_EQUAL, substringComparator ); /** * 编写第三个过滤器:性别为女 * BinaryComparator */ BinaryComparator binaryComparator = new BinaryComparator("女".getBytes()); SingleColumnValueFilter singleColumnValueFilter3 = new SingleColumnValueFilter( "info".getBytes(), "gender".getBytes(), CompareFilter.CompareOp.EQUAL, binaryComparator ); /** * 编写第四个过滤器:理科的学生 * RegexStringComparator */ RegexStringComparator regexStringComparator = new RegexStringComparator("^理科.*"); SingleColumnValueFilter singleColumnValueFilter4 = new SingleColumnValueFilter( "info".getBytes(), "clazz".getBytes(), CompareFilter.CompareOp.EQUAL, regexStringComparator ); Scan scan = new Scan(); // scan.setFilter(singleColumnValueFilter); // scan.setFilter(singleColumnValueFilter2); // scan.setFilter(singleColumnValueFilter3); // scan.setFilter(singleColumnValueFilter4); //创建一个FilterList对象 FilterList filterList = new FilterList(); filterList.addFilter(singleColumnValueFilter); filterList.addFilter(singleColumnValueFilter2); filterList.addFilter(singleColumnValueFilter3); filterList.addFilter(singleColumnValueFilter4); scan.setFilter(filterList); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
过滤出学号是以15001001开头的文科学生
/** * 过滤出学号是以15001001开头的文科学生 */ @Test public void test2(){ try { HTableInterface students = conn.getTable("students"); BinaryPrefixComparator binaryPrefixComparator = new BinaryPrefixComparator("15001001".getBytes()); RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, binaryPrefixComparator); RegexStringComparator regexStringComparator = new RegexStringComparator("^文科.*"); SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter( "info".getBytes(), "clazz".getBytes(), CompareFilter.CompareOp.EQUAL, regexStringComparator ); FilterList filterList = new FilterList(); filterList.addFilter(rowFilter); filterList.addFilter(singleColumnValueFilter); Scan scan = new Scan(); scan.setFilter(filterList); ResultScanner scanner = students.getScanner(scan); print(scanner); } catch (IOException e) { e.printStackTrace(); } }
作业:查询文科一班学生总分排名前10的学生(输出:学号,姓名,班级,总分)结果写到hbase
布隆过滤器
本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。
实际上,布隆过滤器广泛应用于网页黑名单系统、垃圾邮件过滤系统、爬虫网址判重系统等,Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的 IO 次数,Google Chrome 浏览器使用了布隆过滤器加速安全浏览服务。
在很多 Key-Value 系统中也使用了布隆过滤器来加快查询过程,如 Hbase,Accumulo,Leveldb,一般而言,Value 保存在磁盘中,访问磁盘需要花费大量时间,然而使用布隆过滤器可以快速判断某个 Key 对应的 Value 是否存在,因此可以避免很多不必要的磁盘 IO 操作。
通过一个 Hash 函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。
运用场景
1、目前有 10 亿数量的自然数,乱序排列,需要对其排序。限制条件在 32 位机器上面完成,内存限制为 2G。如何完成?
2、如何快速在亿级黑名单中快速定位 URL 地址是否在黑名单中?(每条 URL 平均 64 字节)
3、需要进行用户登陆行为分析,来确定用户的活跃情况?
4、网络爬虫-如何判断 URL 是否被爬过?
5、快速定位用户属性(黑名单、白名单等)?
6、数据存储在磁盘中,如何避免大量的无效 IO?
7、判断一个元素在亿级数据中是否存在?
8、缓存穿透。
实现原理
假设我们有个集合 A,A 中有 n 个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为 a 位的数组 B中的不同位置上,这些位置上的二进制数均设置为 1。如果待检查的元素,经过这 k个哈希散列函数的映射后,发现其 k 个位置上的二进制数全部为 1,这个元素很可能属于集合A,反之,一定不属于集合A。
比如我们有 3 个 URL
{URL1,URL2,URL3}
,通过一个hash 函数把它们映射到一个长度为 16 的数组上,如下:
由于 Hash 存在哈希冲突,如上面URL2,URL3
都定位到一个位置上,假设 Hash 函数是良好的,如果我们的数组长度为 m 个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳 m/100
个元素,显然空间利用率就变低了,也就是没法做到空间有效(space-efficient)。
Hash1(URL1) = 3,Hash2(URL1) = 5,Hash3(URL1) = 6 Hash1(URL2) = 5,Hash2(URL2) = 7,Hash3(URL2) = 13 Hash1(URL3) = 4,Hash2(URL3) = 7,Hash3(URL3) = 10
误判现象
上面的做法同样存在问题,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断这个值存在。比如此时来一个不存在的
URL1000
,经过哈希计算后,发现 bit 位为下:
Hash1(URL1000) = 7,Hash2(URL1000) = 8,Hash3(URL1000) = 14
这就是布隆过滤器的误判现象,所以,布隆过滤器判断存在的不一定存在,但是,判断不存在的一定不存在。
布隆过滤器可精确的代表一个集合,可精确判断某一元素是否在此集合中,精确程度由用户的具体设计决定,达到 100% 的正确是不可能的。但是布隆过滤器的优势在于,利用很少的空间可以达到较高的精确率
a)ROW 根据KeyValue中的行来过滤storefile
举例:假设有2个storefile文件sf1和sf2,
sf1包含kv1(r1 cf:q1 v),kv2(r2 cf:q1 v)
sf2包含kv3(r3 cf:q1 v),kv4(r4 cf:q1 v)
若是设置了CF属性中的bloomfilter为ROW,那么得(r1)时就会过滤sf2,get(r3)就会过滤sf1
b)ROWCOL 根据KeyValue中的行+限定符来过滤storefile
举例:假设有2个storefile文件sf1和sf2,
sf1包含kv1(r1 cf:q1 v),kv2(r2 cf:q1 v)
sf2包含kv3(r1 cf:q2 v),kv4(r2 cf:q2 v)
若是设置了CF属性中的布隆过滤器为ROW,不管获得(R1,Q1)仍是获得(R1,Q2),都会读取SF1 + SF2;而若是设置了CF属性中的布隆过滤器为 ROWCOL,
那么GET(R1, q1)就会过滤sf2,get(r1,q2)就会过滤sf1
c)NO 默认的值,默认不开启布隆过滤器
实现:
try { //使用HTableDescriptor类创建一个表对象 HTableDescriptor students = new HTableDescriptor("students"); //在创建表的时候,至少指定一个列簇 HColumnDescriptor info = new HColumnDescriptor("info"); info.setBloomFilterType(BloomType.ROW); //<=========================================== //将列簇添加到表中 students.addFamily(info); //真正的执行,是由HMaster //hAdmin hAdmin.createTable(students); System.out.println(Bytes.toString(students.getName()) + "表 创建成功。。。"); } catch (IOException e) { e.printStackTrace(); }