一、HBase过滤器简介

Hbase提供了种类丰富的过滤器(filter)来提高数据处理的效率,用户可以通过内置或自定义的过滤器来对数据进行过滤,所有的过滤器都在服务端生效,即谓词下推(predicate push down)。这样可以保证过滤掉的数据不会被传送到客户端,从而减轻网络传输和客户端处理的压力。

二、过滤器基础

2.1 Filter接口和FilterBase抽象类

Filter接口中定义了过滤器的基本方法,FilterBase抽象类实现了Filter接口。所有内置的过滤器则直接或者间接继承自FilterBase抽象类。用户只需要将定义好的过滤器通过setFilter方法传递给Scanput的实例即可。

  1. setFilter(Filter filter)
  1. // Scan 中定义的setFilter
  2. @Override
  3. public Scan setFilter(Filter filter) {
  4. super.setFilter(filter);
  5. return this;
  6. }
  1. // Get 中定义的setFilter
  2. @Override
  3. public Get setFilter(Filter filter) {
  4. super.setFilter(filter);
  5. return this;
  6. }

FilterBase的所有子类过滤器如下:

说明:上图基于当前时间点(2019.4)最新的Hbase-2.1.4 ,下文所有说明均基于此版本。

2.2 过滤器分类

HBase 内置过滤器可以分为三类:分别是比较过滤器,专用过滤器和包装过滤器。分别在下面的三个小节中做详细的介绍。

三、比较过滤器

所有比较过滤器均继承自CompareFilter。创建一个比较过滤器需要两个参数,分别是比较运算符比较器实例

  1. public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) {
  2. this.compareOp = compareOp;
  3. this.comparator = comparator;
  4. }

3.1 比较运算符

  • LESS (<)
  • LESS_OR_EQUAL (<=)
  • EQUAL (=)
  • NOT_EQUAL (!=)
  • GREATER_OR_EQUAL (>=)
  • GREATER (>)
  • NO_OP (排除所有符合条件的值)

比较运算符均定义在枚举类CompareOperator

  1. @InterfaceAudience.Public
  2. public enum CompareOperator {
  3. LESS,
  4. LESS_OR_EQUAL,
  5. EQUAL,
  6. NOT_EQUAL,
  7. GREATER_OR_EQUAL,
  8. GREATER,
  9. NO_OP,
  10. }

注意:在 1.x 版本的HBase中,比较运算符定义在CompareFilter.CompareOp枚举类中,但在2.0之后这个类就被标识为 @deprecated ,并会在3.0移除。所以2.0之后版本的HBase需要使用 CompareOperator这个枚举类。

3.2 比较器

所有比较器均继承自ByteArrayComparable抽象类,常用的有以下几种:

  • BinaryComparator : 使用Bytes.compareTo(byte [],byte [])按字典序比较指定的字节数组。
  • BinaryPrefixComparator : 按字典序与指定的字节数组进行比较,但只比较到这个字节数组的长度。
  • RegexStringComparator : 使用给定的正则表达式与指定的字节数组进行比较。仅支持EQUALNOT_EQUAL操作。
  • SubStringComparator : 测试给定的子字符串是否出现在指定的字节数组中,比较不区分大小写。仅支持EQUALNOT_EQUAL操作。
  • NullComparator :判断给定的值是否为空。
  • BitComparator :按位进行比较。

BinaryPrefixComparatorBinaryComparator的区别不是很好理解,这里举例说明一下:

在进行EQUAL的比较时,如果比较器传入的是abcd的字节数组,但是待比较数据是abcdefgh

  • 如果使用的是BinaryPrefixComparator比较器,则比较以abcd字节数组的长度为准,即efgh不会参与比较,这时候认为abcdabcdefgh 是满足EQUAL条件的;
  • 如果使用的是BinaryComparator比较器,则认为其是不相等的。

3.3 比较过滤器种类

比较过滤器共有五个(Hbase 1.x 版本和2.x 版本相同),见下图:

  • RowFilter :基于行键来过滤数据;
  • FamilyFilterr :基于列族来过滤数据;
  • QualifierFilterr :基于列限定符(列名)来过滤数据;
  • ValueFilterr :基于单元格(cell) 的值来过滤数据;
  • DependentColumnFilter :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。

前四种过滤器的使用方法相同,均只要传递比较运算符和运算器实例即可构建,然后通过setFilter方法传递给scan

  1. Filter filter = new RowFilter(CompareOperator.LESS_OR_EQUAL,
  2. new BinaryComparator(Bytes.toBytes("xxx")));
  3. scan.setFilter(filter);

DependentColumnFilter的使用稍微复杂一点,这里单独做下说明。

