I know, i know
地球另一端有你陪我
一、过滤器
为了配合 hbase 查找数据时更加人性化,提供了过滤器进数据的过滤
普通的过滤器需要配合比较运算符和比较器进行使用
1、常见的比较运算符
-
LESS <
-
LESS_OR_EQUAL <=
-
EQUAL =
-
NOT_EQUAL <>
-
GREATER_OR_EQUAL >=
-
GREATER >
-
NO_OP 排除所有
2、常见的比较器
BinaryComparator
按字节索引顺序比较指定字节数组,采用Bytes.compareTo(byte[])
BinaryPrefixComparator
通BinaryComparator,只是比较左端前缀的数据是否相同
RegexStringComparator
提供一个正则的比较器,仅支持 EQUAL 和非EQUAL
SubstringComparator
判断提供的子串是否出现在中
3、常见过滤器
rowKey过滤器:RowFilter
列簇过滤器:FamilyFilter
列过滤器:QualifierFilter
列值过滤器:ValueFilter
4、专用过滤器
单列值过滤器:SingleColumnValueFilter
SingleColumnValueFilter会返回满足条件的cell所在行的所有cell的值(即会返回一行数据)
列值排除过滤器:SingleColumnValueExcludeFilter
与SingleColumnValueFilter类似,会排除掉指定的列,其他的列全部返回
rowkey前缀过滤器:PrefixFilter
比对前缀是否相同
分页过滤器PageFilter
近似 scan 中的 limit
5、多过滤器 FilterList
全在代码里
package day51;
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.*;
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 FilterTest {
Connection conn;
Admin admin;
TableName studentTN;
Table students;
@Before
public void createConn() throws IOException {
// 1、创建一个配置文件
Configuration conf = HBaseConfiguration.create();
// 配置ZK的地址,通过ZK可以找到HBase
conf.set("hbase.zookeeper.quorum", "master:2181,node1:2181,node2:2181");
// 2、创建连接
conn = ConnectionFactory.createConnection(conf);
// 3、创建Admin对象
admin = conn.getAdmin();
studentTN = TableName.valueOf("students");
students = conn.getTable(studentTN);
}
public void print(Filter filter) throws IOException {
Scan scan = new Scan();
scan.setFilter(filter);
ResultScanner scanner = students.getScanner(scan);
for (Result rs : scanner) {
String id = Bytes.toString(rs.getRow());
String name = Bytes.toString(rs.getValue("cf1".getBytes(), "name".getBytes()));
String age = Bytes.toString(rs.getValue("cf1".getBytes(), "age".getBytes()));
String gender = Bytes.toString(rs.getValue("cf1".getBytes(), "gender".getBytes()));
String clazz = Bytes.toString(rs.getValue("cf1".getBytes(), "clazz".getBytes()));
System.out.println(id + "," + name + "," + age + "," + gender + "," + clazz);
}
}
public void printCell(Filter filter) throws IOException{
Scan scan = new Scan();
scan.setFilter(filter);
ResultScanner scanner = students.getScanner(scan);
for (Result rs : scanner) {
for (Cell cell : rs.listCells()) {
String id = Bytes.toString(CellUtil.cloneRow(cell));
String f = Bytes.toString(CellUtil.cloneFamily(cell));
String q = Bytes.toString(CellUtil.cloneQualifier(cell));
String v = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(id + "," + f + ":" + q + " " + v);
}
}
}
@Test
// BinaryComparator 二进制比较器,完全匹配
// ValueFilter 列值过滤器,会过滤每一个列值,即cell
// 因此只要一条数据有一个cell满足条件就会被留下,没啥大用
// 此处尝试 age > 23
public void BinaryComparatorTest() throws IOException {
// 创建二进制比较器
BinaryComparator bct
= new BinaryComparator("23".getBytes());
// 创建列值过滤器(比较运算符,比较器)
ValueFilter vft
= new ValueFilter(CompareFilter.CompareOp.GREATER, bct);
print(vft);
}
@Test
// SingleColumnValueFilter 单列值过滤器
// 指定表,列簇:列比较其下的值
// 一次比较一条,因此不会有上一个的问题
// age > 23 的学生
public void SingleColumnValueFilterTest() throws IOException {
SingleColumnValueFilter scvft = new SingleColumnValueFilter(
// 列簇,列名
"cf1".getBytes(), "age".getBytes(),
// 比较运算符
CompareFilter.CompareOp.GREATER,
// 比较值
"23".getBytes());
print(scvft);
}
@Test
// 和单列值基本相同,只是会过滤掉被比较的列
public void SingleColumnValueExcludeFilterTest()
throws IOException {
SingleColumnValueExcludeFilter scveft
= new SingleColumnValueExcludeFilter(
// 列簇,列名
"cf1".getBytes(), "age".getBytes(),
// 比较运算符
CompareFilter.CompareOp.GREATER,
// 比较值
"23".getBytes());
print(scveft);
}
@Test
// BinaryPrefixComparator 二进制前缀比较器
// 前缀进行匹配
// RowFilter rowkey过滤器
// 查找学号 15001009 开头的学生
public void BinaryPrefixComparator() throws IOException {
// 二进制前缀比较器
BinaryPrefixComparator bpct
= new BinaryPrefixComparator("15001009".getBytes());
// rowkey 过滤器
RowFilter rft
= new RowFilter(CompareFilter.CompareOp.EQUAL, bpct);
print(rft);
}
@Test
// PrefixFilter:rowkey 前缀过滤器
// 锁定过滤 rowkey
// 相当于BinaryPrefixComparator + RowFilter
public void PrefixFilter() throws IOException {
// 由于只能比较 rowkey 因此不需要其他参数
PrefixFilter pfft = new PrefixFilter("15001009".getBytes());
print(pfft);
}
@Test
// RegexStringComparator 正则比较器
// 例:[A-Za-z0-9]{1}f[0-9]+
// 1个(A-Za-z0-9)的 + f + 1到N个(0-9)
// FamilyFilter 列簇过滤器
// 过滤出符合条件的列簇下的所有cell
// 这里用这个[a-z]+[0-9]+
public void RegexStringComparatorTest() throws IOException {
// 正则过滤器(String)
RegexStringComparator rsct
= new RegexStringComparator("[a-z]+[0-9]+");
FamilyFilter fft
= new FamilyFilter(CompareFilter.CompareOp.EQUAL, rsct);
// 不同列簇下,每条数据的组成可能不同,通过 result 一个个 get
// 往往会因为那一项为 null,造成空指针异常
// 此时需要使用 listcell 对每一个cell进行提取
printCell(fft);
}
@Test
// SubstringComparator 子字符串比较器
// 没啥好说的
// QualifierFilter 列过滤器,比较列
public void SubStringComparatorTest() throws IOException {
SubstringComparator ssct
= new SubstringComparator("na");
QualifierFilter qfft
= new QualifierFilter(CompareFilter.CompareOp.EQUAL, ssct);
printCell(qfft);
}
@Test
// PageFilter 分页过滤器
// 实际使用类似 scan 中的 limit,也没啥大用
public void PageFilterTest() throws IOException {
PageFilter pft = new PageFilter(20);
print(pft);
}
@Test
// 一页10行数据,试图获取第四页的数据
// 即 31 - 40 行
// 方法一 尝试用pagefilter硬搞
public void pageTest1() throws IOException {
int page = 4;
int pagesize = 10;
int page_first = (page - 1) * pagesize + 1;
PageFilter pft = new PageFilter(page_first);
Scan scan = new Scan();
scan.setFilter(pft);
String rowkey = null;
ResultScanner scanner = students.getScanner(scan);
// 取到第31条的rowkey
for (Result rs : scanner) {
rowkey = Bytes.toString(rs.getRow());
}
// 31条往下读一页就是第四页
Scan scan1 = new Scan();
scan1.withStartRow(rowkey.getBytes());
PageFilter pageFilter2 = new PageFilter(pagesize);
scan1.setFilter(pageFilter2);
ResultScanner scanner2 = students.getScanner(scan1);
for (Result rs : scanner2) {
String id = Bytes.toString(rs.getRow());
String name = Bytes.toString(rs.getValue("cf1".getBytes(), "name".getBytes()));
String age = Bytes.toString(rs.getValue("cf1".getBytes(), "age".getBytes()));
String gender = Bytes.toString(rs.getValue("cf1".getBytes(), "gender".getBytes()));
String clazz = Bytes.toString(rs.getValue("cf1".getBytes(), "clazz".getBytes()));
System.out.println(id + "," + name + "," + age + "," + gender + "," + clazz);
}
}
@Test
// 方法二 scan limit搞
public void pageTest2() throws IOException{
int page = 4;
int pagesize = 10;
int baseId = 1500100000;
int page_first = baseId + (page - 1) * pagesize + 1;
Scan scan = new Scan();
// 关于 int 转字符串快捷方法
scan.withStartRow((page_first + "").getBytes());
scan.setLimit(pagesize);
ResultScanner scanner = students.getScanner(scan);
for (Result rs : scanner) {
String id = Bytes.toString(rs.getRow());
String name = Bytes.toString(
rs.getValue("cf1".getBytes(), "name".getBytes()));
String age = Bytes.toString(
rs.getValue("cf1".getBytes(), "age".getBytes()));
String gender = Bytes.toString(
rs.getValue("cf1".getBytes(), "gender".getBytes()));
String clazz = Bytes.toString(
rs.getValue("cf1".getBytes(), "clazz".getBytes()));
System.out.println(
id + "," + name + "," + age + "," + gender + "," + clazz);
}
}
@Test
// FilterList 多条件过滤
public void FilterList() throws IOException {
FilterList filterList = new FilterList();
SingleColumnValueFilter filter1
= new SingleColumnValueFilter(
"cf1".getBytes(),
"age".getBytes(),
CompareFilter.CompareOp.EQUAL,
"23".getBytes());
SingleColumnValueFilter filter2
= new SingleColumnValueFilter(
"cf1".getBytes(),
"clazz".getBytes(),
CompareFilter.CompareOp.GREATER,
new BinaryPrefixComparator("文科".getBytes()));
SingleColumnValueFilter filter3
= new SingleColumnValueFilter(
"cf1".getBytes(),
"gender".getBytes(),
CompareFilter.CompareOp.EQUAL,
"女".getBytes());
filterList.addFilter(filter1);
filterList.addFilter(filter2);
filterList.addFilter(filter3);
print(filterList);
}
@After
public void close() throws IOException {
admin.close();
conn.close();
students.close();
}
}
关于布隆过滤器
Bloom Filter(布隆过滤器)是1970年由布隆提出,实际上是一个很长的二进制向量和一系列随机映射函数。
布隆过滤器可以用于检索一个元素是否在一个集合中,它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
在计算机科学中,我们常常会碰到时间换空间或者空间换时间的情况,即为了达到某一个方面的最优而牺牲另一个方面。Bloom Filter在时间空间这两个因素之外又引入了另一个因素:错误率。
在判断一个元素是否属于某个集合时,会有一定的错误率(宁杀错,不放过)
有可能把不属于这个集合的元素误认为属于这个集合(False Positive),
但不会把属于这个集合的元素误认为不属于这个集合(False Negative),
增加了错误率这个因素后,Bloom Filter 通过允许少量错误来节省大量的存储空间。
它的用法其实是很容易理解的,我们拿个HBase中应用的例子来说下,我们已经知道rowKey存放在HFile中,那么为了从一系列的HFile中查询某个rowkey,我们就可以通过 Bloom Filter 快速判断 rowkey 是否在这个HFile中,从而过滤掉大部分的HFile,减少需要扫描的Block
二进制向量使用数组来实现,初始状态下位数组每一位都为0
假如此时有一个集合S = {x1, x2, … xn},Bloom Filter使用k个独立的hash函数,分别将集合中的每一个元素映射到{1,…,m}的范围。对于任何一个元素,被映射到的数字作为对应的位数组的索引,该位会被置为1。比如元素x1被hash函数映射到数字8,那么位数组的第8位就会被置为1。
下图中集合S只有两个元素x和y,分别被3个hash函数进行映射,映射到的位置分别为(0,3,6)和(4,7,10),对应的位会被置为1
现在假如要判断另一个元素是否是在此集合中,只需要被这3个hash函数进行映射,查看对应的位置是否有0存在,如果有的话,表示此元素肯定不存在于这个集合,否则有可能存在。下图所示就表示z肯定不在集合{x,y}中
HFile 中和 Bloom Filter 相关的Block
Scanned Block Section(扫描HFile时被读取):
Bloom Block
Load-on-open-section(regionServer启动时加载到内存):
BloomFilter Meta Block、Bloom Index Block
Bloom Block:Bloom数据块,存储Bloom的位数组
Bloom Index Block:Bloom数据块的索引
BloomFilter Meta Block:从HFile角度看bloom数据块的一些元数据信息
HBase中每个HFile都有对应的位数组,KeyValue在写入HFile时会先经过几个hash函数的映射,映射后将对应的数组位改为1,get请求进来之后再进行hash映射,如果在对应数组位上存在0,说明该get请求查询的数据不在该HFile中。
HFile中的Bloom Block中存储的就是上面说得位数组,当HFile很大时,Data Block 就会很多,同时KeyValue也会很多,需要映射入位数组的rowKey也会很多,所以为了保证准确率,位数组就会相应越大,那Bloom Block也会越大,为了解决这个问题就出现了Bloom Index Block,一个HFile中有多个Bloom Block(位数组),根据rowKey拆分,一部分连续的Key使用一个位数组。这样查询rowKey就要先经过Bloom Index Block(在内存中)定位到Bloom Block,再把Bloom Block加载到内存,进行过滤。