【HBase】HBase 过滤器

[TOC]

一、Filter 介绍

一般来说调整表设计就可以优化访问模式。但是有时已经把表设计调整得尽可能好了,为不同访问模式优化得尽可能好了。当仍然需要减少返回客户端的数据时,这就是考虑使用过滤器的时候了。

过滤器也被称为下推判断器(push-down predicates),支持把数据过滤标准从客户端下推到服务器,带有 Filter 条件的 RPC 查询请求会把 Filter 分发到各个 RegionServer,所有的过滤器都在服务端生效,使被过滤掉的数据不会被传送到客户端,这些过滤逻辑在读操作时使用,对返回客户端的数据有影响。这样可以降低网络传输的压力。

Filter 可以根据 簇、列、版本等更多的条件来对数据进行过滤,基于 HBase 本身提供的三维有序(主键有序、列有序、版本有序),这些 Filter 可以高效的完成查询过滤的任务。

HBase Filter 具有以下特点:

  • Filter 是在 HBase 服务器端上执行判断操作;
  • Filter 可以应用到行键(RowFilter),列限定符(QualifierFilter)或者数据值(ValueFilter);
  • Filter 允许对数据分页处理(PageFilter),限制扫描器返回行数;
  • FilterList 可以组合使用多个Filter。

二、Filter 方法执行顺序及含义

在 Filter 的源码中定义了 Filter 各方法的含义和执行顺序:

A filter can expect the following call sequence:

  • reset() : reset the filter state before filtering a new row.
  • filterAllRemaining(): true means row scan is over; false means keep going.
  • filterRowKey(Cell): true means drop this row; false means include.(常用)
  • filterCell(Cell): decides whether to include or exclude this Cell.(常用)
  • transformCell(Cell): if the Cell is included, let the filter transform the Cell.(基本不用)
  • filterRowCells(List):allows direct modification of the final list to be submitted.提交前,修改要被过滤的 Cell 的机会。
  • filterRow():last chance to drop entire row based on the sequence of filter calls. Eg: filter a row if it doesn't contain a specified column.丢掉整行的最后机会。

过滤器执行流程大致如下:

image
  1. 调用 filterRowKey 方法,进行行健级别的过滤,如果该行需要过滤,返回 true,不需要过滤返回 false;
  2. 如果该行没有被 filterRowKey 过滤掉,接着调用 filterCell 方法筛选该行的每一个 Cell,这个方法返回一个 ReturnCode,返回的 ReturnCode 用于判断该 Cell 将要发生什么;
  3. 在第 2 步过滤 Cell 对象后,filterRowCells 方法将处理被过滤的 Cell 列表,可以在该方法中对被过滤的 Cell 做一些转换或运算;
  4. 再完成前面的动作后,filterRow 提供了最后的机会用于选择是否过滤该行;
  5. filterAllRemaining 可以构造逻辑来提早停止一次扫描,例如在很多行、列中找到需要的值后,不再关心剩余的行,此方法将很有益处。
image

三、自定义 Filter

自定义一个查找密码长度小于指定值的过滤器,如下:

public class PasswordStrengthFilter extends FilterBase {

    private int len;
    private boolean filterRow = false;

    // This flag is used to speed up seeking cells when matched column is found, such that following
    // columns in the same row can be skipped faster by NEXT_ROW instead of NEXT_COL.
    private boolean columnFound = false;
    
    public PasswordStrengthFilter(int len) {
        this.len = len;
    }

    @Override
    public ReturnCode filterCell(final Cell c) throws IOException {
        if (!CellUtil.matchingColumn(c, UsersDAO.INFO_FAM, UsersDAO.PASS_COL)) {
            return columnFound ? ReturnCode.NEXT_ROW : ReturnCode.NEXT_COL;
        }
        // Column found
        columnFound = true;
        if(c.getValueLength() >= len) {
            this.filterRow = true;
            return ReturnCode.SKIP;
        }
        return ReturnCode.INCLUDE;
    }

    @Override
    public boolean filterRow() {
        return this.filterRow;
    }

    @Override
    public void reset() {
        this.filterRow = false;
        columnFound = false;
    }

}

四、Filter 的实现

Filter 和 FilterList 作为一个通用的数据过滤框架,提供了一系列的接口,供用户来实现自定义的 Filter。当然,HBase 本身也提供了一系列的内置 Filter,例如:PrefixFilter、RowFilter、FamilyFilter、QualifierFilter、ValueFilter、ColumnPrefixFilter 等。

事实上,很多 Filter 都没有必要在服务端从 Scan 的 startRow 一直扫描到 endRow,中间有很多数据是可以根据 Filter 具体的语义直接跳过,通过减少磁盘 IO 和比较次数来实现更高的性能的。以 PrefixFilter(“333”) 为例,需要返回的是RowKey 以 “333” 为前缀的数据。

image

