HBase 深度
概述
HBase 是运行在 HDFS 之上的分布式列族数据库,脱胎于 Google BigTable 论文。它用稀疏的列族模型承载海量结构化数据,具备高吞吐随机读写、自动水平扩展能力,是离线数仓 ODS 与实时查询场景的常用存储。本文围绕架构、数据模型、存储结构、RowKey 设计与读写流程展开。
一、整体架构
1.1 角色分工
┌──────────────┐
│ HMaster │ 元数据管理、Region 分配、故障恢复
└──────┬───────┘
│
┌───────────┐ ┌─────┴─────┐ ┌───────────┐
│ZooKeeper │◄───►│RegionServer│◄───►│RegionServer│
│ meta 定位 │ └─────┬─────┘ └─────┬─────┘
└───────────┘ │ │
┌───┴───────────────┐
│ HDFS │ 数据最终落盘
└───────────────────┘| 角色 | 职责 |
|---|---|
| HMaster | 管理 Region 的分配与迁移、表结构的增删改、RegionServer 故障转移 |
| RegionServer | 承载 Region,处理读写请求,执行 Flush 与 Compaction |
| ZooKeeper | 保存 meta 表位置、HMaster 主备选举、RegionServer 在线状态 |
| HDFS | 存储最终落盘的 HFile 与 WAL 日志 |
HMaster 不参与数据读写路径,单点故障不会导致读写中断,只影响 DDL 与负载均衡。
1.2 寻址流程
客户端定位数据的核心是 meta 表(hbase:meta),存所有 Region 的位置信息:
1. 客户端向 ZooKeeper 读取 meta 表所在 RegionServer
2. 查询 meta 表,得到目标 RowKey 所在 Region 的 RegionServer
3. 直接连接该 RegionServer 发起读写
4. 客户端本地缓存该映射,避免每次查询 metaRegion 分裂或迁移后缓存失效,客户端抛 RegionTooBusyException / NotServingRegionException 时重查 meta 更新缓存。
二、数据模型
2.1 逻辑结构
一行数据由 RowKey + 多个列族 组成,列族下可有任意数量的列(Qualifier):
表 user
RowKey 列族 info 列族 stats
"u001" info:name=张三 stats:level=3
info:age=28 stats:lastLogin=20260804
"u002" info:name=李四 stats:level=5| 概念 | 说明 |
|---|---|
| Table | 逻辑表,按 RowKey 字典序存储 |
| RowKey | 行主键,唯一标识一行,直接决定数据落在哪个 Region |
| Column Family | 列族,物理存储的最小单元,同一列族共享一个 Store |
| Column Qualifier | 列名,无需预定义,随写随建 |
| Cell | {RowKey, CF, Qualifier, TimeStamp} 唯一确定一个值 |
| TimeStamp | 版本号,默认取写入时间,同一 Cell 可存多个版本 |
2.2 稀疏特性
不同行的列可以完全不同,空列不占存储。这与关系库的固定 Schema 形成鲜明对比,适合属性多变的数据。
| 对比项 | HBase | 关系型数据库 |
|---|---|---|
| 存储 | 列族 + 稀疏 | 行 + 固定列 |
| 扩展 | 水平自动扩展 | 垂直为主,分库分表靠人工 |
| 事务 | 行级原子性 | ACID 强一致 |
| 查询 | RowKey 点查 / Scan | 任意字段 SQL |
| 适用 | 海量写入、宽表 | 复杂关联、强一致业务 |
三、存储结构
3.1 内存与磁盘分工
RegionServer 内一个 Region 对应一个列族称为一个 Store,Store 内部是内存 MemStore + 磁盘 StoreFile 的组合:
Region (表 user, RowKey 区间 [a000, z999])
├── Store(info) → MemStore → StoreFile / HFile(落 HDFS)
└── Store(stats) → MemStore → StoreFile / HFile3.2 写入链路:WAL + MemStore
Put 请求
→ 1. 追加写入 WAL(HLog,先持久化,崩溃可恢复)
→ 2. 写入内存 MemStore(按 RowKey 排序的跳表)
→ 3. 返回客户端成功
→ 4. MemStore 达到阈值时 Flush 成 StoreFile(HFile)落盘关键点:
- WAL 先写保证数据不丢,RegionServer 宕机后从 WAL 重放恢复 MemStore 中未落盘的数据。
- MemStore 内的数据按 RowKey 有序,因此 Flush 出的 StoreFile 天然有序。
- 读请求会合并查 MemStore 与 StoreFile,所以写入后立即可读(无内存延迟)。
3.3 HFile 结构
StoreFile 底层是 HFile(基于 HDFS 的自定义文件格式):
| 部分 | 作用 |
|---|---|
| Scanned Block | 数据块,默认 64KB,按 RowKey 有序 |
| Data Index | 数据块索引,支持块级跳读 |
| Bloom Filter Block | 布隆过滤器,点查时快速排除不存在的 RowKey |
| Trailer / FileInfo | 文件尾信息与元数据 |
HFile 借助 Data Index 与 Bloom Filter 实现"跳过无关块"的高效点查。
3.4 Compaction
落盘文件随时间增多,读路径要合并的文件越多,性能下降,因此需要 Compaction:
| 类型 | 触发 | 作用 |
|---|---|---|
| Minor Compaction | StoreFile 数量超过阈值 | 合并少量相邻小文件 |
| Major Compaction | 定时(默认 7 天)或手动 | 合并全部文件、清理删除标记与过期版本 |
Major Compaction 代价高、耗 IO,生产环境常手动安排在业务低峰执行。
四、RowKey 设计
RowKey 是 HBase 性能的命门,设计要点如下。
4.1 设计原则
| 原则 | 说明 |
|---|---|
| 散列均匀 | 避免热点,常用加盐(Salt)、哈希前缀、反转 |
| 长度适中 | 建议 10~100 字节,过长浪费存储与内存 |
| 利用有序性 | 把范围查询的共同前缀放前面 |
| 避免单调递增 | 自增 ID 会让所有新数据涌向最后一个 Region |
4.2 常用策略
加盐(Salt):在 RowKey 前拼随机前缀,打散写热点。
原始 RowKey: user_10001, user_10002, ...
加盐后: 3_user_10001, 9_user_10002, ...哈希前缀:对业务主键取哈希前几位作为前缀,分布最均匀,但丢失有序性。
反转:适合以时间为主键的倒序场景,如 20260804_001 反转为 100_4060802。
4.3 多租户设计示例
订单表按"租户 ID + 时间反转"设计 RowKey:
RowKey = tenantId + (Long.MAX - timestamp)
→ 同一租户数据连续存储,且最新数据排在前面配合预分区,把 RowKey 均匀切到多个 Region,彻底规避热点。
五、读写流程
5.1 读(Get / Scan)
Get 请求
→ 1. 定位 RegionServer(meta 缓存)
→ 2. RegionServer 在 Region 内查找:
MemStore(未落盘数据)
+ BlockCache(热数据缓存)
+ 各 StoreFile(按 RowKey 二分查找,Bloom Filter 过滤)
→ 3. 合并各来源结果,返回最新版本5.2 写(Put)
Put 请求
→ 1. WAL 追加(可配置跳过 WAL:Put.setDurability(SKIP_WAL))
→ 2. MemStore 写入(跳表,保持有序)
→ 3. 返回成功
→ 4. Flush 条件满足 → 生成 StoreFile批量写建议使用 Table.put(List<Put>),一次 RPC 提交多行,大幅提升吞吐。
5.3 Flush 触发条件
| 触发 | 参数 |
|---|---|
| MemStore 大小超阈值 | hbase.hregion.memstore.flush.size(默认 128MB) |
| RegionServer 总 MemStore 超比例 | hbase.regionserver.global.memstore.size(默认 0.4) |
| WAL 文件数过多 | hbase.regionserver.hlog.blocksize 相关 |
| 手动 Flush | flush 'table' 命令 |
六、常见问题速查
| 问题 | 原因与处理 |
|---|---|
| 写热点 | RowKey 单调递增或未加盐,改为散列前缀 + 预分区 |
| 读延迟高 | StoreFile 过多未 Compaction,安排 Major Compaction |
| RegionServer 宕机 | WAL 重放恢复,等待 HMaster 转移 Region;关注堆内存 OOM |
| WAL 写成为瓶颈 | 检查磁盘与 HDFS 副本数,必要时调大 WAL 批量写 |
| meta 表损坏 | 检查 ZooKeeper 与 hbase:meta,紧急时可重建 meta |