HBase写数据过程
HBase 写数据流程:又快又不丢,靠的是”先记日志、再写内存、最后刷盘”
HBase 写入数据,面临两个矛盾的要求:
- 要快:高吞吐写入,不能等磁盘 IO
- 要可靠:RegionServer 宕机了,数据不能丢
HBase 的解法是三步走:
- 先写日志(WAL)——记下来,保证不丢
- 再写内存(MemStore)——快,写完就返回客户端
- 最后异步刷盘——攒够了再写到磁盘
核心逻辑:写日志(慢但可靠)+ 写内存(快但易失)→ 两者结合 = 又快又不丢。
写一条数据,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 上”。
流程跟读数据一样:
- 客户端连 ZooKeeper,找
hbase:meta表的位置 - 查
hbase:meta表,根据 RowKey 定位目标 Region 和 RegionServer - 客户端直连那个 RegionServer
定位完成之后,后面的写操作都在这个 RegionServer 上完成。
第二步:写 WAL——“我先记下来,保证不丢”
WAL(Write-Ahead Log)是 HBase 数据可靠性的第一道防线。
写 WAL 的流程:
- RegionServer 收到写请求
- 先把操作记录到 HLog 文件(存在 HDFS 上,多副本)
- 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 '表名' |
刷盘过程:
- MemStore 加锁(短暂阻塞新写入)
- 数据排序后写入临时文件
- 临时文件转正为 StoreFile
- 释放锁,清理 MemStore
刷盘期间会有毫秒级的写入阻塞,但通常很快。
第五步:Compaction——“小文件合并成大文件”
频繁刷盘会产生大量小 StoreFile。读数据的时候要扫多个文件,效率低。
HBase 的解法:后台合并(Compaction)。
| 类型 | 触发条件 | 做什么 |
|---|---|---|
| Minor Compaction | StoreFile 数量超过 3 个 | 合并几个小文件成一个大文件,不删过期数据 |
| Major Compaction | 默认 7 天一次 | 合并所有文件成一个大文件,删除过期数据 |
Major Compaction 会清理掉 TTL 过期、版本超限的数据,释放磁盘空间。 但 Major Compaction 资源消耗大,生产环境通常会调大间隔或安排到低峰期。
第六步:Region Split——“Region 太大了,拆成两个”
Region 的数据量持续增长,超过阈值(默认 10GB)后会自动拆分成两个子 Region。
- 选择中间 RowKey 作为拆分点
- 生成两个子 Region
- 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 间隔 |