实际的扫描流程如图所示:

  1. 碰到 RowKey=111 的行时,发现 111 比前缀 333 小,因此直接跳过 111 这一行去扫下一行,返回状态码 NEXT_ROW;
  2. 下一个 Cell 的 RowKey =222,仍然比前缀 333 小,因此继续扫下一行,返回状态 NEXT_ROW;
  3. 下一个 Cell 的 RowKey=333,前缀和 333 匹配,返回 column=f:ddd 这个 Cell 给用户,返回状态码为 INCLUDE;
  4. 下一个 Cell 的 RowKey 仍为 333,前缀和 333 匹配,返回 column=f:eee 这个 Cell 给用户,返回状态码为 INCLUDE;
  5. 下一个 Cell 的 RowKey 为 444,前缀比 333 大,不再继续扫描数据。

这个流程中,每碰到一个 Cell,返回的状态码 NEXT_ROW、INCLUDE 等,就告诉了RegionServer 扫描框架下一个 Cell 的位置。例如在第 2 步中,返回状态码 NEXT_ROW,那么下一个 Cell 的 RowKey 必须是比 222 大的,于是就跳过了 column=f:ccc 这个 Cell,直接定位到了 RowKey=333 的行,继续扫描数据。

在 Filter 中引入了 7 种状态码,见如下:

  public enum ReturnCode {
    /**
     * Include the Cell
     */
    INCLUDE,
    /**
     * Include the Cell and seek to the next column skipping older versions.
     */
    INCLUDE_AND_NEXT_COL,
    /**
     * Skip this Cell
     */
    SKIP,
    /**
     * Skip this column. Go to the next column in this row.
     */
    NEXT_COL,
    /**
     * Seek to next row in current family. It may still pass a cell whose family is different but
     * row is the same as previous cell to {@link #filterCell(Cell)} , even if we get a NEXT_ROW
     * returned for previous cell. For more details see HBASE-18368. <br>
     * Once reset() method was invoked, then we switch to the next row for all family, and you can
     * catch the event by invoking CellUtils.matchingRows(previousCell, currentCell). <br>
     * Note that filterRow() will still be called. <br>
     */
    NEXT_ROW,
    /**
     * Seek to next key which is given as hint by the filter.
     */
    SEEK_NEXT_USING_HINT,
    /**
     * Include KeyValue and done with row, seek to next. See NEXT_ROW.
     */
    INCLUDE_AND_SEEK_NEXT_ROW,
}

当 ReturnCode 为 SEEK_NEXT_USING_HINT 时,需要在 Filter 的 getNextCellHint() 方法中告诉具体的 Cell。

五、HBase 内置 Filter

要完成一个过滤的操作,需要两个参数。

一个是抽象的操作符,Hbase提供了枚举类型的变量来表示这些抽象的操作符:LESS/LESS_OR_EQUAL/EQUAL/NOT_EUQAL 等;

另一个是具体的比较器(Comparator),代表具体的比较逻辑,比如字节级的比较、字符串级的比较等。

有了这两个参数,就可以清晰的定义筛选的条件,过滤数据。

抽象操作符(比较运算符)

public enum CompareOperator {
  // Keeps same names as the enums over in filter's CompareOp intentionally.
  // The convertion of operator to protobuf representation is via a name comparison.
  /** less than */
  LESS,
  /** less than or equal to */
  LESS_OR_EQUAL,
  /** equals */
  EQUAL,
  /** not equal */
  NOT_EQUAL,
  /** greater than or equal to */
  GREATER_OR_EQUAL,
  /** greater than */
  GREATER,
  /** no operation */
  NO_OP,
}

比较器(指定比较机制)

  • BinaryComparator 按字节索引顺序比较指定字节数组,采用 Bytes.compareTo(byte[])
  • BinaryPrefixComparator 跟前面相同,只是比较左端的数据是否相同
  • NullComparator 判断给定的是否为空
  • BitComparator 按位比较
  • RegexStringComparator 提供一个正则的比较器,仅支持 EQUAL 和非 EQUAL
  • SubstringComparator 判断提供的子串是否出现在 value 中

HBase Filter 的可分为比较过滤器和专用过滤器

5.1、比较过滤器

行键过滤器 RowFilter

Filter rowFilter = new RowFilter(CompareOp.GREATER, new BinaryComparator("95007".getBytes()));
scan.setFilter(rowFilter);

筛选出匹配的所有的行,基于行键(RowKey)过滤数据,可以执行精确匹配,子字符串匹配或正则表达式匹配,过滤掉不匹配的数据。

一般来说,对 RowKey进行范围过滤,可以执行 Scan 的 startKey 和 endKey,RowFilter 可以更精确的过滤。

列簇过滤器 FamilyFilter

Filter familyFilter = new FamilyFilter(CompareOp.EQUAL, new BinaryComparator("info".getBytes()));
scan.setFilter(familyFilter);

与 RowFilter 类似,区别是比较列族,而不是比较行键。当 HBase 表有多个列族时,可以用来筛选不同列族中的列。

列过滤器 QualifierFilter

