深入 HBase 00 - 导读:row key 决定数据位置
前置问题与边界
HBase 能在 HDFS 上提供随机读写,不是因为 HDFS 获得了原地随机修改能力。HBase 把随机写接在 RegionServer 服务层:mutation 先经过 WAL 和 MemStore,随后 flush 成 HDFS 上不可变的 HFile。随机读也不是对 HDFS 文件做逐字节定位;客户端先定位 row key 所属 Region,再直连负责该 Region 的 RegionServer,由 RegionServer 合并 MemStore、BlockCache 和 HFile 中的结果。
本篇回答一个核心问题:HBase 为什么能在面向大文件的 HDFS 之上提供在线随机读写,代价落在哪里。row key 的 salt、反转和时间桶放到第 03 篇;WAL、MVCC、MemStore 与 flush 的时序放到第 04 篇;BlockCache、Bloom Filter 和 HFile 放到第 05、06 篇;Compaction、恢复、复制和运维会在后续篇目展开。
基础机制以 HBase 2.6.6、Hadoop 3.4.3、JDK 17 为基线。本篇不讨论大版本兼容性变化,只建立 2.6 路径下的基本对象关系。
HBase 是稀疏宽列模型。一个 Cell 的坐标可以表达为 (row, family, qualifier, timestamp):row key 决定排序和位置,column family 是预先定义的物理组织边界,qualifier 可以在同一列族内动态出现,timestamp 让同一坐标保存多个版本。这个模型不同于固定列的关系表,也不同于 Parquet、ORC 这类主要面向分析扫描的列存格式。
对象路径图
flowchart TD
C[Client] -->|locate row key| M[hbase:meta]
M -->|region location| C
C -->|RPC| RS[RegionServer]
RS --> R[Region: row-key range]
R --> W[WAL on HDFS]
R --> MS[MemStore]
MS -->|flush| HF[HFile on HDFS]
HF --> BC[BlockCache]
HF --> CP[Compaction]
这张图有两个边界。第一,hbase:meta 提供 Region 位置信息,客户端拿到后会缓存;普通数据读写不经过 HMaster。第二,HDFS 保存 WAL 和 HFile,HBase 负责表、行、版本、Region、缓存和恢复语义,二者不能互相替代。
RowKey 是位置、排序和扫描键
row key 按字节字典序排序。这个排序决定一行数据落入哪个 Region,也决定相邻数据能否被一次连续 scan 读出。关系数据库的主键通常先被理解为唯一标识;HBase 的 row key 同时还是分布键和范围扫描键。
单调递增的时间戳前缀会把新写入推向末端 Region,扩容 RegionServer 也不能立即摊平一个热点 range。天然分散的用户前缀、反转域名、salt 前缀和时间桶都在改变“相邻”的含义。设计 row key 的任务不是让字符串好看,而是让常见访问路径在有序 key 空间里形成合理分布。
Region 是表的水平切分单位
一张表由多个 Region 组成,每个 Region 覆盖一段连续 row-key range。同一时刻,一个 Region 由一个 RegionServer 提供服务。表增长后,HBase 通过 split 把 key 空间拆成更多 Region,再由 assignment 和 balancer 把 Region 分散到不同 RegionServer。
Region 的存在解释了 HBase 的伸缩方式。增加节点只增加承载 Region 的位置;是否能摊开流量,仍取决于 row key 是否把请求分散到多个 range。一个永远只打到同一前缀的写入模型,即使集群有很多 RegionServer,也会先压住少数 Region。
WAL、MemStore 和 HFile 接住随机写
HDFS 擅长追加和顺序读写,不擅长频繁原地修改。HBase 的写路径把每次 mutation 记录到 WAL,并放入内存中的 MemStore。MemStore 达到阈值或被触发后,数据 flush 成 HDFS 上不可变的 HFile。
一次 Put 返回成功,只说明该 mutation 已按当前 durability 设置完成 HBase 写入协议。它不等于数据已经进入最终 HFile。flush 前的 RegionServer 崩溃依赖 WAL 重放恢复;flush 后的数据进入 HFile,但后台仍可能被 compaction 合并到新的 HFile。
读取成本由多个结构共同收敛
读取一行时,RegionServer 要合并 MemStore、BlockCache 和 HFile 中的候选 Cell。MemStore 保存尚未 flush 的新数据,BlockCache 缓存热点 block,HFile index 和 Bloom Filter 用来缩短文件查找路径。StoreFile 越多,读路径可能要检查的文件越多;BlockCache 命中率、Bloom Filter 配置和 compaction 进度会直接影响读延迟。
这也是 HBase 和分析型列存的分界。分析型列存主要优化大范围列扫描、压缩和向量化;HBase 优先服务按 row key 的随机读写和有序范围 scan。
关键对象和状态
| 对象 | 位置 | 作用 | 易错边界 |
|---|---|---|---|
| RowKey | 客户端与 RegionServer | 定位、排序、范围扫描 | 不是只用于唯一标识 |
| Region | HBase 服务层 | row-key range 分片 | 扩容机器不等于自动消除单 range 热点 |
hbase:meta |
HBase 系统表 | 保存 Region 位置 | 不是 ZooKeeper 中的全部元数据 |
| RegionServer | HBase 服务进程 | 承载 Region 读写 | 普通读写不经 HMaster |
| WAL | HDFS 文件 | 崩溃恢复日志 | durability 设置影响返回边界 |
| MemStore | RegionServer 内存 | 尚未 flush 的有序数据 | 成功返回不等于已进入 HFile |
| HFile | HDFS 文件 | flush 后不可变数据 | StoreFile 数量影响读放大 |
| BlockCache | RegionServer 内存 | 缓存读 block | 缓存不能消除所有磁盘访问 |
| Compaction | 后台任务 | 合并 StoreFile | tombstone 和旧版本清理有条件 |
最小实验
UNVERIFIED_RUNTIME:当前任务环境未安装 HBase 2.6.6、Hadoop 3.4.3 和 JDK 17 组合,以下步骤只作为可复现实验,不提供伪造输出、耗时或日志。
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
1 | |
这组步骤只观察三件事:scan 按 row key 字典序返回,STARTROW 到 STOPROW 表达连续 range,flush 后 HFile 位于 table、Region、column family 目录之下。STOPROW 是排他边界。若集群使用自定义 hbase.rootdir,HDFS 路径以实际配置为准。
清理命令:
1 | |
1 | |
1 | |
失败恢复预告
写入返回后、MemStore 尚未 flush 前,RegionServer 崩溃会把问题交给 WAL、ServerCrashProcedure、WAL split/replay 和 Region reassignment。HDFS 只保证底层 WAL 文件和 HFile 副本,HBase 才负责把这些文件重新解释成表、Region 和 Cell 的可见状态。
复制链路也沿 WAL 展开。WAL edit 可以异步送到远端集群,但复制不是跨集群同步事务。第 12 篇会单独讨论顺序、延迟、冲突和故障切换。
工程迁移
| HBase 机制 | 可迁移问题 | 相似系统 |
|---|---|---|
| WAL + MemStore | 随机写如何变成追加日志和内存结构 | LSM memtable、redo log |
| HFile | 持久化数据如何避免原地修改 | SSTable、Lucene segment |
| Region | 有序 key 空间如何切分和迁移 | Tablet、Shard、Partition |
| BlockCache | 热读如何避开磁盘 | Page cache、query cache |
| Compaction | 后台合并如何换前台读成本 | SSTable compaction、segment merge |
[PATTERN] 先把随机写变成追加日志和内存有序结构,再用不可变文件和后台合并收敛读放大。这个模式能迁移到 LSM 数据库、搜索引擎 segment 和日志结构存储,但不能迁移出相同的一致性语义。
常见误解
误解一:HBase 是 HDFS 的随机写插件。HBase 没有改变 HDFS 的原地修改能力,它在 HDFS 之上构建服务层。
误解二:HBase 表像 MySQL 表一样有固定列。HBase 的列族预定义,qualifier 可动态出现,不存在的 Cell 不占位。
误解三:HBase 是分析型列存数据库。HBase 按 column family 物理组织,但目标是在线随机读写和有序 scan,不是替代 Parquet/ORC。
误解四:ZooKeeper 保存所有 HBase 元数据。持久 Region 路由在 hbase:meta,协调状态和服务发现信息需要分开描述。
误解五:HBase 提供跨行事务。HBase 的普通强边界是单行原子性,不能扩展成跨行、跨表事务。
练习
-
为“按用户查询最近 30 天行为”设计一个 row key,并标出它会把热点推向哪里。
-
解释为什么时间戳作为 row key 前缀容易制造单 Region 写热点。
-
把一次
Put拆成 WAL、MemStore、flush、HFile 四个阶段,标出每个阶段的崩溃恢复依据。 -
对比 HDFS 文件读取和 HBase 单行读取,指出 NameNode、DataNode、
hbase:meta、RegionServer 各自出现的位置。 -
观察一张已有 HBase 表的 Region 列表,判断当前热点是否能由 row-key range 解释。
系列导航
| 篇号 | 主题 | 状态 |
|---|---|---|
| 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 Data Model:https://hbase.apache.org/docs/datamodel/
- Apache HBase Architecture:https://hbase.apache.org/docs/architecture/
- Apache HBase Client:https://hbase.apache.org/docs/architecture/client/
- Apache HBase Regions:https://hbase.apache.org/docs/architecture/regions/
- Apache HBase RegionServer:https://hbase.apache.org/docs/architecture/regionserver/
- Apache HBase HFile Format:https://hbase.apache.org/docs/hfile-format/
- Apache HBase ACID Semantics:https://hbase.apache.org/acid-semantics/
- Apache Hadoop 3.4.3 Documentation:https://hadoop.apache.org/docs/r3.4.3/
- Fay Chang 等,Bigtable: A Distributed Storage System for Structured Data:https://research.google/pubs/bigtable-a-distributed-storage-system-for-structured-data/