3.4 DependentColumnFilter

可以把DependentColumnFilter理解为一个valueFilter和一个时间戳过滤器的组合DependentColumnFilter有三个带参构造器,这里选择一个参数最全的进行说明:

  1. DependentColumnFilter(final byte [] family, final byte[] qualifier,
  2. final boolean dropDependentColumn, final CompareOperator op,
  3. final ByteArrayComparable valueComparator)
  • family :列族
  • qualifier :列限定符(列名)
  • dropDependentColumn :决定参考列是否被包含在返回结果内,为true时表示参考列被返回,为false时表示被丢弃
  • op :比较运算符
  • valueComparator :比较器

这里举例进行说明:

  1. DependentColumnFilter dependentColumnFilter = new DependentColumnFilter(
  2. Bytes.toBytes("student"),
  3. Bytes.toBytes("name"),
  4. false,
  5. CompareOperator.EQUAL,
  6. new BinaryPrefixComparator(Bytes.toBytes("xiaolan")));
  • 首先会去查找student:name中值以xiaolan开头的所有数据获得参考数据集,这一步等同于valueFilter过滤器;
  • 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为结果数据集,这一步等同于时间戳过滤器;
  • 最后如果dropDependentColumn为true,则返回参考数据集+结果数据集,若为false,则抛弃参考数据集,只返回结果数据集

四、专用过滤器

专用过滤器通常直接继承自FilterBase,适用于范围更小的筛选规则。

4.1 单列列值过滤器 (SingleColumnValueFilter)

基于某列(参考列)的值决定某行数据是否被过滤。其实例有以下方法:

  • setFilterIfMissing(boolean filterIfMissing) :默认值为false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为true时,则不包含;
  • setLatestVersionOnly(boolean latestVersionOnly) :默认为true,即只检索参考列的最新版本数据;设置为false,则检索所有版本数据。
  1. SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
  2. "student".getBytes(),
  3. "name".getBytes(),
  4. CompareOperator.EQUAL,
  5. new SubstringComparator("xiaolan"));
  6. singleColumnValueFilter.setFilterIfMissing(true);
  7. scan.setFilter(singleColumnValueFilter);

4.2 单列列值排除器 (SingleColumnValueExcludeFilter)

SingleColumnValueExcludeFilter继承自上面的SingleColumnValueFilter,过滤行为与其相反。

4.3 行键前缀过滤器 (PrefixFilter)

基于RowKey值决定某行数据是否被过滤。

  1. PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("xxx"));
  2. scan.setFilter(prefixFilter);

4.4 列名前缀过滤器 (ColumnPrefixFilter)

基于列限定符(列名)决定某行数据是否被过滤。

  1. ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("xxx"));
  2. scan.setFilter(columnPrefixFilter);

4.5 分页过滤器 (PageFilter)

可以使用这个过滤器实现对结果按行进行分页,创建PageFilter实例的时候需要传入每页的行数。

  1. public PageFilter(final long pageSize) {
  2. Preconditions.checkArgument(pageSize >= 0, "must be positive %s", pageSize);
  3. this.pageSize = pageSize;
  4. }

下面的代码体现了客户端实现分页查询的主要逻辑,这里对其进行一下解释说明:

客户端进行分页查询,需要传递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'

分页查询主要实现逻辑:

  1. byte[] POSTFIX = new byte[] { 0x00 };
  2. Filter filter = new PageFilter(15);
  3. int totalRows = 0;
  4. byte[] lastRow = null;
  5. while (true) {
  6. Scan scan = new Scan();
  7. scan.setFilter(filter);
  8. if (lastRow != null) {
  9. // 如果不是首行 则lastRow + 0
  10. byte[] startRow = Bytes.add(lastRow, POSTFIX);
  11. System.out.println("start row: " +
  12. Bytes.toStringBinary(startRow));
  13. scan.withStartRow(startRow);
  14. }
  15. ResultScanner scanner = table.getScanner(scan);
  16. int localRows = 0;
  17. Result result;
  18. while ((result = scanner.next()) != null) {
  19. System.out.println(localRows++ + ": " + result);
  20. totalRows++;
  21. lastRow = result.getRow();
  22. }
  23. scanner.close();
  24. //最后一页,查询结束
  25. if (localRows == 0) break;
  26. }
  27. System.out.println("total rows: " + totalRows);

需要注意的是在多台Regin Services上执行分页过滤的时候,由于并行执行的过滤器不能共享它们的状态和边界,所以有可能每个过滤器都会在完成扫描前获取了PageCount行的结果,这种情况下会返回比分页条数更多的数据,分页过滤器就有失效的可能。

