HBase MemStore简介
MemStore 是 HBase 非常重要的组成部分,MemStore 作为 HBase 的写缓存,保存着数据的最近一次更新,同时是HBase能够实现高性能随机读写的重要组成。
MemStore
HBase MemStoreHBase Table 的每个 Column family 维护一个 MemStore,当满足一定条件时 MemStore 会执行一次 flush,文件系统中生成新的 HFile。而每次 Flush 的最小单位是 Region。
MemStore的主要作用:
-
更新数据存储在 MemStore 中,使用 LSM(Log-Structured Merge Tree)数据结构存储,在内存内进行排序整合。即保证写入数据有序(HFile中数据都按照RowKey进行排序),同时可以极大地提升HBase的写入性能。
-
作为内存缓存,读取数据时会优先检查 MemStore,根据局部性原理,新写入的数据被访问的概率更大。
-
在持久化写入前可以做某些优化,例如:保留数据的版本设置为1,持久化只需写入最新版本。
如果一个 HRegion 中 MemStore 过多(Column family 设置过多),每次 flush 的开销必然会很大,并且生成大量的 HFile 影响后续的各项操作,因此建议在进行表设计的时候尽量减少 Column family 的个数。
Flush 时机
MemStore 无论是对 HBase 的写入还是读取性能都至关重要,其中 flush 操作又是 MemStore 最核心的操作。MemStore 在多种情况下会执行一次 Flush 操作:
再次注意,MemStore 的最小 flush 单元是 HRegion 而不是单个 MemStore。
-
hbase.hregion.memstore.flush.size
默认值:128M
MemStore 级别限制,当 Region 中任意一个 MemStore 的大小(压缩后的大小)达到了设定值,会触发 MemStore flush。 -
hbase.hregion.memstore.block.multiplier
默认值:2
Region 级别限制,当 Region 中所有 MemStore 的大小总和达到了设定值(hbase.hregion.memstore.block.multiplier * hbase.hregion.memstore.flush.size,默认 2* 128M = 256M),会触发 MemStore flush。 -
hbase.regionserver.global.memstore.upperLimit
默认值:0.4
Region Server 级别限制,当一个 Region Server 中所有 MemStore 的大小总和达到了设定值(hbase.regionserver.global.memstore.upperLimit * hbase_heapsize,默认 0.4 * RS堆内存大小),会触发全部 MemStore flush。 -
hbase.regionserver.global.memstore.lowerLimit
默认值:0.38
与 hbase.regionserver.global.memstore.upperLimit 类似,区别是:当一个 Region Server 中所有 MemStore 的大小总和达到了设定值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize,默认 0.38 * RS堆内存大小),会触发部分 MemStore flush。Flush 顺序是按照 Region 的总 MemStore 大小,由大到小执行,先操作 MemStore 最大的 Region,再操作剩余中最大的 Region,直至总体 MemStore 的内存使用量低于设定值(hbase.regionserver.global.memstore.lowerLimit * hbase_heapsize)。
-
hbase.regionserver.maxlogs
默认值:32
当一个 Region Server 中 HLog 数量达到设定值,系统会选取最早的一个 HLog 对应的一个或多个 Region 进行 flush。当增加 MemStore 的大小以及调整其他的 MemStore 的设置项时,也需要去调整 HLog 的配置项。否则,WAL的大小限制可能会首先被触发。因而,将利用不到其他专门为Memstore而设计的优化。
需要关注的 HLog 配置是 HLog 文件大小,由参数 hbase.regionserver.hlog.blocksize 设置(默认512M),HLog 大小达到上限,或生成一个新的 HLog
通过WAL限制来触发Memstore的flush并非最佳方式,这样做可能会会一次flush很多Region,尽管“写数据”是很好的分布于整个集群,进而很有可能会引发flush“大风暴”。
-
hbase.regionserver.optionalcacheflushinterval
默认值:3600000
HBase 定期刷新 MemStore,默认周期为1小时,确保 MemStore 不会长时间没有持久化。为避免所有的 MemStore 在同一时间都进行 flush,定期的 flush 操作有 20000 左右的随机延时。 -
手动触发
用户可以通过shell命令一下分别对一个 Table 或者一个 Region 进行 flush:
hbase> flush 'TABLENAME'
hbase> flush 'REGIONNAME' -
其他
执行 Compact 和 Split 之前,会进行一次 flush。
Flush 阻止更新的情况
-
出现上述(2)的情况,Region 下所有 Memstore 的总大小超过了 MemStore 默认大小的倍数,该 Region 在 flush 完成前会 block 新的更新请求。
-
出现上述(3)的情况,RegionServer 所有 MemStore 占整个堆的最大比例超过 hbase.regionserver.global.memstore.upperLimit 设置值,该 RegionServer 的更新请求会被 block,一直到 MemStore 恢复阈值一下。
更新被阻塞对单个节点和整个集群的影响都很大,需要关注 MemStore 的大小和 Memstore Flush Queue 的长度。
Memstore Flush 流程
为了减少 flush 过程对读写的影响,HBase 采用了类似于两阶段提交的方式,将整个 flush 过程分为三个阶段:
-
prepare 阶段:遍历当前 Region 中的所有 MemStore,将 MemStore 中当前数据集 kvset 做一个快照 snapshot,然后再新建一个新的 kvset,后期的所有写入操作都会写入新的 kvset 中。整个 flush 阶段读操作读 MemStore 的部分,会分别遍历新的 kvset 和 snapshot。prepare 阶段需要加一把 updateLock 对写请求阻塞,结束之后会释放该锁。因为此阶段没有任何费时操作,因此持锁时间很短。
-
flush 阶段:遍历所有 MemStore,将 prepare 阶段生成的 snapshot 持久化为临时文件,临时文件会统一放到目录.tmp下。这个过程因为涉及到磁盘IO操作,因此相对比较耗时。
-
commit 阶段:遍历所有的 MemStore,将 flush 阶段生成的临时文件移到指定的 Column family 目录下,生成对应的 Storefile(HFile) 和 Reader,把 Storefile 添加到 HStore 的 Storefiles 列表中,最后再清空 prepare 阶段生成的 snapshot。
上述 flush 流程可以通过日志信息查看:
/******* prepare 阶段 ********/
2018-07-06 18:33:31,329 INFO [MemStoreFlusher.1] regionserver.HRegion: Started memstore flush for [table],,1528539945017.80ab9764ae70fa97b75057c376726653., current region memstore size 21.73 MB, and 1/1 column families' memstores are being flushed.
/******* flush 阶段 ********/
2018-07-06 18:33:31,696 INFO [MemStoreFlusher.1] regionserver.DefaultStoreFlusher: Flushed, sequenceid=40056, memsize=21.7 M, hasBloomFilter=true, into tmp file hdfs://ns/hbase/data/default/[table]/80ab9764ae70fa97b75057c376726653/.tmp/f71e7e8c15774da683bdecaf7cf6cb99
/******* commit 阶段 ********/
2018-07-06 18:33:31,718 INFO [MemStoreFlusher.1] regionserver.HStore: Added hdfs://ns/hbase/data/default/[table]/80ab9764ae70fa97b75057c376726653/d/f71e7e8c15774da683bdecaf7cf6cb99, entries=119995, sequenceid=40056, filesize=7.3 M
整个 flush 过程可能涉及到 compact 操作和 split 操作,因为过于复杂,不做详细讲解。
MemStore 对业务的影响
正常情况下,大部分 Memstore Flush 操作都不会对业务读写产生太大影响,比如:定期刷新 MemStore、手动触发、单个 MemStore flush、Region 级别的 flush 以及超过 HLog 数量限制等情况,这几种场景只会短暂的阻塞对应 Region 上的写请求,阻塞时间很短,毫秒级别。
然而一旦触发 Region Server 级别的限制导致 flush,就会对用户请求产生较大的影响。会阻塞所有落在该 RegionServer 上的更新操作,阻塞时间很长,甚至可以达到分钟级别。
导致触发 RegionServer 级别限制的主要因素:
- Region Server 上运行的 Region 总数
Region 越多,Region Server 上维护的 MemStore 就越多。根据业务表读写请求量和 RegionServer 可分配内存大小,合理设置表的分区数量(预分区的情况)。
- Region 上的 Store 数(表的 Column family 数量)
每个 Column family 会维护一个 MemStore,每次 MemStore Flush,会为每个 Column family 都创建一个新的 HFile。当其中一个CF的 MemStore 达到阈值 flush 时,所有其他CF的 MemStore 也会被 flush,因此不同CF中数据量的不均衡将会导致产生过多 HFile 和小文件,影响集群性能。很多情况下,一个CF是最好的设计。
频繁的 MemStore Flush
频繁的 MemStore Flush 会创建大量的 HFile。在检索的时候,就不得不读取大量的 HFile,读性能会受很大影响。为预防打开过多 HFile 及避免读性能恶化(读放大),HBase 有专门的 HFile 合并处理(HFile Compaction Process),根据一定的策略,合并小文件和删除过期数据。后续的文章会有详细介绍。