深入 HBase 14 - Get、Scan、Filter 与分页
前置问题与边界
HBase 的读请求不是“把表读出来再过滤”。Get 直接定位单行;Scan 按 row-key range 顺序推进;Filter 可以减少返回给客户端的 Cell,也可能在服务端继续扫描大量数据。caching、batch、limit 和 reversed 影响 RPC 粒度、结果分片和遍历方向,但不会改变 HFile 的物理排序。
本篇回答一个核心问题:scan range、cache、batch、limit、reversed scan 如何影响 RPC 和结果语义。BlockCache、Bloom Filter 和 HFile block 在第 05、06 篇;row key 设计在第 03 篇;Coprocessor 和 Phoenix 边界在第 16 篇。
读取路径图
flowchart TD
C[Client] --> O{Operation}
O -->|Get row| G[Single row lookup]
O -->|Scan range| S[Region scanner]
S --> R[Row-key ordered traversal]
R --> F[Filter evaluation]
F --> B[Batch splits cells]
F --> K[Caching controls rows per RPC]
K --> N[ResultScanner next]
B --> P[Partial result if row is wide]
Get 和 Scan 都经过 Region 定位和 RegionServer 读路径,但访问形状不同。Get 适合单行或少量已知 row key;Scan 适合连续 row-key range。把 Filter 当成关系数据库 where 子句,会低估 row key 设计对扫描成本的影响。
关键对象和状态
| 对象 | 作用 | 语义边界 |
|---|---|---|
Get |
单 row key 读取 | 不做范围遍历 |
Scan |
row-key range 遍历 | stop row 通常是排他边界 |
ResultScanner |
客户端迭代器 | 多次 next 触发 RPC |
setCaching |
每次 RPC 预取行数 | 不是 BlockCache 开关 |
setBatch |
每个 Result 返回的 Cell 数上限 | 可能产生 partial result |
setLimit |
scan 返回行数上限 | 不等于每 RPC 行数 |
Filter |
服务端过滤结果 | 不能替代合理 row key range |
setReversed |
反向 scan | 不改变底层 HFile 排序 |
caching 和 BlockCache 名字相似,但不是一件事。Scan#setCaching 控制一次 RPC 尽量拿多少行;BlockCache 缓存 HFile block,属于 RegionServer 存储读路径。
Scan range
row key 的字节排序决定了 scan 的自然顺序。withStartRow 和 withStopRow 用来限制范围,常见 stop row 是排他边界。范围设计越贴近查询谓词,RegionServer 需要扫描的无关行越少。
Prefix 查询的常见做法不是全表 scan 加 PrefixFilter,而是先把 start row 和 stop row 约束到同一前缀范围,再用 filter 做补充。MultiRowRangeFilter 适合把多个离散 row-key range 合并进一次 scan,但它仍然要按范围工作。
模式提炼:先缩小物理范围,再做逻辑过滤。公式是 scan(range(row_key)) -> filter(cells) -> return(results)。搜索引擎先按倒排缩小候选、数据库先走索引再回表、对象存储先按 prefix 列举,都属于同类顺序。
Caching、batch 和 partial result
setCaching(n) 控制客户端 scanner 一次 RPC 请求服务端返回多少行左右。值太小会增加 RPC 次数;值太大可能提高客户端内存占用和单次响应延迟。它不是“把数据放进缓存”,也不是服务器端 BlockCache 的开关。
setBatch(n) 控制每个 Result 最多包含多少个 Cell。宽行场景下,一个逻辑行可能被拆成多个 partial results。公共 API 中存在 Result#mayHaveMoreCellsInRow() 这一类信号,调用方必须识别 partial result,否则容易把半行误认为整行。
setLimit(n) 限制 scan 返回的行数。它与 caching 的区别在于:limit 是总结果行数边界,caching 是 RPC 批量获取粒度。两者常一起使用,但含义不同。
Filter 的职责
Filter 在 RegionServer 端参与结果过滤,可以减少返回给客户端的数据量。常见 filter 包括 PrefixFilter、PageFilter、WhileMatchFilter、ColumnPrefixFilter、SingleColumnValueFilter、MultiRowRangeFilter 等。Filter 的效果取决于它是否能提前停止扫描或跳过数据;有些 filter 只减少返回,不减少底层扫描量。
PageFilter 容易被误用为稳定分页方案。它限制的是 scan 返回数量,不保存全局游标。多页读取更可靠的做法通常是使用上一次返回的最后 row key 作为下一次 start row,并处理边界去重。并发写入和 Region 移动下,分页语义要按业务可接受的一致性设计。
Reversed scan
setReversed(true) 让 scan 反向遍历 row key range。它适合“取某个前缀下最新 N 条”这类 row key 已经按时间可逆表达的场景。反向 scan 不会把 HFile 物理排序改成倒序,也不会修复不适合范围查询的 row key 设计。
如果 row key 使用正向时间戳,反向 scan 可能方便拿最近数据;如果 row key 用反转时间戳或补码时间戳,正向 scan 就可能已经满足最新优先。选择哪一种,应回到第 03 篇的 row key 排序设计。
Java public API 示例
UNVERIFIED_RUNTIME:以下示例使用 HBase 2.x public API 形态,当前任务未在目标版本编译运行。
1 | |
最小实验
UNVERIFIED_RUNTIME:以下命令用于 HBase 2.6.6 环境复现,当前任务未采集输出。
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
实验只观察范围、limit 和 reversed 的结果边界,不记录耗时。Filter 相关实验应配合服务端扫描指标,避免把“返回少”误判为“扫描少”。
失败恢复
读路径的失败通常不是 WAL replay 级别的恢复,而是客户端 scanner 重试、Region 重新定位和应用侧分页状态恢复。一次 scan 跨多个 RPC;Region 移动、scanner lease 过期或客户端超时后,应用需要能从最后确认处理的 row key 继续。
宽行和 partial result 会放大恢复复杂度。若 batch 导致一行拆成多个 Result,应用只记录最后 row key 可能不够,还要记录列进度或避免使用会拆行的批量设置。分页任务如果要求严格不重不漏,需要把 row key、最后处理的 qualifier、版本和业务去重键一起设计。
工程迁移
| 访问需求 | 推荐读法 | 风险 |
|---|---|---|
| 单实体读取 | Get |
row key 必须已知 |
| 用户时间线 | prefix range scan | row key 要把用户和时间排在一起 |
| 后台导出 | 大 range scan 加合理 caching | scanner lease 和 Region 移动 |
| 宽行读取 | 控制 batch 或避免超宽行 | partial result 处理 |
| 翻页展示 | last row key 游标 | 并发写入下结果可能变化 |
模式速查:听到“分页、列表、最近 N 条”,先把 row-key range 画出来,再决定 caching、batch、limit 和 filter。没有 range 的 scan,通常是建模问题。
常见误解
- “Filter 就像 SQL where 一样省扫描”不准确;很多 filter 只减少返回结果。
- “caching 是 BlockCache 配置”不准确;它控制 scanner RPC 预取行数。
- “batch 是分页行数”不准确;它限制的是 Cell 数,可能拆开一行。
- “reversed scan 会改变文件排序”不准确;它只改变遍历方向。
练习
- 为
user#{userId}#{ts}设计最近 20 条读取方案,分别给出正向时间戳和反向时间戳写法。 - 解释
setCaching(1)和setLimit(1)的差异。 - 写出一个 scanner 失败后从最后 row key 恢复的处理流程。
系列导航
- 00 导读:row key 决定数据位置
- 01 数据模型:Cell、Column Family 与版本
- 02 集群架构:HMaster、RegionServer、ZooKeeper 与 hbase:meta
- 03 Schema 与 row key 设计
- 04 写入路径:WAL、MVCC、MemStore 与 flush
- 05 读取路径:BlockCache、Bloom Filter 与 HFile block
- 06 HFile 内部结构
- 07 Compaction、TTL、版本与 Delete
- 08 Region 生命周期:split、merge 与 assignment
- 09 Client 路由与重试
- 10 行级原子性与一致性边界
- 11 RegionServer 崩溃与 WAL 恢复
- 12 跨集群 Replication
- 13 Snapshot、Backup 与恢复
- 14 Get、Scan、Filter 与分页(本篇)
- 15 BufferedMutator 与 Bulk Load
- 16 Coprocessor、Endpoint 与 Phoenix 边界
- 17 Kerberos、RPC 保护与 ACL
- 18 Metrics、hbtop、Compaction 与性能调优
- 19 HBase 3.0 与设计边界
参考资料
- Apache HBase Reference Guide:https://hbase.apache.org/docs/
- Apache HBase User API:https://hbase.apache.org/2.6/apidocs/
- Apache HBase
GetAPI:https://hbase.apache.org/2.6/devapidocs/org/apache/hadoop/hbase/client/Get.html - Apache HBase
ScanAPI:https://hbase.apache.org/2.6/devapidocs/org/apache/hadoop/hbase/client/Scan.html - Apache HBase
ResultAPI:https://hbase.apache.org/2.6/devapidocs/org/apache/hadoop/hbase/client/Result.html - Apache HBase Filters:https://hbase.apache.org/2.6/apidocs/org/apache/hadoop/hbase/filter/package-summary.html
