基于本文回答
0
评论

讲讲MemStore 的 Flush(刷写)机制,什么情况下会触发 Flush?

在 HBase 中,MemStore 是 Region 中用于在内存中缓存写入数据的数据结构(每个 Column Family 对应一个 MemStore)。当 MemStore 积累了足够多的数据后,HBase 会将这些内存中的数据异步写入到底层文件系统(通常是 HDFS)中,形成持久化的 HFile。这个过程就叫做 Flush(刷写)

Flush 机制是 HBase 写入链路中至关重要的一环,它不仅关乎数据的持久化,还直接影响 HBase 的读写性能和内存稳定性。

下面详细讲解 MemStore Flush 机制以及触发 Flush 的几种核心场景:


一、 触发 Flush 的 6 种情况

HBase 触发 Flush 的条件可以分为 Region 级别RegionServer 级别WAL 级别时间级别 以及 手动/系统级别

1. MemStore 级别(Region 级别限制)- 最常见的正常触发

当单个 Region 内任意一个 MemStore(对应某个列族)的大小达到了配置的阈值时,就会触发该 Region 的 Flush。

  • 配置参数hbase.hregion.memstore.flush.size
  • 默认值128MB
  • 机制:一旦某个 MemStore 达到 128MB,该 Region 会进入 Flush 队列。
  • ⚠️ 核心注意:HBase 的 Flush 是 Region 级别的。也就是说,如果一个 Region 有多个列族(即多个 MemStore),只要其中一个 MemStore 达到了 128MB 触发了 Flush,该 Region 下所有的 MemStore 都会被一起强制 Flush。这也是为什么 HBase 官方强烈建议单表列族不要超过 2-3 个的原因(会导致大量小文件)。

2. RegionServer 全局内存级别(安全阀/高低水位)- 危险触发

为了防止 RegionServer 发生 OOM(内存溢出),HBase 设置了全局 MemStore 内存限制。它分为“高水位”和“低水位”。

  • 高水位(阻塞写)
    • 配置参数hbase.regionserver.global.memstore.size(老版本为 ...upperLimit
    • 默认值0.4 (即 JVM 堆内存的 40%)
    • 机制:当 RegionServer 上所有 MemStore 的总大小达到 JVM 堆内存的 40% 时,RegionServer 会阻塞所有客户端的写入请求(非常危险!),并开始强行 Flush。它会按照 MemStore 大小从大到小依次 Flush Region,直到总内存下降到低水位以下。
  • 低水位(平滑刷写)
    • 配置参数hbase.regionserver.global.memstore.size.lower.limit(老版本为 ...lowerLimit
    • 默认值0.95 (即高水位的 95%,也就是 JVM 堆内存的 0.4 * 0.95 = 0.38
    • 机制:当总内存达到低水位时,RegionServer 会在后台启动 Flush 线程,找到最大的 MemStore 提前进行 Flush,从而尽量避免触碰高水位导致阻塞写入。

3. WAL(预写日志)数量级别

HBase 写入数据时会先写 WAL,再写 MemStore。如果 RegionServer 宕机,依靠 WAL 恢复数据。如果 MemStore 一直不 Flush,WAL 日志就不能被删除(因为数据还在内存中,未落盘)。

  • 配置参数hbase.regionserver.maxlogs
  • 默认值32
  • 机制:如果未被清理的 WAL 文件数量达到了 32 个,说明内存中积累了太多未落盘的数据,此时即使 MemStore 没有达到 128MB,RegionServer 也会强制选择那些拥有最老未 Flush 数据的 Region 进行 Flush,以便推进 WAL 文件的滚动清理,释放磁盘空间并控制故障恢复的时间。

4. 定期/时间级别

为了防止某些 Region 数据写入极慢,导致数据长期驻留在内存中得不到持久化,HBase 设有定期自动 Flush 的机制。

  • 配置参数hbase.regionserver.optionalcacheflushinterval
  • 默认值3600000 毫秒(即 1 小时)
  • 机制:如果一个 Region 在 1 小时内没有任何 Flush 操作,HBase 会自动触发一次该 Region 的 Flush。
  • 注:如果在内存充足且数据极其重要的情况下,有的架构师会把这个值设为 0(关闭自动 flush),完全依赖大小限制,但这属于高阶调优。

5. 手动触发

运维人员可以通过 HBase Shell 或 API 手动触发 Flush,通常用于维护、备份或测试。

  • Shell 命令
    • flush 'tablename' (刷写整张表)
    • flush 'region_name' (刷写特定的 Region)

6. 系统操作触发

当对 Region 进行特定的管理操作时,为了保证数据的一致性,系统会自动触发 Flush:

  • Region Split(分裂):当 Region 数据过大需要分裂为两个前,必须先将内存数据 Flush 到磁盘。
  • Region Merge(合并):合并前。
  • Region Close/Move(关闭或移动):当负载均衡器(Balancer)移动 Region 到其他节点,或者关闭表时,会触发 Flush,确保没有数据遗留在旧节点的内存中。

二、 Flush 的简要流程

Flush 并不是一瞬间完成的,它主要包含三个阶段(Prepare -> Flush -> Commit):

  1. Prepare 阶段(极短时间阻塞写)
    • 获取 UpdateReadWriteLock 的写锁(阻塞客户端对该 Region 的并发写)。
    • 生成一个 MemStore Scanner。
    • 创建一个快照(Snapshot),此时当前的 MemStore 变为只读状态。
    • 立刻创建一个新的、空的 MemStore,用来接收后续新的客户端写入。
    • 释放写锁(恢复客户端写入)。这个阻塞阶段极短,通常在微秒/毫秒级。
  2. Flush 阶段(异步,不阻塞写)
    • 后台线程根据快照(Snapshot)的数据,按照 RowKey 进行排序,并将其写入到 HDFS 中,生成一个临时的 HFile。
  3. Commit 阶段
    • 将临时的 HFile 重命名为正式的 HFile,挂载到 Region 下。
    • 清空之前的内存快照。
    • 更新 WAL 的序号(Sequence ID),标记之前的 WAL 可以被清理了。

三、 总结与最佳实践

  • Flush 会产生大量小文件:每次 Flush 都会生成新的 HFile。触发过快会导致 HFile 激增,进而引发 Compaction(合并),严重消耗 CPU 和磁盘 I/O。
  • 列族不要太多:因为 Region 级别的 Flush 是“连坐”的。如果有 3 个列族,A 列族写满 128MB 触发 Flush,B 和 C 哪怕只有 1MB 也会被强制 Flush,产生大量 1MB 的碎文件。
  • 避免触发全局高水位:一旦触发 0.4 的全局限制,所有写入都会被直接 Block(阻塞),导致客户端写入延迟骤增甚至超时(常表现为 RegionTooBusyException)。需要通过调优堆内存、限制写入速度或增加 RegionServer 来解决。
右滑查看面试常问