4. HBase 核心概念与架构

HBase 这东西,说白了就是一套跑在 HDFS 上的、支持实时读写的分布式 KV 数据库。很多刚接触实时数据产品的朋友,一上来就被它的 Region、MemStore、HFile 搞晕了。别急,我带你捋一遍核心脉络。

一句话总结:HBase = 稀疏的、多维度的、排序的 Map。RowKey 是主键,Column Family 是列族,TimeStamp 是版本。

4.1 HBase 数据模型

HBase 的数据模型,你可以想象成一个巨大的 Excel 表格,但它有几个关键特性:

  • 稀疏性:允许某些列为空,不占存储空间。
  • 多维排序:按 RowKey 字典序、Column Family、Qualifier、TimeStamp 排序。
  • 版本控制:每个单元格可以保留多个历史版本。

4.1.1 RowKey 设计

RowKey 是 HBase 的唯一索引。我见过太多因为 RowKey 设计不合理导致集群性能翻车的案例。RowKey 的设计原则其实就三条:

  1. 长度原则:越短越好。建议 8~16 字节,太长浪费内存和磁盘。
  2. 散列原则:避免热点。不要用时间戳做前缀,否则写入全打到一个 Region 上。
  3. 唯一原则:保证全局唯一。

我的经验:我曾经接手过一个实时风控项目,RowKey 直接用用户 ID 倒序拼接时间戳。结果每天凌晨 0 点,新数据全往最后一个 Region 写,直接打挂一台 RegionServer。后来改成 MD5 前缀 + 时间戳,问题就解决了。

4.1.2 Column Family(列族)

Column Family 是 HBase 物理存储的基本单元。一个表可以定义 1~3 个列族,再多就会影响性能。为什么?因为每个列族对应一个独立的 Store,Region 分裂时所有列族一起分裂,IO 开销很大。

特性 说明
物理隔离 不同列族存储在不同 HFile 中
压缩独立 每个列族可单独设置压缩算法
TTL 独立 每个列族可设置不同的过期时间
数量限制 建议不超过 3 个,否则 Region 分裂时性能下降

注意:列族名和列名尽量短。HBase 存储时会完整保留这些名字,长名字会浪费大量空间。我见过有人用 "user_profile_info" 做列族名,每个单元格多占十几个字节,几亿行数据下来就是几 GB 的浪费。

4.2 RegionServer 架构

RegionServer 是 HBase 真正干活的节点。它负责管理 Region、处理读写请求、执行 Compaction 和 Split。它的内部架构,我画了一张图帮你理解:

RegionServer 内部架构 RegionServer Region 1 Store (CF1) MemStore HFile Store (CF2) MemStore HFile Region 2 Store (CF1) MemStore HFile Store (CF2) MemStore HFile WAL (Write-Ahead Log) — HDFS Block Cache (LRU) 每个 Region 包含多个 Store(对应列族),每个 Store 包含一个 MemStore 和多个 HFile

从图中可以看到,RegionServer 的核心组件包括:

  • Region:表的分片,每个 Region 包含一段连续的 RowKey 范围。
  • Store:每个列族对应一个 Store,物理上独立存储。
  • MemStore:写缓存,数据先写到这里,满了再刷到 HFile。
  • HFile:磁盘上的存储文件,不可变。
  • WAL:预写日志,保证数据不丢。
  • Block Cache:读缓存,缓存热点数据。

4.3 读写流程

理解了架构,我们来看看读写流程。这两个流程是 HBase 运维的核心,你必须烂熟于心。

4.3.1 写入流程

写入流程,我习惯把它总结为三步:写 WAL → 写 MemStore → 刷 HFile。

  1. 客户端请求:客户端通过 ZooKeeper 找到 Meta 表所在的 RegionServer,再根据 RowKey 找到目标 Region。
  2. 写 WAL:数据先追加到 WAL 日志(HDFS 上),保证宕机不丢数据。
  3. 写 MemStore:数据写入内存中的 MemStore,返回客户端成功。
  4. 刷盘(Flush):当 MemStore 达到阈值(默认 128MB),触发 Flush,生成 HFile。

关键点:写入返回成功是在第 3 步,不是第 4 步。所以 HBase 的写入延迟很低(毫秒级),但刷盘是异步的。如果 RegionServer 在刷盘前宕机,WAL 会负责恢复数据。

4.3.2 读取流程

读取流程比写入复杂一些,因为数据可能分布在 MemStore 和多个 HFile 中。

  1. 定位 Region:同样通过 ZooKeeper + Meta 表定位目标 Region。
  2. 检查 Block Cache:先查读缓存,命中直接返回。
  3. 扫描 MemStore:再查 MemStore 中的最新数据。
  4. 扫描 HFile:最后扫描磁盘上的 HFile,使用 Bloom Filter 跳过不包含目标 RowKey 的文件。
  5. 合并结果:按时间戳合并,返回最新版本。

我的经验:读性能的瓶颈往往在 HFile 扫描上。如果 HFile 数量太多(比如 Compaction 不及时),一次读取可能要扫描几十个文件。我建议监控 hbase.regionserver.hfile.count 指标,超过 50 就要考虑优化 Compaction 策略了。

4.3.3 读写流程对比

维度 写入 读取
延迟 毫秒级(写内存) 毫秒~几十毫秒(可能扫磁盘)
瓶颈 WAL 写入 HDFS 的 IO HFile 数量 + Block Cache 命中率
缓存 MemStore(写缓存) Block Cache(读缓存)
数据一致性 WAL 保证 MVCC 保证

避坑指南:我曾经遇到过一个案例,业务方批量写入 10 亿条数据,没关 WAL(设置 setWriteToWAL(false)),结果 RegionServer 宕机,数据全丢了。记住:WAL 是 HBase 数据安全的最后一道防线,除非你能接受数据丢失,否则永远不要关 WAL。

4.4 核心概念总结

嗯,到这里,HBase 的核心概念和架构就讲完了。你想想看,其实 HBase 的设计哲学很简单:

  • 写优化:顺序写 WAL + 内存写 MemStore,保证低延迟。
  • 读优化:Block Cache + Bloom Filter + 列族隔离,减少磁盘 IO。
  • 扩展性:Region 自动分裂,水平扩展无上限。

我个人觉得,理解 HBase 的关键在于记住一句话:数据先写内存,再异步刷盘,读的时候合并内存和磁盘的数据。 这个思想贯穿了 HBase 的整个设计。