4. HBase存储机制:Region的分裂与合并、HFile与MemStore的读写路径

好,咱们今天来聊聊HBase的存储机制。说实话,这部分内容是我当年刚接触HBase时觉得最绕的地方。Region怎么分裂?什么时候合并?数据到底是怎么从客户端写到磁盘上的?

别急,我一个一个给你拆开讲。搞懂了这些,你才算真正入了HBase的门。

4.1 Region的分裂与合并

Region是HBase负载均衡的基本单位。说白了,一张大表会被切成很多个小Region,分散在不同的RegionServer上。

4.1.1 Region分裂

Region为什么会分裂?很简单——太大了。

我个人的习惯是,当Region的大小超过某个阈值时,HBase会自动把它一分为二。这个阈值默认是10GB,但我在生产环境中通常会调小一些,比如5GB左右。为什么呢?因为分裂操作本身是有代价的。

分裂过程大致分三步:

  1. 决定分裂点:找到Region中中间的那个RowKey,作为分裂点。
  2. 创建子Region:在父Region的目录下,创建两个子Region的引用文件。
  3. 通知Master:告诉HMaster,这个Region已经分裂了,你重新分配一下负载。

嗯,这里要注意一点:分裂期间,父Region仍然可以提供服务。数据读写不会中断。这一点HBase设计得挺聪明的。

避坑指南:我曾经遇到过一个问题——频繁分裂导致集群性能抖动。后来发现是预分区没做好。如果你一开始就预估好数据分布,提前创建好足够多的Region,就能减少很多不必要的分裂。

4.1.2 Region合并

有分裂就有合并。Region合并是分裂的逆操作。

什么时候需要合并?你想想看,如果某个Region的数据被大量删除,或者数据量本身就很小,那它占着一个RegionServer的资源就不太划算。合并可以把相邻的小Region合并成一个大的。

合并有两种方式:

  • 在线合并:通过HBase Shell或API手动触发。我建议在业务低峰期做。
  • 自动合并:HBase 2.0之后引入了自动合并机制,但说实话,我一般关掉它。因为自动合并的触发条件比较难调,搞不好反而影响性能。

注意:合并操作会涉及数据的重新分布,期间会有短暂的IO压力。如果你在线上环境做合并,记得先评估一下集群的负载情况。

4.2 HFile与MemStore的读写路径

这部分是HBase存储的核心。我把它拆成写路径和读路径来讲。

4.2.1 写路径:从客户端到磁盘

数据写入HBase,大致要经过这么几个环节:

  1. 客户端写入WAL:Write-Ahead Log,预写日志。数据先写WAL,保证宕机不丢数据。
  2. 写入MemStore:内存中的写缓存。数据在这里暂存,达到阈值后刷写到磁盘。
  3. 刷写为HFile:MemStore满了(默认128MB),就会刷写成一个HFile文件。
  4. HFile合并:小HFile太多会影响读性能,后台会有合并线程把它们合并成大文件。

我举个例子你就明白了。假设你要往一张表里写100万条数据:

// 伪代码示意
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn("cf".getBytes(), "name".getBytes(), "张三".getBytes());
table.put(put);

这条数据会先写到WAL日志里,然后进入MemStore。当MemStore满了,或者达到刷写间隔(默认1小时),就会生成一个HFile文件。

关键点:HFile是不可变的。一旦生成,就不会再修改。后续的更新和删除,都是通过追加新数据来实现的。这就是为什么HBase需要定期做合并(Compaction)。

4.2.2 读路径:从磁盘到客户端

读路径比写路径复杂一些。为什么?因为数据可能分散在多个地方。

读数据时,HBase会依次检查:

  • BlockCache:读缓存。如果数据在缓存里,直接返回,速度极快。
  • MemStore:还没刷盘的数据,也在内存里。
  • HFile:磁盘上的文件。需要扫描HFile的索引来定位数据。

我个人的经验是,读性能的瓶颈往往在HFile的扫描上。如果HFile太多,或者索引太大,读延迟就会飙升。

这里有个小技巧:

优化建议:合理设置BlockCache的大小。我一般给BlockCache分配堆内存的20%-30%。太小了命中率低,太大了又挤占MemStore的空间。这个平衡点需要根据你的读写比例来调。

4.3 总结一下

Region的分裂与合并,解决的是数据分布的问题。HFile和MemStore,解决的是数据读写效率的问题。

说白了,HBase的存储机制就是一套「先写内存、再刷磁盘、定期合并」的流程。你只要记住这个核心逻辑,后面再遇到什么性能问题,都能顺着这个思路去排查。

我记得有一次线上问题,就是MemStore刷写太频繁导致的。后来调大了MemStore的大小,问题就解决了。所以你看,理解底层机制,对排查问题真的很有帮助。

下一章,咱们聊聊HBase的Compaction机制。这个也是个大坑,提前给你打个预防针。