4.6 时间戳过滤器 (TimestampsFilter)

  1. List<Long> list = new ArrayList<>();
  2. list.add(1554975573000L);
  3. TimestampsFilter timestampsFilter = new TimestampsFilter(list);
  4. scan.setFilter(timestampsFilter);

4.7 首次行键过滤器 (FirstKeyOnlyFilter)

FirstKeyOnlyFilter只扫描每行的第一列,扫描完第一列后就结束对当前行的扫描,并跳转到下一行。相比于全表扫描,其性能更好,通常用于行数统计的场景,因为如果某一行存在,则行中必然至少有一列。

  1. FirstKeyOnlyFilter firstKeyOnlyFilter = new FirstKeyOnlyFilter();
  2. scan.set(firstKeyOnlyFilter);

五、包装过滤器

包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。

5.1 SkipFilter过滤器

SkipFilter包装一个过滤器,当被包装的过滤器遇到一个需要过滤的KeyValue实例时,则拓展过滤整行数据。下面是一个使用示例:

  1. // 定义ValueFilter过滤器
  2. Filter filter1 = new ValueFilter(CompareOperator.NOT_EQUAL,
  3. new BinaryComparator(Bytes.toBytes("xxx")));
  4. // 使用SkipFilter进行包装
  5. Filter filter2 = new SkipFilter(filter1);

5.2 WhileMatchFilter过滤器

WhileMatchFilter包装一个过滤器,当被包装的过滤器遇到一个需要过滤的KeyValue实例时,WhileMatchFilter则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:

  1. Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL,
  2. new BinaryComparator(Bytes.toBytes("rowKey4")));
  3. Scan scan = new Scan();
  4. scan.setFilter(filter1);
  5. ResultScanner scanner1 = table.getScanner(scan);
  6. for (Result result : scanner1) {
  7. for (Cell cell : result.listCells()) {
  8. System.out.println(cell);
  9. }
  10. }
  11. scanner1.close();
  12. System.out.println("--------------------");
  13. // 使用WhileMatchFilter进行包装
  14. Filter filter2 = new WhileMatchFilter(filter1);
  15. scan.setFilter(filter2);
  16. ResultScanner scanner2 = table.getScanner(scan);
  17. for (Result result : scanner1) {
  18. for (Cell cell : result.listCells()) {
  19. System.out.println(cell);
  20. }
  21. }
  22. scanner2.close();
  1. rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
  2. rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
  3. rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
  4. rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
  5. rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0
  6. rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0
  7. rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0
  8. rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0
  9. rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0
  10. --------------------
  11. rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
  12. rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
  13. rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
  14. rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0

可以看到被包装后,只返回了rowKey4之前的数据。

六、FilterList

以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用FilterListFilterList支持通过构造器或者addFilter方法传入多个过滤器。

  1. // 构造器传入
  2. public FilterList(final Operator operator, final List<Filter> filters)
  3. public FilterList(final List<Filter> filters)
  4. public FilterList(final Filter... filters)
  5. // 方法传入
  6. public void addFilter(List<Filter> filters)
  7. public void addFilter(Filter filter)

多个过滤器组合的结果由operator参数定义 ,其可选参数定义在Operator枚举类中。只有MUST_PASS_ALLMUST_PASS_ONE两个可选的值:

  • MUST_PASS_ALL :相当于AND,必须所有的过滤器都通过才认为通过;
  • MUST_PASS_ONE :相当于OR,只有要一个过滤器通过则认为通过。
  1. @InterfaceAudience.Public
  2. public enum Operator {
  3. /** !AND */
  4. MUST_PASS_ALL,
  5. /** !OR */
  6. MUST_PASS_ONE
  7. }

使用示例如下:

  1. List<Filter> filters = new ArrayList<Filter>();
  2. Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL,
  3. new BinaryComparator(Bytes.toBytes("XXX")));
  4. filters.add(filter1);
  5. Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL,
  6. new BinaryComparator(Bytes.toBytes("YYY")));
  7. filters.add(filter2);
  8. Filter filter3 = new QualifierFilter(CompareOperator.EQUAL,
  9. new RegexStringComparator("ZZZ"));
  10. filters.add(filter3);
  11. FilterList filterList = new FilterList(filters);
  12. Scan scan = new Scan();
  13. scan.setFilter(filterList);

参考资料

HBase: The Definitive Guide _> Chapter 4. Client API: Advanced Features

更多大数据系列文章可以参见个人 GitHub 开源项目: 程序员大数据入门指南

