尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

HBase过滤器原理与实战:服务端过滤机制与性能优化指南

HBase过滤器原理与实战:服务端过滤机制与性能优化指南 1. 项目概述为什么HBase过滤器是数据查询的“手术刀”如果你用过HBase的Java API尤其是Scan和Get操作可能会发现一个现象从HBase里拿数据经常是“一锅端”。你发起一个查询HBase会把整个Region里符合RowKey范围的数据都扫描出来一股脑地返回给客户端。这在数据量小的时候问题不大但当你的表有几十上百列而你只关心其中一两个列的值时这种粗放的方式就成了性能瓶颈和网络带宽的“杀手”。大量的无效数据在服务器端被读取、序列化再通过网络传输到客户端最后被你写的Java代码过滤掉——这个过程里CPU、IO、网络资源都被白白浪费了。HBase过滤器Filter就是为了解决这个痛点而生的。你可以把它理解为数据库里的WHERE子句但它的执行位置不是在客户端而是在HBase的RegionServer服务器端。当你在Scan或Get对象上设置一个过滤器后RegionServer在读取数据的过程中就会逐行甚至逐单元格地用这个过滤器的规则去判断。只有符合条件的数据才会被包含在返回的结果集中。不符合条件的在服务器端就被直接丢弃了根本不会传到你的客户端。这种“服务端过滤”的机制是提升HBase查询效率、降低资源消耗的核心手段。这就像你去图书馆找书没有过滤器时管理员会把整个书架Region的书都搬出来让你自己挑。而有了过滤器你告诉管理员“我只要2023年出版的、关于Java编程的书”管理员在书架前就直接帮你筛选好了最后递到你手上的就是精准符合你要求的几本。HBase过滤器就是那个“智能管理员”。理解并熟练使用过滤器是从HBase入门走向精通的必经之路。它直接关系到你生产环境应用的查询性能、稳定性和成本。今天我们就来深入剖析HBase过滤器的核心原理、使用方法和那些官方文档里不会写的“避坑指南”。2. 过滤器核心设计与架构解析2.1 过滤器的工作机制与执行位置要理解过滤器首先要明白它在HBase数据流中的位置。一个典型的HBase读取请求Scan生命周期如下客户端构造请求你在客户端代码中创建Scan对象设置startRow和stopRow并附加一个或多个过滤器。RPC请求发送请求被发送到对应Region所在的RegionServer。RegionServer处理RegionServer根据startRow和stopRow定位到具体的Region。从该Region的Store对应一个列族中顺序读取MemStore内存和HFile磁盘中的数据。关键步骤在读取每一行数据Cell的集合时在内存中MemStore或从磁盘扫描器Scanner读取数据后、返回给客户端前会调用附加的过滤器进行判断。过滤器决策过滤器对当前行进行评估返回一个Filter.ReturnCode。这个返回码决定了该行数据的命运INCLUDE: 包含此行继续处理此行中的各个列。INCLUDE_AND_NEXT_COL: 包含此列并跳到下一列。SKIP: 跳过此行继续扫描下一行。SEEK_NEXT_USING_HINT: 跳过此行并给出一个提示的下一个RowKey让扫描器直接跳转到那里用于优化某些过滤器的性能如PrefixFilter。NEXT_ROW: 直接跳到下一行通常用于行内过滤器判断某列不符合条件后决定放弃整行。结果返回只有被INCLUDE的行或列才会被序列化通过RPC响应返回给客户端。注意过滤器的执行是在RegionServer端但它不减少磁盘IO。如果Scan的范围是全表扫描RegionServer仍然需要读取HFile中的所有数据块。过滤器减少的是1) 从磁盘读取的数据块解压、反序列化后在Java堆内存中进行后续处理的数据量2) 从RegionServer到客户端的网络传输量3) 客户端反序列化和处理的数据量。因此合理设置Scan的startRow和stopRow配合过滤器才是最优解。2.2 过滤器的分类与继承体系HBase内置了丰富的过滤器可以从不同维度进行分类理解分类有助于你根据场景选型。按过滤维度分类行键过滤器基于RowKey进行过滤。例如PrefixFilter前缀过滤、RowFilter使用比较器过滤。列过滤器基于列族Column Family和列限定符Column Qualifier进行过滤。例如FamilyFilter、QualifierFilter、ColumnPrefixFilter。值过滤器基于单元格Cell的值进行过滤。例如ValueFilter、SingleColumnValueFilter。参考过滤器基于其他过滤器或复杂逻辑进行过滤。例如FilterList过滤器列表用于组合多个过滤器、SkipFilter当某行被包装的过滤器包含时反而跳过它、WhileMatchFilter一旦某行不匹配则停止扫描。按功能特性分类比较过滤器这是最基础、最强大的一类它们依赖于CompareOperator比较操作符和ByteArrayComparable比较器来工作。RowFilter、FamilyFilter、QualifierFilter、ValueFilter都属于此类。专用过滤器为特定高频场景优化的过滤器如PrefixFilter、ColumnPrefixFilter、PageFilter分页、FirstKeyOnlyFilter只取每行第一个键值对常用于行数统计。装饰过滤器本身不直接过滤数据而是用来修饰或改变其他过滤器的行为如FilterList、SkipFilter。核心继承关系所有过滤器都实现了Filter接口。CompareFilter是一个重要的抽象基类上述比较过滤器都继承自它。理解CompareFilter就掌握了过滤器的半壁江山。它的核心构造方法是public CompareFilter(final CompareOperator op, final ByteArrayComparable comparator)你需要提供两样东西比较操作符和比较器。比较操作符CompareOperator定义了如何比较。常用值有LESS小于LESS_OR_EQUAL小于等于EQUAL等于NOT_EQUAL不等于GREATER_OR_EQUAL大于等于GREATER大于NO_OP无操作通常用于只需存在性检查不关心值比较器ByteArrayComparable定义了比较的规则即“比什么”和“怎么比”。HBase内置了多种比较器BinaryComparator最常用的二进制比较器按字节字典序比较。BinaryPrefixComparator二进制前缀比较器判断目标值是否以指定字节数组开头。RegexStringComparator正则表达式比较器慎用性能开销大。SubstringComparator子字符串比较器判断目标值是否包含指定子串。NullComparator判断是否为空。2.3 过滤器性能关键服务端 vs. 客户端这是使用过滤器时必须绷紧的一根弦。虽然过滤器在服务端执行但并非所有过滤器都能以最优性能运行。服务端生效的过滤器大多数内置过滤器如PrefixFilterColumnPrefixFilter 基于比较器的RowFilter/ValueFilter等都可以在服务端生效。它们会被序列化随着Scan请求一起发送到RegionServer并在扫描过程中被调用。可能降级到客户端的过滤器一些复杂的过滤器尤其是自定义过滤器Custom Filter如果其逻辑RegionServer无法执行或者因为版本兼容性问题可能会被“拖回”到客户端执行。这意味着RegionServer会把所有数据都传给客户端由你的客户端JVM来运行过滤逻辑这就完全丧失了过滤器的意义性能甚至会变得更差。如何判断一个简单的经验法则是尽量使用HBase内置的、经过优化的过滤器。如果必须使用自定义过滤器要确保其逻辑简单并且了解你所用HBase版本对自定义过滤器的支持情况。在开发测试时可以通过查看RegionServer的日志或使用scan.setFilter(filter).setMaxResultSize(1)这类限制结果大小的方法来辅助判断数据是否在服务端被有效过滤了。3. 核心过滤器详解与实战代码示例理论讲完了我们直接上代码看看这些过滤器到底怎么用。假设我们有一张用户行为表user_actionsRowKey设计为{userId}_{timestamp}列族为cf里面有action行为类型、page页面、duration停留时长等列。3.1 行键过滤器精准定位数据起点PrefixFilter最常用的行键过滤器当你需要查询某个用户的所有行为时PrefixFilter是首选。因为我们的RowKey以userId_开头。import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.filter.PrefixFilter; import org.apache.hadoop.hbase.util.Bytes; // ... 建立Connection和Table对象 Scan scan new Scan(); String userId user123; // PrefixFilter的参数就是你要匹配的前缀字节数组 Filter prefixFilter new PrefixFilter(Bytes.toBytes(userId _)); scan.setFilter(prefixFilter); // 通常配合setStartRow使用但PrefixFilter本身会确保只扫描包含该前缀的行 // scan.setStartRow(Bytes.toBytes(userId _)); ResultScanner scanner table.getScanner(scan); for (Result result : scanner) { // 处理结果 } scanner.close();实操心得PrefixFilter性能极高因为它可以和HFile的索引布隆过滤器结合帮助扫描器快速定位到可能包含该前缀的数据块甚至在某些情况下跳过整个数据块。它内部可能会返回SEEK_NEXT_USING_HINT返回码来指导扫描器跳跃。RowFilter更灵活的行键过滤如果你想找RowKey大于某个特定时间戳的记录就需要RowFilter。import org.apache.hadoop.hbase.CompareOperator; import org.apache.hadoop.hbase.filter.RowFilter; import org.apache.hadoop.hbase.filter.BinaryComparator; Scan scan new Scan(); String startRow user123_20240101000000; // 过滤出RowKey大于等于 startRow 的行 RowFilter rowFilter new RowFilter(CompareOperator.GREATER_OR_EQUAL, new BinaryComparator(Bytes.toBytes(startRow))); scan.setFilter(rowFilter); // 注意这里通常也需要设置scan.setStartRow让扫描器物理定位更快。过滤器是逻辑过滤startRow是物理定位。 scan.setStartRow(Bytes.toBytes(startRow));注意事项RowFilter和Scan自带的setStartRow/setStopRow功能有重叠但目的不同。setStartRow是给扫描器一个物理起始位置跳过之前的所有数据块是性能优化的关键。而RowFilter是逻辑判断即使数据被扫描器读出来了也会被过滤掉。最佳实践是同时使用用setStartRow/setStopRow缩小物理扫描范围再用RowFilter做精确的逻辑过滤。3.2 列过滤器筛选关心的列ColumnPrefixFilter按列名前缀过滤假设我们的列名除了action还有action_detail、action_type。我们只想查询以action开头的列。import org.apache.hadoop.hbase.filter.ColumnPrefixFilter; Scan scan new Scan(); // 只返回列限定符以action开头的列 ColumnPrefixFilter colPrefixFilter new ColumnPrefixFilter(Bytes.toBytes(action)); scan.setFilter(colPrefixFilter); // 如果我们同时想限制列族可以结合Scan的addFamily方法 // scan.addFamily(Bytes.toBytes(cf));这个过滤器非常高效因为它可以在列级别进行过滤。MultipleColumnPrefixFilter匹配多个列名前缀如果想同时查询action和page开头的列。import org.apache.hadoop.hbase.filter.MultipleColumnPrefixFilter; byte[][] prefixes new byte[][]{Bytes.toBytes(action), Bytes.toBytes(page)}; MultipleColumnPrefixFilter multiPrefixFilter new MultipleColumnPrefixFilter(prefixes); scan.setFilter(multiPrefixFilter);QualifierFilter使用比较器过滤列名更复杂的列名过滤比如找出列名长度等于5的列。import org.apache.hadoop.hbase.filter.QualifierFilter; import org.apache.hadoop.hbase.filter.BinaryComparator; // 这是一个略显刻意的例子但展示了灵活性 // 假设我们想找到列名正好是page的列 QualifierFilter qualifierFilter new QualifierFilter( CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(page)) ); scan.setFilter(qualifierFilter);3.3 值过滤器基于单元格内容的过滤这是业务逻辑最丰富的部分。ValueFilter过滤所有列的值查询所有行为中停留时长假设存储在duration列值为数字字符串超过“100”的记录。import org.apache.hadoop.hbase.filter.ValueFilter; // 注意ValueFilter会检查**所有列**的值 ValueFilter valueFilter new ValueFilter( CompareOperator.GREATER, new BinaryComparator(Bytes.toBytes(100)) // 按字符串字典序比较小心99 100 ); scan.setFilter(valueFilter);踩过的坑ValueFilter会扫描所有列的值包括action、page等非数值列。如果这些列的值不是数字与BinaryComparator比较可能会产生非预期的结果或者抛出异常。更危险的是字符串比较“99”是大于“100”的因为‘9’的ASCII码大于‘1’。对于数值比较必须将值转换为可比较的字节格式如用Bytes.toBytes(Long.valueOf(100))或者使用LongComparator等专用比较器如果HBase版本支持。SingleColumnValueFilter针对特定列的值过滤这才是最常用、最符合直觉的值过滤器。它只针对你指定的列族和列进行值判断。import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; import org.apache.hadoop.hbase.filter.SubstringComparator; Scan scan new Scan(); // 过滤出 action列的值 包含 “login” 子串 的行 SingleColumnValueFilter scvf new SingleColumnValueFilter( Bytes.toBytes(cf), // 列族 Bytes.toBytes(action), // 列限定符 CompareOperator.EQUAL, // 注意这里是比较操作符 new SubstringComparator(login) // 使用子字符串比较器 ); // 两个关键设置 // 1. 如果指定的列不存在这行数据是否返回setFilterIfMissing(true)表示不返回。 scvf.setFilterIfMissing(true); // 2. 是否只返回最新版本setLatestVersionOnly(true)可以提高性能。 scvf.setLatestVersionOnly(true); scan.setFilter(scvf);SingleColumnValueFilter是业务查询的利器。setFilterIfMissing方法至关重要设为true时如果某行没有你指定的列这行会被过滤掉设为false时即使没有该列行也会被返回。这完全取决于你的业务逻辑。3.4 组合过滤器用FilterList实现复杂逻辑现实需求很少是单一的。比如“查找用户user123在2024年1月之后且行为为‘purchase’的记录”。这就需要组合过滤器。import org.apache.hadoop.hbase.filter.FilterList; import org.apache.hadoop.hbase.filter.SingleColumnValueFilter; Scan scan new Scan(); scan.setStartRow(Bytes.toBytes(user123_20240101000000)); // 创建子过滤器 Filter prefixFilter new PrefixFilter(Bytes.toBytes(user123_)); SingleColumnValueFilter actionFilter new SingleColumnValueFilter( Bytes.toBytes(cf), Bytes.toBytes(action), CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(purchase)) ); actionFilter.setFilterIfMissing(true); // 创建FilterList定义过滤器之间的逻辑关系 // FilterList.Operator.MUST_PASS_ALL 表示所有条件必须同时满足 (AND逻辑) // FilterList.Operator.MUST_PASS_ONE 表示满足任意一个即可 (OR逻辑) FilterList filterList new FilterList(FilterList.Operator.MUST_PASS_ALL); filterList.addFilter(prefixFilter); filterList.addFilter(actionFilter); scan.setFilter(filterList);FilterList的执行顺序过滤器会按照你addFilter的顺序依次执行。当前一个过滤器决定INCLUDE某行后该行才会被传递给下一个过滤器。如果遇到SKIP或NEXT_ROW则后续过滤器可能不会对该行进行评估。因此将最可能过滤掉大量数据的、成本较低的过滤器放在前面是一个重要的性能优化技巧。例如把PrefixFilter放在SingleColumnValueFilter前面。4. 高级特性与性能优化实战4.1 分页查询PageFilter的陷阱与正确用法PageFilter看似简单但坑最多。它的作用是限制一次扫描返回的行数。import org.apache.hadoop.hbase.filter.PageFilter; Scan scan new Scan(); PageFilter pageFilter new PageFilter(10); // 每页10行 scan.setFilter(pageFilter); ResultScanner scanner table.getScanner(scan); int count 0; for (Result result : scanner) { // 处理结果 count; if (count 10) { // 理论上PageFilter会保证只返回10行 break; } } scanner.close();大坑预警PageFilter的工作机制是在RegionServer端它会计数当包含的行数达到设定值后就停止扫描并返回。但是HBase的Scan是面向Region的。如果你的Scan跨了多个Region比如全表扫描PageFilter会在每个Region内部单独计数。也就是说你设置PageFilter(10)如果扫描了3个Region最终可能会返回多达30条记录每个Region10条。正确实现分页的方法使用startRow和stopRow进行物理分页这是最高效的方式。查询第一页后记录最后一行的RowKey作为下一页查询的startRow。这要求RowKey是有序的。客户端手动分页使用scan.setMaxResultSize()和scan.setCaching()进行粗略控制然后在客户端收集结果达到所需数量后停止。但这仍然可能从服务端获取多余数据。PageFilter 单Region扫描确保你的Scan范围在一个Region内通过精心设计RowKey和预分区实现。但这限制了业务灵活性。生产环境中方法1是推荐做法。PageFilter更适合在已知扫描范围很小如一个用户的数据时做简单的限制。4.2 布隆过滤器加速“是否存在”查询布隆过滤器Bloom Filter不是客户端API中的过滤器而是HBase表的一个元数据特性它对于Get操作性能提升巨大。它用于快速判断一个数据块中是否可能包含某个RowKey。原理每个数据块HFile Block都有一个布隆过滤器位数组。写入时将该行RowKey经过多个哈希函数映射到位数组的多个位置并置1。查询时同样哈希RowKey检查对应位置是否都为1。如果有一个为0则肯定不存在如果全为1则可能存在存在误判可能。作用在执行Get时HBase可以快速跳过那些肯定不包含目标RowKey的数据块减少不必要的磁盘IO。配置在创建表或修改表时设置。CREATE ‘mytable’, {NAME ‘cf’, BLOOMFILTER ‘ROW’}NONE关闭默认。ROW基于RowKey构建布隆过滤器。适用于Get操作。ROWCOL基于RowKey列族列限定符构建。适用于指定了列族的Get操作更精确但存储开销更大。实操心得对于随机Get为主的表强烈建议开启ROW级别的布隆过滤器它能极大提升点查性能。对于使用Scan为主、或者Get总是能命中比如通过RowKey前缀的场景布隆过滤器的收益不大因为扫描本身就要读取数据块。存储开销和计算开销是它的代价需要权衡。4.3 自定义过滤器应对极端定制化需求当内置过滤器无法满足需求时你可以编写自定义过滤器。例如你需要过滤出action列的值在一个特定集合内的行。import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.filter.FilterBase; import org.apache.hadoop.hbase.util.Bytes; import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; public class ValueInSetFilter extends FilterBase { private Setbyte[] valueSet; private boolean filterRow true; // 默认过滤掉该行 public ValueInSetFilter(Setbyte[] valueSet) { // 注意byte[]作为Map/Set的key需要特殊处理这里为简化使用SetString // 实际生产环境应使用ByteArrayWrapper等工具类 this.valueSet valueSet; } Override public ReturnCode filterKeyValue(Cell cell) throws IOException { // 检查当前单元格的列族和列是否是我们关心的 if (Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength(), Bytes.toBytes(action), 0, Bytes.toBytes(action).length)) { // 提取单元格的值 byte[] value Arrays.copyOfRange(cell.getValueArray(), cell.getValueOffset(), cell.getValueOffset() cell.getValueLength()); // 判断值是否在集合中 for (byte[] target : valueSet) { if (Bytes.equals(value, target)) { filterRow false; // 值在集合中不过滤该行 return ReturnCode.INCLUDE_AND_NEXT_COL; // 包含此列并检查下一列 } } } // 如果不是关心的列或者值不在集合中继续检查本行下一列 return ReturnCode.INCLUDE_AND_NEXT_COL; } Override public boolean filterRow() throws IOException { // 如果filterRow为true则过滤掉整行 return filterRow; } Override public void reset() throws IOException { // 在处理下一行前重置状态 this.filterRow true; } // 必须实现序列化/反序列化方法用于在RPC间传递过滤器对象 Override public byte[] toByteArray() throws IOException { // ... 序列化valueSet的逻辑 return new byte[0]; } public static ValueInSetFilter parseFrom(final byte[] pbBytes) throws DeserializationException { // ... 反序列化逻辑 return null; } }使用警告性能自定义过滤器可能无法利用HBase的底层优化性能通常低于内置过滤器。服务端部署自定义过滤器的类必须在RegionServer的类路径中可用否则会触发org.apache.hadoop.hbase.DoNotRetryIOException异常。这给部署带来了复杂性。版本兼容过滤器序列化/反序列化的逻辑必须谨慎处理HBase版本升级可能导致兼容性问题。最佳实践优先考虑能否用多个内置过滤器组合FilterList或者通过调整RowKey/列名设计来避免自定义过滤器。如果必须使用确保逻辑简单并做好充分的测试和性能评估。5. 常见问题排查与性能调优指南5.1 过滤器不生效可能是这些原因过滤器被忽略检查Scan是否真的设置了过滤器。scan.setFilter(filter)方法调用是否正确。比较器使用错误这是最常见的问题。比如用BinaryComparator进行数字比较或者混淆了字符串和字节数组的比较顺序。务必清楚你的数据在HBase中是如何以字节形式存储的。setFilterIfMissing行为不符预期在使用SingleColumnValueFilter时忘记设置setFilterIfMissing其默认值是false。这意味着即使该列不存在行也会被返回这可能和你的业务逻辑冲突。Scan范围过大过滤器在逻辑上生效了但因为你的Scan没有设置startRow/stopRow导致RegionServer扫描了海量数据虽然最后返回的很少但查询依然很慢。永远记得用RowKey范围来限制物理扫描。版本问题setLatestVersionOnly默认是false。如果你只关心最新版本但该列有多个历史版本过滤器会对每个版本都进行评估影响性能。如果业务允许将其设为true。5.2 查询性能低下从这几个方面优化首要优化RowKey设计这是HBase性能的基石。好的RowKey设计应能将查询负载均匀分布并让大多数查询都能通过startRow/stopRow或PrefixFilter完成。避免全表扫描。善用布隆过滤器对随机Get频繁的表启用ROW级布隆过滤器。优化过滤器顺序在FilterList中将最“廉价”、过滤性最强的过滤器放在前面。例如PrefixFilter通常比ValueFilter成本低。限制扫描范围与属性scan.setCaching(100)设置每次RPC从服务器端获取的行数。太大会占用客户端内存太小会增加RPC次数。根据单行数据大小调整通常100-500是合理范围。scan.setMaxResultSize(5 * 1024 * 1024)设置一次扫描返回的最大字节数。防止一次查询返回过多数据拖垮客户端或网络。scan.setBatch(10)设置每次next()调用返回的每行中的列数。如果你只需要每行的前几列设置batch可以显著减少网络传输。避免使用正则和子串比较器RegexStringComparator和SubstringComparator性能开销非常大因为它们需要在服务端对每一行数据进行字符串匹配。如果可能通过设计RowKey或列名来避免这种模糊查询。监控与诊断使用HBase UI或Metrics系统监控scanTime、filteredReadRequests等指标。如果发现filteredReadRequests远大于返回行数说明过滤器在服务端生效了但扫描范围太大。如果两者接近则可能是过滤器本身效率问题或扫描范围合理。5.3 过滤器组合使用的复杂场景示例场景查询用户user123和user456在2024年3月1日到3月31日之间action为“click”或“view”且duration大于50的所有记录。Scan scan new Scan(); // 设置时间范围假设RowKey包含时间戳 scan.setStartRow(Bytes.toBytes(user123_20240301000000)); // 注意stopRow是排他的所以我们需要用‘\0’作为下一个可能的起始点的小技巧 // 更精确的做法是计算出 endUserId “_” endTimestamp 的后继RowKey byte[] stopRowForUser123 Bytes.toBytes(user123_20240401000000); byte[] stopRowForUser456 Bytes.toBytes(user456_20240401000000); // 这里简化处理实际需要更复杂的逻辑处理两个用户的范围可能需要分两次Scan // 创建过滤器 FilterList userFilterList new FilterList(FilterList.Operator.MUST_PASS_ONE); // OR: user123 OR user456 userFilterList.addFilter(new PrefixFilter(Bytes.toBytes(user123_))); userFilterList.addFilter(new PrefixFilter(Bytes.toBytes(user456_))); SingleColumnValueFilter actionFilter new SingleColumnValueFilter( Bytes.toBytes(cf), Bytes.toBytes(action), CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(click)) ); actionFilter.setFilterIfMissing(false); // 我们先不在这里过滤缺失用另一个OR逻辑处理 SingleColumnValueFilter actionFilter2 new SingleColumnValueFilter( Bytes.toBytes(cf), Bytes.toBytes(action), CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(view)) ); actionFilter2.setFilterIfMissing(false); FilterList actionFilterList new FilterList(FilterList.Operator.MUST_PASS_ONE); // OR: click OR view actionFilterList.addFilter(actionFilter); actionFilterList.addFilter(actionFilter2); SingleColumnValueFilter durationFilter new SingleColumnValueFilter( Bytes.toBytes(cf), Bytes.toBytes(duration), CompareOperator.GREATER, new BinaryComparator(Bytes.toBytes(50)) // 再次注意字符串比较问题 ); durationFilter.setFilterIfMissing(true); // 没有duration列的行过滤掉 // 组合所有条件用户 AND (action1 OR action2) AND duration FilterList finalFilterList new FilterList(FilterList.Operator.MUST_PASS_ALL); finalFilterList.addFilter(userFilterList); finalFilterList.addFilter(actionFilterList); finalFilterList.addFilter(durationFilter); scan.setFilter(finalFilterList); // 注意这个Scan的startRow/stopRow需要根据两个用户分别处理这里逻辑不完整。 // 更可行的方案是分两次查询分别对user123和user456进行Scan然后在客户端合并结果。这个例子揭示了复杂过滤逻辑的挑战FilterList可以嵌套实现AND/OR但Scan的startRow/stopRow是线性的无法直接表达“两个前缀”这样的逻辑。当业务逻辑过于复杂时重新审视数据模型和查询方式或者考虑在客户端进行少量数据的二次过滤往往是更明智的选择。过滤器是HBase赋予开发者的强大武器但它不是银弹。高效查询的根源在于良好的表设计尤其是RowKey。过滤器是在此基础上进行精准“修剪”的工具。理解每一类过滤器的原理、代价和最佳实践结合具体的业务场景灵活运用才能让你在面对海量数据时依然能游刃有余地实现毫秒级查询。
返回列表