Filter qualifierFilter = new QualifierFilter(CompareOp.EQUAL, new BinaryComparator("name".getBytes()));
scan.setFilter(qualifierFilter);

根据列名进行筛选。

值过滤器 ValueFilter

Filter valueFilter = new ValueFilter(CompareOp.EQUAL, new SubstringComparator("男"));
scan.setFilter(valueFilter);

筛选特定值的单元格,可以与 RegexStringComparator 搭配使用,完成复杂的筛选。

不同的比较器,只能与部分比较运算符搭配,例如 SubstringComparator 只能使用 EQUALNOT_EQUAL

时间戳过滤器 TimestampsFilter

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

当需要在扫描结果中对版本进行细粒度控制时,可以使用这个 Filter 传入一个时间戳集合,对时间进行限制,只会返回与指定时间戳相同的版本数据,并且与设置时间戳范围共同使用。

5.2、专用过滤器

单列值过滤器 SingleColumnValueFilter ----返回满足条件的整行

SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
                "info".getBytes(), //列簇
                "name".getBytes(), //列
                CompareOp.EQUAL, 
                new SubstringComparator("刘晨"));
//如果不设置为 true,则那些不包含指定 column 的行也会返回
singleColumnValueFilter.setFilterIfMissing(true);
scan.setFilter(singleColumnValueFilter);

使用某一列的值,决定一行数据是否被过滤。

对于不包含指定列的行数据,通过 setFilterIfMissing() 决定是否返回。

单列值排除器 SingleColumnValueExcludeFilter

SingleColumnValueExcludeFilter singleColumnValueExcludeFilter = new SingleColumnValueExcludeFilter(
                "info".getBytes(), 
                "name".getBytes(), 
                CompareOp.EQUAL, 
                new SubstringComparator("刘晨"));
singleColumnValueExcludeFilter.setFilterIfMissing(true);
        
scan.setFilter(singleColumnValueExcludeFilter);

继承自 SingleColumnValueFilter,实现的与单列值过滤器相反的语义。

前缀过滤器 PrefixFilter----针对行键

PrefixFilter prefixFilter = new PrefixFilter("9501".getBytes());
        
scan.setFilter(prefixFilter);

基于行键(Rowkey)的前缀过滤行数据。Scan 操作以字典序查找,当行键大于前缀时,Scan 结束。

列前缀过滤器 ColumnPrefixFilter

ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter("name".getBytes());
        
scan.setFilter(columnPrefixFilter);

通过对列名称的前缀匹配过滤,返回的结果只包含满足过滤器的列。

分页过滤器 PageFilter

byte[] lastRow = null; 
Filter filter = new PageFilter(10); 

while(true) { 
    int rowCount = 0; 
    Scan scan = new Scan(); 
    scan.setFilter(filter); 
    scan.setStartRow(lastRow); 

    ResultScanner resultScanner = table.getScanner(scan); 
    Iterator<Result> resultIterator = resultScanner.iterator(); 
    while (resultIterator.hasNext()) { 
        Result result = resultIterator.next(); 
        // ... 
        lastRow = result.getRow(); // 记录最后一行的rowkey 
        rowCount++; // 记录本页行数 
    } 

    if(rowCount <= 10) { 
        break; 
    } 
}

使用该过滤器,对结果进行按行分页,需要指定 pageSize 参数,控制每页返回的行数,并设置 startRow 多次调用 getScanner(),感觉功能只适用于一些特殊场景,性能也并不高。

5.3、多种过滤条件使用

通过 FilterList 实例可以提供多个过滤器共同使用的功能。并且可以指定对多个过滤器的过滤结果如何组合。

Filter filter1 = new ...; 
Filter filter2 = new ...; 
Filter filter3 = new ...; 

FilterList filterList = new FilterList(Arrays.asList(filter1, filter2, filter3)); 

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

六、参考博文

1.漫谈HBase Filter
2.HBase之过滤器

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • 一、简介 Hbase:全名Hadoop DataBase,是一种开源的,可伸缩的,严格一致性(并非最终一致性)的分...
    菜鸟小玄阅读 2,364评论 0 12
  • 目录: 引言 -- 参数基础 1. 结构(Structural)过滤器--FilterList 2.列值过滤器--...
    磊宝万岁阅读 1,472评论 0 2
  • HBase 的基本 API,包括增、删、改、查等。查询可以根据 Rowkey 进行 Get 或根据 Rowkey ...
    Alex90阅读 8,955评论 0 2
  • 一、Hbase过滤器的介绍 HBase为筛选数据提供了一组过滤器,通过这个过滤器可以在HBase中的数据的多个维...
    ahzhaojj阅读 1,112评论 0 3
  • 桃花盛开的季节我遇见一个惬笑嫣然的姑娘桃花微风间清香舞动你一个微笑,刹那芳华十年伏笔,两厢情愿刚好遇见你刚好爱上你...
    独殊星尘阅读 165评论 0 0