HBase 的 Scan 操作默认按行键字典序读取数据,如果在扫描时只想要满足某个列值条件的行,传统做法是把范围内数据全部拉回客户端再做判断,这在大表场景下会带来很大的网络和内存压力。Filter 过滤器正是为解决这类问题而设计,它会被下推到 RegionServer 端执行,只把符合条件的行或单元格返回给客户端。本文以实际业务中常见的条件扫描为例,梳理 Scan 与 Filter 的配合方式。

一、Scan 与 Filter 的工作机制
HBase 表的数据按行键字典序分散在不同 Region 中,Scan 请求会被拆分成多个 RegionScanner 并行处理。设置起始行和结束行可以缩小扫描范围,避免跨 Region 的全表扫描。如果没有 Filter,RegionServer 会把范围内每一行数据以及指定的列族列名全部返回,即使客户端只关心某列值大于 18 的行。
Filter 在服务端的读取路径上生效。它提供多个回调方法,例如行键过滤、行过滤和单元格过滤,RegionScanner 会按顺序调用这些方法决定行或单元格是否继续传递。以 SingleColumnValueFilter 为例,它需要读取指定列的值并与比较器比较,只有满足条件时该行才能返回;而 PrefixFilter 只需检查行键前缀,判断成本更低。因此,在组合过滤器中把低成本过滤器放在前面,有助于减少后续高成本判断的执行次数。
Scan 通过 setFilter 方法接收一个 Filter 实例,也支持传入 FilterList 来组合多个过滤器。FilterList 支持 MUST_PASS_ALL 与 MUST_PASS_ONE 两种逻辑,分别对应与、或关系。合理使用组合条件,可以在服务端完成更精确的数据筛选。
二、常用过滤器与代码示例
条件扫描中最常用的过滤器是 SingleColumnValueFilter,它比较指定列族和列名的值。下面的代码演示了从 user_100 到 user_200 范围内,筛选 info 列族中 age 列值大于等于 18 的行,并返回完整行数据。
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.CompareOperator;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
Configuration conf = HBaseConfiguration.create();
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("user_table"));
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("user_100"));
scan.setStopRow(Bytes.toBytes("user_200"));
SingleColumnValueFilter ageFilter = new SingleColumnValueFilter(
Bytes.toBytes("info"),
Bytes.toBytes("age"),
CompareOperator.GREATER_OR_EQUAL,
new BinaryComparator(Bytes.toBytes(18))
);
ageFilter.setFilterIfMissing(true);
scan.setFilter(ageFilter);
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
String rowKey = Bytes.toString(result.getRow());
byte[] ageValue = result.getValue(Bytes.toBytes("info"), Bytes.toBytes("age"));
System.out.println(rowKey + " -> " + (ageValue == null ? "null" : Bytes.toInt(ageValue)));
}
scanner.close();
table.close();
connection.close();
如果需要匹配行键前缀,PrefixFilter 比 RowFilter 加正则更高效。下面的代码组合了前缀过滤和列值过滤,形成与条件:
Scan scan2 = new Scan();
scan2.setStartRow(Bytes.toBytes("user_100"));
scan2.setStopRow(Bytes.toBytes("user_200"));
PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("user_10"));
SingleColumnValueFilter ageFilter2 = new SingleColumnValueFilter(
Bytes.toBytes("info"),
Bytes.toBytes("age"),
CompareOperator.GREATER_OR_EQUAL,
new BinaryComparator(Bytes.toBytes(18))
);
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
filterList.addFilter(prefixFilter);
filterList.addFilter(ageFilter2);
scan2.setFilter(filterList);
ResultScanner scanner2 = table.getScanner(scan2);
for (Result result : scanner2) {
System.out.println(Bytes.toString(result.getRow()));
}
scanner2.close();
行键本身包含规律时,也可以使用 RowFilter 配合正则表达式进行匹配。但要注意正则匹配需要读取行键后做模式比较,效率一般低于前缀过滤,适合无法用简单前缀描述的场景。
Scan scan3 = new Scan();
scan3.setStartRow(Bytes.toBytes("user_100"));
scan3.setStopRow(Bytes.toBytes("user_200"));
RowFilter rowFilter = new RowFilter(
CompareOperator.EQUAL,
new RegexStringComparator("user_1\\d{2}")
);
scan3.setFilter(rowFilter);
ResultScanner scanner3 = table.getScanner(scan3);
for (Result result : scanner3) {
System.out.println(Bytes.toString(result.getRow()));
}
scanner3.close();
三、组合过滤与性能优化注意事项
过滤器的好处在下推计算,但并不意味着可以随意使用。首先,扫描范围要尽可能小。即使 Filter 能够过滤大部分数据,如果 startRow 和 stopRow 设置得过于宽泛,请求会被分发到大量 Region 上,增加 RegionServer 的 CPU 和磁盘 IO 压力。条件扫描时应结合业务行键设计,把范围缩到最小。
SingleColumnValueFilter 涉及列值读取,使用时应明确处理列缺失的情况。默认情况下,如果目标列不存在,该行会被过滤掉;如果业务希望保留缺失列的行,需要调用 setFilterIfMissing(true)。另外,如果列有多个版本,建议调用 setLatestVersionOnly(true),只取最新版本比较,否则可能因为历史版本满足条件而返回本不该返回的行。
在 FilterList 中,过滤器按添加顺序执行。对于 MUST_PASS_ALL 条件,优先放置行键级或成本低的过滤器,比如 PrefixFilter,再放置 SingleColumnValueFilter,可以减少不必要的列读取。对于 MUST_PASS_ONE 条件,需要考虑逻辑上是否有低成本的短路条件。还要注意,Scan 默认返回整行所有列,如果只需要少量列,通过 addColumn 限制返回列族和列限定符,能显著降低网络传输。
最后,HBase 不同版本 API 有差异。2.x 版本使用 CompareOperator,旧版本使用 CompareFilter.CompareOp,升级和排查问题时要留意兼容性。过滤条件无法使用二级索引,对于复杂查询还应考虑 Phoenix 或 Elasticsearch 等方案,但简单条件扫描中 Scan 加 Filter 依然是最轻量的选择。

