HBase写数据过程

HBase 写数据流程:又快又不丢,靠的是”先记日志、再写内存、最后刷盘”

HBase 写入数据,面临两个矛盾的要求:

  • 要快:高吞吐写入,不能等磁盘 IO
  • 要可靠:RegionServer 宕机了,数据不能丢

HBase 的解法是三步走

  1. 先写日志(WAL)——记下来,保证不丢
  2. 再写内存(MemStore)——快,写完就返回客户端
  3. 最后异步刷盘——攒够了再写到磁盘

核心逻辑:写日志(慢但可靠)+ 写内存(快但易失)→ 两者结合 = 又快又不丢。

写一条数据,HBase 经历了什么?

1. 客户端发请求 → RegionServer
2. 写 WAL(预写日志,HDFS 持久化)
3. 写 MemStore(内存)
4. 返回客户端"成功"
5. (异步)MemStore 满了 → 刷盘成 StoreFile
6. (异步)小 StoreFile 合并成大文件(Compaction)

客户端在第 4 步就收到成功了,第 5、6 步是后台慢慢做的。

graph TD
    A[客户端] -->|1. 发送写请求| B[目标 RegionServer]
    B -->|2. 写入预写日志WAL/HLog<br/>同步到 HDFS 多副本| C[HLog 持久化]
    C -->|3. 写入内存缓存MemStore<br/>按 RowKey 排序| D[MemStore]
    D -->|4. 立即反馈| A
    D -->|5. 触发刷盘条件?<br/>大小/时间阈值等| E{判断}
    E -->|是| F[MemStore 刷盘<br/> 生成 StoreFile]
    E -->|否| D
    F -->|6. 小文件数量/大小达标?| G{判断}
    G -->|是| H[StoreFile 合并<br/>Minor/Major Compaction]
    G -->|否| F
    H -->|7. Region 大小超限?| I{判断}
    I -->|是| J[Region 拆分<br/> 子 Region 负载均衡]
    I -->|否| H
    J -->|8. 新 Region 分配到其他 RegionServer| B

第一步:定位 RegionServer

写数据之前,客户端要先知道”这条数据写到哪个 RegionServer 上”。

流程跟读数据一样:

  1. 客户端连 ZooKeeper,找 hbase:meta 表的位置
  2. hbase:meta 表,根据 RowKey 定位目标 Region 和 RegionServer
  3. 客户端直连那个 RegionServer

定位完成之后,后面的写操作都在这个 RegionServer 上完成。

第二步:写 WAL——“我先记下来,保证不丢”

WAL(Write-Ahead Log)是 HBase 数据可靠性的第一道防线。

写 WAL 的流程:

  1. RegionServer 收到写请求
  2. 先把操作记录到 HLog 文件(存在 HDFS 上,多副本)
  3. HLog 写入成功,才进入下一步

为什么先写 WAL?

如果 RegionServer 宕机了,MemStore 里的数据还没刷盘,就会丢失。有了 WAL,重启后可以重放日志,恢复数据。

WAL 存在 HDFS 上,默认 3 副本,磁盘坏了也不丢。

写 WAL 的成本: 要写 HDFS,有网络 IO 和磁盘 IO。为了降低延迟,HBase 支持 WAL 异步写入(hbase.regionserver.wal.async=true),但会牺牲一点可靠性。

第三步:写 MemStore——“进内存,马上回复”

WAL 写完后,数据写入 MemStore(内存缓存)。

MemStore 的特性:

  • 每个列族一个 MemStore
  • 数据按 RowKey 排序存储
  • 写入速度极快(纯内存操作)

写完 MemStore 后,RegionServer 立即返回客户端”写入成功”。

这时候数据还没有落盘——它在内存里。 这也是 HBase 写入快的原因:写操作不走磁盘随机 IO,只走顺序写 WAL + 内存写 MemStore。

第四步:异步刷盘——“内存满了,写到磁盘”

MemStore 不能无限增长,否则内存会爆。达到阈值后,后台线程把 MemStore 刷成磁盘上的 StoreFile(HFile 格式)。

刷盘触发条件(满足任一):

条件 默认值 说明
MemStore 大小超限 128MB 单个 MemStore 达到阈值
时间超限 1 小时 数据在 MemStore 里太久没刷
Region 总内存超限 堆内存 × 0.4 / Region 数 Region 级别限制
手动触发 flush '表名'

刷盘过程:

  1. MemStore 加锁(短暂阻塞新写入)
  2. 数据排序后写入临时文件
  3. 临时文件转正为 StoreFile
  4. 释放锁,清理 MemStore

刷盘期间会有毫秒级的写入阻塞,但通常很快。

第五步:Compaction——“小文件合并成大文件”

频繁刷盘会产生大量小 StoreFile。读数据的时候要扫多个文件,效率低。

HBase 的解法:后台合并(Compaction)。

类型 触发条件 做什么
Minor Compaction StoreFile 数量超过 3 个 合并几个小文件成一个大文件,不删过期数据
Major Compaction 默认 7 天一次 合并所有文件成一个大文件,删除过期数据

Major Compaction 会清理掉 TTL 过期、版本超限的数据,释放磁盘空间。 但 Major Compaction 资源消耗大,生产环境通常会调大间隔或安排到低峰期。

第六步:Region Split——“Region 太大了,拆成两个”

Region 的数据量持续增长,超过阈值(默认 10GB)后会自动拆分成两个子 Region。

  1. 选择中间 RowKey 作为拆分点
  2. 生成两个子 Region
  3. HMaster 把子 Region 分配到不同 RegionServer

Split 保证了单个 Region 不会过大,数据分布均匀,读写压力分散。

故障恢复:WAL 怎么救命?

RegionServer 宕机了,MemStore 里没刷盘的数据怎么办?

1. ZooKeeper 检测到 RegionServer 心跳丢失 → 通知 HMaster
2. HMaster 把宕机节点的 Region 分配给其他 RegionServer
3. 新 RegionServer 加载 Region 后,读取 HLog,重放该 Region 的操作
4. 数据恢复到 MemStore,然后刷盘
5. Region 对外提供服务

没有 WAL,宕机就丢数据。有 WAL,宕机只是慢一点恢复。

写流程的优化手段

优化方向 手段
减少网络往返 批量写入(table.put(List<Put>)
降低 WAL 延迟 异步 WAL(牺牲一点可靠性换速度)
减少刷盘压力 调大 MemStore 阈值
减少 Compaction 影响 限制 Compaction 带宽、调大 Major Compaction 间隔