本文最新更新, 请访问作者个人主页, 后续也会更新一些HBase高级特性相关的文章.
本文首先结合HBase过滤器部分的源码, 讲述HBase过滤器抽象基类Filter中各个函数的作用. 最终给出一个简单的自定义过滤器的案例, 在此基础上分析了Filter中各个方法的执行流程, 读者在理解该案例的基础上可以编写任何个性化的过滤器. 本文涉及的源码基于HBase 1.4.x这一Stable Release版本. 为方便读者复现本文的案例, 相关代码已经发布到GitHub, 连接可由本人个人主页中获取.
HBase自定义过滤器能够在RegionServer上, 执行更加复杂和个性化的过滤操作. 例如, 在利用HBase存储时空数据时, 可以将时空范围的过滤操作放到过滤器中. 使用过滤器主要有以下两个优点: 一是通过使用过滤器将过滤逻辑放到RegionServer上执行, 可以使客户端代码更加简洁; 二是将过滤操作放到RegionServer上执行可以减少网络传输, 提升查询效率. 当然, 使用自定义过滤器也存在一定的缺点: 过滤器对象需要在HBase的Client和RegionServer之间通过网络传输, 对于用户自定义过滤器, 需要重写序列化和反序列化操作, 不同于Go语言, Java语言并没有提供原生的高效序列化支持, HBase预定义的过滤器使用了Protocol Buffers进行序列化与反序列化. 本文给出的案例使用了Java的NIO进行自定义过滤器对象的序列化和反序列化, 在生产环境中建议使用Protocol Buffers.
HBse自定义过滤器概览
HBase过滤器属于客户端API, 相关的源码均在hbase-client模块的org.apache.hadoop.hbase.filter包下. 对于设定了过滤器的scan, 在每个Region上过滤器都会起作用.
自定义HBase过滤器需要继承抽象类Filter或抽象类FilterBase. Filter是FilterBase的基类, FilterBase为我们提供了一些方法的默认实现, 在自定义过滤器时可先继承FilterBase, 实现其抽象方法, 在需要更进一步的个性化操作时再考虑按需实现Filter提供的方法. 为了更好的演示Filter中各个方法的作用, 本文的案例在实现时继承了抽象类Filter.
在具体探讨HBase过滤器之前, 我们将首先探讨抽象类Filter中各个函数的作用, 理解这些函数的具体作用是我们进行后续研究的基础. 类Filter的定义如下, 这里为每个属性和函数添加了详细的注释, 解释其作用.
public abstract class Filter {
/**
* 是否逆序扫描的标志. true表示当前以字节序从大到小扫描, false表示当前以字节序从小到大扫描.
*/
protected transient boolean reversed;
/**
* 在每行扫描结束时调用, 一般用于重置过滤标志
*/
abstract public void reset() throws IOException;
/**
* 真正开始过滤的第一步, 用于对row key进行过滤. 若过滤当前行, 返回true, 若保留当前行返回false.
* @param buffer 包含row key的字节数组
* @param offset row key在buffer数组中的开始位置
* @param length row key的长度
*/
abstract public boolean filterRowKey(byte[] buffer, int offset, int length)
throws IOException;
/**
* 用于判断是否需要继续进行过滤操作, 在调用filterRowKey(), filterKeyValue(), transformCell(),
* reset()函数之后都会调用. 返回true表示不需要继续进行过滤, 将结束包含当前过滤器的scan操作,
* 返回false表示需要继续进行后续过滤操作.
*/
abstract public boolean filterAllRemaining() throws IOException;
/**
* 真正开始过滤的第二步, 用于对每个cell进行过滤, 其返回值的含义参考枚举ReturnCode
* @param v 当前行中的每个cell
*/
abstract public ReturnCode filterKeyValue(final Cell v) throws IOException;
/**
* 对于filterKeyValue()中通过的cell调用, 用于对cell进行一定的转换操作, 一般直接返回传入的参数即可
* @param v 经过filterKeyValue()决定保留的cell
*/
abstract public Cell transformCell(final Cell v) throws IOException;
/**
* 弃用, 使用transformCell()代替
*/
@Deprecated
abstract public KeyValue transform(final KeyValue currentKV) throws IOException;
public enum ReturnCode {
// 包含当前cell
INCLUDE,
// 包含当前cell, 查询下一列
INCLUDE_AND_NEXT_COL, // 与INCLUDE的区别是会忽略旧版本数据
// 跳过当前cell
SKIP,
// 跳过当前列, 查询当前行的下一列
NEXT_COL, // 与SKIP仅有细微差别, 如果序遍历所有列使用NEXT_COL, 如果不需要查询当前cell之后的列使用SKIP
// 查询当前column family中的下一行. 注意: 当前行的其他column family在之后仍可能被查询
NEXT_ROW,
// 调用getNextCellHint()决定下一个需要查询的cell
SEEK_NEXT_USING_HINT,
// 包含当前cell并查询下一行, 当前行中所有剩余的cell都将不会被查询
INCLUDE_AND_SEEK_NEXT_ROW,
}
/**
* 对该函数的具体作用尚未完全明了. 根据源码中的解释, 该函数是对需要提交的的cell进行进行原地更改.
* 一般情况下无需重载该函数.
*/
abstract public void filterRowCells(List<Cell> kvs) throws IOException;
/**
* 主要用于检查冲突的scans(例如不一次读取整行的scan)
*/
abstract public boolean hasFilterRow();
/**
* 最后用于决定是否过滤当前行的机会, 若返回true过滤当前行, 否则保留当前行
*/
abstract public boolean filterRow() throws IOException;
/**
* 弃用, 使用getNextCellHint()代替
*/
@Deprecated