HBase 系列(七)——HBase 过滤器详解

释放双眼,带上耳机,听听看~!

一、HBase过滤器简介

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

二、过滤器基础

2.1 Filter接口和FilterBase抽象类

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


1
2
1setFilter(Filter filter)
2

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

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

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

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

2.2 过滤器分类

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

三、比较过滤器

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


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

3.1 比较运算符

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

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


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

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

3.2 比较器

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

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

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

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

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

3.3 比较过滤器种类

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

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

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


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

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

3.4 DependentColumnFilter

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


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

这里举例进行说明:


1
2
3
4
5
6
7
1DependentColumnFilter dependentColumnFilter = new DependentColumnFilter(
2    Bytes.toBytes(&quot;student&quot;),
3    Bytes.toBytes(&quot;name&quot;),
4    false,
5    CompareOperator.EQUAL,
6    new BinaryPrefixComparator(Bytes.toBytes(&quot;xiaolan&quot;)));
7
  • 首先会去查找 student:name 中值以 xiaolan 开头的所有数据获得 参考数据集,这一步等同于 valueFilter 过滤器;

  • 其次再用参考数据集中所有数据的时间戳去检索其他列,获得时间戳相同的其他列的数据作为 结果数据集,这一步等同于时间戳过滤器;

  • 最后如果 dropDependentColumn 为 true,则返回 参考数据集+结果数据集,若为 false,则抛弃参考数据集,只返回 结果数据集。

四、专用过滤器

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

4.1 单列列值过滤器 (SingleColumnValueFilter)

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

  • setFilterIfMissing(boolean filterIfMissing) :默认值为 false,即如果该行数据不包含参考列,其依然被包含在最后的结果中;设置为 true 时,则不包含;

  • setLatestVersionOnly(boolean latestVersionOnly) :默认为 true,即只检索参考列的最新版本数据;设置为 false,则检索所有版本数据。


1
2
3
4
5
6
7
8
1SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
2                &quot;student&quot;.getBytes(),
3                &quot;name&quot;.getBytes(),
4                CompareOperator.EQUAL,
5                new SubstringComparator(&quot;xiaolan&quot;));
6singleColumnValueFilter.setFilterIfMissing(true);
7scan.setFilter(singleColumnValueFilter);
8

4.2 单列列值排除器 (SingleColumnValueExcludeFilter)

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

4.3 行键前缀过滤器 (PrefixFilter)

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


1
2
3
1PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes(&quot;xxx&quot;));
2scan.setFilter(prefixFilter);
3

4.4 列名前缀过滤器 (ColumnPrefixFilter)

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


1
2
3
1ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes(&quot;xxx&quot;));
2 scan.setFilter(columnPrefixFilter);
3

4.5 分页过滤器 (PageFilter)

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


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

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

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

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

4.6 时间戳过滤器 (TimestampsFilter)


1
2
3
4
5
1List&lt;Long&gt; list = new ArrayList&lt;&gt;();
2list.add(1554975573000L);
3TimestampsFilter timestampsFilter = new TimestampsFilter(list);
4scan.setFilter(timestampsFilter);
5

4.7 首次行键过滤器 (FirstKeyOnlyFilter)

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


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

五、包装过滤器

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

5.1 SkipFilter过滤器

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


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

5.2 WhileMatchFilter过滤器

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL,
2                               new BinaryComparator(Bytes.toBytes(&quot;rowKey4&quot;)));
3
4Scan scan = new Scan();
5scan.setFilter(filter1);
6ResultScanner scanner1 = table.getScanner(scan);
7for (Result result : scanner1) {
8    for (Cell cell : result.listCells()) {
9        System.out.println(cell);
10    }
11}
12scanner1.close();
13
14System.out.println(&quot;--------------------&quot;);
15
16// 使用 WhileMatchFilter 进行包装
17Filter filter2 = new WhileMatchFilter(filter1);
18
19scan.setFilter(filter2);
20ResultScanner scanner2 = table.getScanner(scan);
21for (Result result : scanner1) {
22    for (Cell cell : result.listCells()) {
23        System.out.println(cell);
24    }
25}
26scanner2.close();
27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
2rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
3rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
4rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
5rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0
6rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0
7rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0
8rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0
9rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0
10--------------------
11rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
12rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
13rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
14rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
15

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

六、FilterList

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


1
2
3
4
5
6
7
8
9
1// 构造器传入
2public FilterList(final Operator operator, final List&lt;Filter&gt; filters)
3public FilterList(final List&lt;Filter&gt; filters)
4public FilterList(final Filter... filters)
5
6// 方法传入
7 public void addFilter(List&lt;Filter&gt; filters)
8 public void addFilter(Filter filter)
9

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

  • MUST_PASS_ALL :相当于 AND,必须所有的过滤器都通过才认为通过;

  • MUST_PASS_ONE :相当于 OR,只有要一个过滤器通过则认为通过。


1
2
3
4
5
6
7
8
1@InterfaceAudience.Public
2  public enum Operator {
3    /** !AND */
4    MUST_PASS_ALL,
5    /** !OR */
6    MUST_PASS_ONE
7  }
8

使用示例如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1List&lt;Filter&gt; filters = new ArrayList&lt;Filter&gt;();
2
3Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL,
4                               new BinaryComparator(Bytes.toBytes(&quot;XXX&quot;)));
5filters.add(filter1);
6
7Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL,
8                               new BinaryComparator(Bytes.toBytes(&quot;YYY&quot;)));
9filters.add(filter2);
10
11Filter filter3 = new QualifierFilter(CompareOperator.EQUAL,
12                                     new RegexStringComparator(&quot;ZZZ&quot;));
13filters.add(filter3);
14
15FilterList filterList = new FilterList(filters);
16
17Scan scan = new Scan();
18scan.setFilter(filterList);
19

参考资料

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

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

转载于:https://www.cnblogs.com/heibaiying/p/11410504.html

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索