BigData-Notes/notes/Hbase过滤器详解.md
2019-04-12 12:18:22 +08:00

18 KiB
Raw Blame History

Hbase 过滤器详解

一、HBase过滤器简介
二、过滤器基础
        2.1 Filter接口和FilterBase抽象类
        2.2 过滤器分类
三、比较过滤器
        3.1 比较运算符
        3.2 比较器
        3.3 比较过滤器种类
        3.4 DependentColumnFilter
四、专用过滤器
        4.1 单列列值过滤器 (SingleColumnValueFilter)
        4.2 单列列值排除器 (SingleColumnValueExcludeFilter)
        4.3 行键前缀过滤器 (PrefixFilter)
        4.4 列名前缀过滤器 (ColumnPrefixFilter)
        4.5 分页过滤器 (PageFilter)
        4.6 时间戳过滤器 (TimestampsFilter)
        4.7 首次行键过滤器 (FirstKeyOnlyFilter)
五、包装过滤器
        5.1 SkipFilter过滤器
        5.2 WhileMatchFilter过滤器
六、FilterList

一、HBase过滤器简介

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

二、过滤器基础

2.1 Filter接口和FilterBase抽象类

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

setFilter(Filter filter)
 // Scan 中定义的setFilter
 @Override
  public Scan setFilter(Filter filter) {
    super.setFilter(filter);
    return this;
  }
  // Get 中定义的setFilter
 @Override
  public Get setFilter(Filter filter) {
    super.setFilter(filter);
    return this;
  }

FilterBase的所有子类过滤器如下

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

2.2 过滤器分类

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

三、比较过滤器

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

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

3.1 比较运算符

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

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

@InterfaceAudience.Public
public enum CompareOperator {
  LESS,
  LESS_OR_EQUAL,
  EQUAL,
  NOT_EQUAL,
  GREATER_OR_EQUAL,
  GREATER,
  NO_OP,
}

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

所以1.x 版本的比较运算符需要使用CompareFilter.CompareOp枚举类, 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不会参与比较,这时候认为abcdabcdefgh 是满足EQUAL条件的;
  • 如果使用的是BinaryComparator比较器,则认为其是不相等的。

3.3 比较过滤器种类

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

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

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

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

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

3.4 DependentColumnFilter

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

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

这里举例进行说明:

DependentColumnFilter dependentColumnFilter = new DependentColumnFilter( 
    Bytes.toBytes("student"),
    Bytes.toBytes("name"),
    false,
    CompareOperator.EQUAL, 
    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则检索所有版本数据。
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
                "student".getBytes(), 
                "name".getBytes(), 
                CompareOperator.EQUAL, 
                new SubstringComparator("xiaolan"));
singleColumnValueFilter.setFilterIfMissing(true);
scan.setFilter(singleColumnValueFilter);

4.2 单列列值排除器 (SingleColumnValueExcludeFilter)

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

4.3 行键前缀过滤器 (PrefixFilter)

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

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

4.4 列名前缀过滤器 (ColumnPrefixFilter)

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

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

4.5 分页过滤器 (PageFilter)

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

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

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

客户端进行分页查询,需要传递startRow(起始RowKey),知道起始startRow就可以返回对应的pageSize行数据。这里唯一的问题就是对于第一次查询显然startRow就是表格的第一行数据,但是之后第二次、第三次查询我们并不知道startRow只能知道上一次查询的最后一条数据的RowKey简单称之为lastRow)。

我们不能将lastRow作为新一次查询的startRow传入因为scan的查询区间是[startRowendRow) ,即前开后闭区间,这样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'

分页查询主要实现逻辑:

byte[] POSTFIX = new byte[] { 0x00 };
Filter filter = new PageFilter(15);

int totalRows = 0;
byte[] lastRow = null;
while (true) {
    Scan scan = new Scan();
    scan.setFilter(filter);
    if (lastRow != null) {
        // 如果不是首行 则lastRow + 0
        byte[] startRow = Bytes.add(lastRow, POSTFIX);
        System.out.println("start row: " +
                           Bytes.toStringBinary(startRow));
        scan.withStartRow(startRow);
    }
    ResultScanner scanner = table.getScanner(scan);
    int localRows = 0;
    Result result;
    while ((result = scanner.next()) != null) {
        System.out.println(localRows++ + ": " + result);
        totalRows++;
        lastRow = result.getRow();
    }
    scanner.close();
    //最后一页,查询结束  
    if (localRows == 0) break;
}
System.out.println("total rows: " + totalRows);

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

4.6 时间戳过滤器 (TimestampsFilter)

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

4.7 首次行键过滤器 (FirstKeyOnlyFilter)

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

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

五、包装过滤器

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

5.1 SkipFilter过滤器

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

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

5.2 WhileMatchFilter过滤器

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

Filter filter1 = new RowFilter(CompareOperator.NOT_EQUAL,
                               new BinaryComparator(Bytes.toBytes("rowKey4")));

Scan scan = new Scan();
scan.setFilter(filter1);
ResultScanner scanner1 = table.getScanner(scan);
for (Result result : scanner1) {
    for (Cell cell : result.listCells()) {
        System.out.println(cell);
    }
}
scanner1.close();

System.out.println("--------------------");

// 使用WhileMatchFilter进行包装
Filter filter2 = new WhileMatchFilter(filter1);

scan.setFilter(filter2);
ResultScanner scanner2 = table.getScanner(scan);
for (Result result : scanner1) {
    for (Cell cell : result.listCells()) {
        System.out.println(cell);
    }
}
scanner2.close();
rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0
rowKey5/student:name/1555035007051/Put/vlen=8/seqid=0
rowKey6/student:name/1555035007057/Put/vlen=8/seqid=0
rowKey7/student:name/1555035007062/Put/vlen=8/seqid=0
rowKey8/student:name/1555035007068/Put/vlen=8/seqid=0
rowKey9/student:name/1555035007073/Put/vlen=8/seqid=0
--------------------
rowKey0/student:name/1555035006994/Put/vlen=8/seqid=0
rowKey1/student:name/1555035007019/Put/vlen=8/seqid=0
rowKey2/student:name/1555035007025/Put/vlen=8/seqid=0
rowKey3/student:name/1555035007037/Put/vlen=8/seqid=0

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

六、FilterList

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

// 构造器传入
public FilterList(final Operator operator, final List<Filter> filters)
public FilterList(final List<Filter> filters)
public FilterList(final Filter... filters)

// 方法传入
 public void addFilter(List<Filter> filters)
 public void addFilter(Filter filter)

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

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

使用示例如下:

List<Filter> filters = new ArrayList<Filter>();

Filter filter1 = new RowFilter(CompareOperator.GREATER_OR_EQUAL,
                               new BinaryComparator(Bytes.toBytes("XXX")));
filters.add(filter1);

Filter filter2 = new RowFilter(CompareOperator.LESS_OR_EQUAL,
                               new BinaryComparator(Bytes.toBytes("YYY")));
filters.add(filter2);

Filter filter3 = new QualifierFilter(CompareOperator.EQUAL,
                                     new RegexStringComparator("ZZZ"));
filters.add(filter3);

FilterList filterList = new FilterList(filters);

Scan scan = new Scan();
scan.setFilter(filterList);

参考资料

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