MemStore 详解
Store
A Store hosts a MemStore and 0 or more StoreFiles (HFiles). A Store corresponds to a column family for a table for a given region.
- 多行RowKey与Column Family相交的地方构成Stroe,每个Region 包含多个Store。
- 每个Store由一个MemStore和0个或多个StoreFile 组成。
MemStore
MemStore保存了对Store内存的修改,修改Cells单元或KeyValue键值对数据。有刷写请求时,当前的MemStore会被移动到一个快照并被清除,HBase继续提供来自新的MemStore的编辑服务,并且支持快照,直到flush报告刷写成功。此时该快照才会被丢弃。
Note:当发生新的刷写请求时,同一个Region下的所有MemStore都将会被刷写。
为什么要有MemStore?
为了方便查询,存储在HDFS上的Hfile需要按照RowKey排序,而HDFS本身设计为顺序写,只支持追加的方式写入,而不允许修改。这样的话HBase就不能高效的写数据,因为将写入到HBase的数据不被排序就意味着没有检索优化。为了解决这个问题,HBase将最近接受到的数据缓存到内存中,在持久化到HDFS之前完成排序,然后快速顺序写入到HDFS。
Note :存储在HDFS中的Hfie不仅仅是排序后的列表。
除此之外MemStore还有其他好处:
- 作为一个内存级缓存,缓存最近增加的数据,通常新的数据往往比老的数据使用频繁。
- 在持久化到HDFS之前,可以对Rows/Cells做一些优化,当数据的Version为1的时候,MemStore可能存了多个对该Cell的更新,此时写入到HFile时,没必要全部保存,只存储最最后一个最新的版本,将其他之前的版本抛弃。
Note:每次Flush都会为Column Famliy 创建一个新的HFile,这样在查询时效率会相对较高一些,HBase会先检查请求数据是否在MemStore中,然后再检查Block Cache中,最后检查HFile,最终会返回一个merge的结果给用户。
MemStore Flush
每个MemStore都有一定的阈值,当MemStore达到阈值大小时会进行 Flush。
Note:The minimum flush unit is per region, not at individual MemStore level.
A MemStore flush can be triggered under any of the conditions listed as below:
-
Region级的刷写:
某个MemStore达到"hbase.hregion.memstore.flush.size, 128M by default"时,当前MemStore所属Region对应的所有MemStore都会刷写。
-
RegionServer全局触发刷写:
当Regionserver中Memstore的总大小达到"heapsize*0.4*0.95"时该RegionServer下所有的MemStore都会被刷写,直到MemStore的总大小低于hbase.regionserver.global.memstore.upperLimit,刷写顺序为MemStore的使用降序。
-
WAL(Hlog)引起RegionServer触发全局刷写:
当WAL 大小超过 "hbase.regionserver.maxlogs * hbase.regionserver.hlog.blocksize (2GB by default)"时,所有的MemStore都会被刷写,直到WAL中文件的个数小于hbase.regionserver.max.logs,刷写顺序基于时间,时间最久的MemStore将会被率先刷写。
当WAL 中的文件个数超过 "hbase.regionserver.max.logs, 32 by default"时,所有的MemStore都也会被刷写,现在这个值已经不可以设置。
-
当自动刷写的时间到了也会触发memstoreflush,自动刷新的时间间隔默认1小时。
由于MemStore的频繁刷写会对HBase集群的读写性能产生严重的影响,并有可能带来一些额外的负载,同时MemStore的刷写方式可能会影响Schema的设计,所以我们要对MemStore的一些配置格外关注。
对于MemStore的刷写,通常有两类配置项:
-
决定Flush的触发时机。
- hbase.hregion.memstore.flush.size
- hbase.regionserver.heapsize * 0.4 * 0.95
Note:在我们设置第一个参数即每个MemStore的内存大小时,一定要考虑当前RegionServer所管理的Region的个数以及总内存的大小,否则可能一开始的时候Region较少,但随着Region的增多,虽然每个MemStore的大小都没有达到刷写条件,但是总的内存大小已经达到了总内存大小的限制,会使刷写提前触发。
-
决定Flush的触发时更新被阻断。
-
hbase.hregion.memstore.flush.size * 4
当某个MemStore达到hbase.hregion.memstore.flush.size * 4 即512M时会阻止继续往该memstore写数据。 -
hbase.regionserver.global.memstore.upperLimit
当ReigonServer内所有Region的Memstores所占用内存总和达到heapsize的40%时,HBase会强制block所有的更新并flush这些region以释放所有memstore占用的内存,阻断更新请求。 -
hbase.regionserver.global.memstore.lowerLimit
当Regionserver中Memstore的总大小达到"hbase.regionserver.global.memstore.lowerLimit"时不会flush所有的Memstore。它会找一个Memstore内存占用最大的Region,做个别flush,此时写更新还是会被block。hbase.regionserver.global.memstore.lowerLimit = 'heapsize*0.35'
-
当HFile的数量超过一定的阈值也会产生写阻塞。
-
第一组配置时关于正常的Flush,这类flush发生时并不会影响写请求。
第二组配置主要出于安全考虑,有时候集群的"写负载"非常高写入量一直超过flush的量,这是我们就希望MemStore不要超过一定的安全设置。这种情况下写操作就要被阻止,直到MemStore恢复到一个可管理的大小。
提示:密切关注Memstore的大小和Memstore Flush Queue的Size大小。理想情况下,Memstore的大小不应该达到hbase.regionserver.global.memstore.upperLimit的设置,Memstore Flush Queue 的size不能持续增长。某个节点“写阻塞”对该节点来说影响很大,但是对于整个集群的影响更大。HBase设计为:每个Region仅属于一个RS但是“写负载”是均匀分布于整个集群(所有Region上)。有一个如此“慢”的节点,将会使得整个集群都会变慢(最明显的是反映在速度上)。
WAL(Hlog) Size:
- 每当RegionServer收到写请求时,RegionServer会将请求转至相应的Region,每个Region的Column Family数据都存储在Store中,而Store由MemStore和StoreFile组成,MemStore位于RegionServer的内存中,当RegionServer处理写请求时,先将数据写入到MemStore进行排序,然后在刷写到HFile。但由于MemStore是基于内存的,存在RegionSever宕掉时数据丢失的风险,所以,当数据被写入时会默认先写入Write-ahead Log(WAL)。WAL中包含了所有已经写入MemStore但还未Flush到HFile的更改(edits),然后再将数据写入到MemStore,在MemStore中数据还没有持久化,虽然可能RegionSever宕掉,但是可以使用WAL恢复数据。
- 但是当WAL中数据变的很大时,恢复数据的时间就会变得很长,因此对WAL的大小也有一些限制,当达到这些限制的时候MemStore就会触发刷写,从而减小WAL的大小。WAL 的最大值由hbase.regionserver.maxlogs * hbase.regionserver.hlog.blocksize (2GB by default)决定。
***Note:通常增加MemStore的内存大小时,也要增加HLog的大小,否则WAL会率先出发MemStore的刷写,因而将无法利用专门为MemStore设置的优化,并且WAL触发的MemStore刷写为全局触发,一次会Flush很多Region,进而很有可能会引起"Flush 大风暴",尽管写数据是很好的分布在整个集群,但是这种情况仍然是我们不希望看到的。
Tips:通常将WAL的hbase.regionserver.hlog.blocksize * hbase.regionserver.maxlogs 设置为稍微大于hbase.regionserver.global.memstore.lowerLimit * HBASE_HEAPSIZE。
频繁的刷写会产生的问题:
为了避免写阻塞貌似让Flush操作尽早的达到触发条件的阈值时为宜,但是这将导致频繁的Flush操作,而这可能会使读写性能下降以及增加额外的负载。
因为每次Flush都会创建一个新的HFile,频繁的Flush就会产生大量的Flush,这样在HBase检索时会读取大量的HFile,这使得读性能大打折扣。并且当HFile的数量达到一定阈值的时候也会造成"写阻塞"。
所以HBase为了避免打开过多的HFile造成读性能的恶化,有专门的HFile合并处理,HBase会周期的将多个小的HFile合并成一个大的HFile,显然HFile的数量越多,集群系统要做的合并操作就越多,更糟糕的是Compaction处理是跟集群上其他请求并行执行的,当Compaction不能跟上HBase的刷写速度时,同样会在RegionServer上出现"写阻塞"。
***Note:密切关注RegionServer上Compaction Queue 的size,避免在出现问题前持续增大。
当HBase中的table中,Column Famliy的数据量不均匀时,当其中一个较大的Column Family达到MemStore的刷写阈值时,同一个Region中的其他的数据量较少的Column Family对应的MemStore也会刷写,这将会产生大量的HFile小文件。所以通常情况下,一个ColumnFamily时最好的选择。
Source From:https://hbase.apache.org/book.html