HBase 学习之路(七)——HBase过滤器详解的更多相关文章

  1. go语言学习之路六:接口详解

    Go语言没有类和继承的概念,但是接口的存在使得它可以实现很多面向对象的特性.接口定义了一些方法,但是这些方法不包含实现的代码.也就是说这些代码没有被实现(抽象的方法).同时接口里面也不包含变量. 看一 ...

  2. Asp.Net MVC学习总结之过滤器详解(转载)

    来源:http://www.php.cn/csharp-article-359736.html   一.过滤器简介 1.1.理解什么是过滤器 1.过滤器(Filters)就是向请求处理管道中注入额外的 ...

  3. ISO七层模型详解

    ISO七层模型详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在我刚刚接触运维这个行业的时候,去面试时总是会做一些面试题,笔试题就是看一个运维工程师的专业技能的掌握情况,这个很 ...

  4. Scala进阶之路-Scala函数篇详解

    Scala进阶之路-Scala函数篇详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.传值调用和传名调用 /* @author :yinzhengjie Blog:http: ...

  5. MVC过滤器详解

    MVC过滤器详解   APS.NET MVC中(以下简称"MVC")的每一个请求,都会分配给相应的控制器和对应的行为方法去处理,而在这些处理的前前后后如果想再加一些额外的逻辑处理. ...

  6. ASP.NET MVC 5 学习教程:生成的代码详解

    原文 ASP.NET MVC 5 学习教程:生成的代码详解 起飞网 ASP.NET MVC 5 学习教程目录: 添加控制器 添加视图 修改视图和布局页 控制器传递数据给视图 添加模型 创建连接字符串 ...

  7. IP地址和子网划分学习笔记之《IP地址详解》

    2018-05-03 18:47:37   在学习IP地址和子网划分前,必须对进制计数有一定了解,尤其是二进制和十进制之间的相互转换,对于我们掌握IP地址和子网的划分非常有帮助,可参看如下目录详文. ...

  8. OpenCV学习C++接口 Mat像素遍历详解

    OpenCV学习C++接口 Mat像素遍历详解

  9. 零拷贝详解 Java NIO学习笔记四(零拷贝详解)

    转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...

  10. UWP入门(七)--SplitView详解与页面跳转

    原文:UWP入门(七)--SplitView详解与页面跳转 官方文档,逼着自己用英文看,UWP开发离不开官方文档 1. SplitView 拆分视图控件 拆分视图控件具有一个可展开/可折叠的窗格和一个 ...

随机推荐

  1. 陈硕 - Linux 多线程服务端编程 - muduo 网络库作者

    http://chenshuo.com/book/ Muduo网络库源码分析(一) EventLoop事件循环(Poller和Channel)http://blog.csdn.net/nk_test/ ...

  2. 【003】【Java虚拟机——对象死亡的判断】

    对象死亡! 垃圾收集器在对堆进行回收前,首先要做的事情就是要确定这些对象之中哪些还"存活"着, 哪些已经"死去" (即不可能再被不论什么途径使用的对象). 1)  引用计 ...

  3. C#常用多线程方法

    1.  Thread类 C#多线程编程中Thread类需要包含名称空间System.Threading. class Program { static void Main(string[] args) ...

  4. Expression.Blend.4 Chapter 图片和视频的使用

    原文:Expression.Blend.4 Chapter 图片和视频的使用 翻译的地方可能有错误,欢迎大家指正.但是里面每一个程序都是亲自测试过,并加了点自己的看法. 我翻译的是Expression ...

  5. python 教程 第四章、 控制流

    第四章. 控制流 控制语句后面要加冒号: 1)    if语句 if guess == number: print 'Congratulations, you guessed it.' # New b ...

  6. sqlite 初

    1.SQLite是什么 基于文件的轻型数据库 无服务器  零配置  支持事务  开源 2.SQLite 怎么用   2.1 安装 SQLite官网上下载对应的DLL 与工具 配置环境变量 安装完成以后 ...

  7. DirectX 图形流水线

    Direct3D 的可编程流水线用来为实时游戏渲染图形(一个词概括——实时渲染) 上面的图是Dx11的实时渲染流水线,Dx的几个版本都是向下兼容的. Input-Assembler Stage: 输入 ...

  8. JS 数组两种定义方式

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  9. C# WebClient的使用

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  10. 图像滤镜艺术---流行艺术风滤镜特效PS实现

    原文:图像滤镜艺术---流行艺术风滤镜特效PS实现 今天,本人给大家介绍一款新滤镜:流行艺术风效果,先看下效果吧! 原图 流行艺术风效果图 上面的这款滤镜效果是不是很赞,呵呵,按照本人以往的逻辑,我会 ...