原理
HBase 2.0引入了一项新的特性内存压缩(In-Memory Compaction),基于Accordion算法实现。
Accordion重新应用了LSM设计原则(Log-Structured-Merge Tree,HBase基于MemStore的设计模式)。
HBase数据的修改都会写入到一个MemStore(每个列族有一个MemStore),当MemStore数据量达到陪着的阈值或者触发了其他刷新条件,MemStore会被刷新到磁盘,从而创建一个StoreFile,StoreFile是顺序写入,写入以后也不能被变更,所以LSM通过将数据写入从磁盘的随机写入转换为内存的随机写入加上磁盘的顺序写入来提升性能。
但是这样也会带来一个数据的多版本问题,同一行数据多次修改后,可能会存在于多个StoreFile文件中,所以HBase读取数据的时候,需要扫描MemStore跟多个StoreFile来综合查询到这行数据的最新版本,为了减少需要读取的数据版本数以及磁盘访问次数,HBase在后台进行Compact压缩合并StoreFile文件,这个过程会清理过期、删除的数据,并将多个StoreFile合并成一个大的StoreFile。
Accordion算法将LSM原理重新应用于MemStore,以便在数据仍在RAM中时便可以消除过期跟冗余数据。内存压缩可以降低数据刷新到HDFS的频率,从而减少写入放大(LSM刷新到磁盘产生的StoreFile文件会有多轮合并,数据的多次合并会导致数据的多次读取、合并、写入到大文件,称之为写入放大)和整个磁盘占用空间。由于刷新次数较少,因此MemStore溢出时写入操作停止的频率降低,因此写入性能得到改善。磁盘上的数据越少,对块缓存的压力越小,命中率越高,最终读取响应时间越快。最后,减少磁盘写入也意味着在后台执行的压缩更少,即从生产(读取和写入)工作中窃取的周期更少。总而言之,内存压缩的效果可以被设想为催化剂,使系统整体上响应得更快。
内存压缩在大量数据流失时效果最佳;当数据仍在内存中时,可以消除覆盖或过期版本。如果写入都是唯一的,则可能会降低写入吞吐量(内存中压缩使用CPU的成本)。所以使用之前需要评估业务场景并进行测试。
HBase2.0引入的CompactingMemStore即为内存压缩的实现类:
在CompactingMemStore中,数据是以段(Segment)为单位存储数据的。CompactingMemStore包含了一个CompactionPipeline,CompactionPipeline包含了多个不可变的Segment。
当数据写入时,首先写入到的是Active Segment中
如果没有启用内存压缩,当MemStore中的数据量达到指定的阈值时,就会将数据刷新输出到磁盘中的一个StoreFile
如果启用了内存压缩,当Active Segment满了后,会将Segment移动到CompactionPipeline中,内存压缩会管理这个CompactionPipeline,将CompactionPipeline中的多个Segment合并为更大的、更紧凑的Segment,这就是In-Memory Compaction
如何使用
In-Memory Compaction可以全局配置,也可以按列族配置。 支持的级别为NONE、BASIC、EAGER、ADAPTIVE:
public enum MemoryCompactionPolicy {
/**
* No memory compaction, when size threshold is exceeded data is flushed to disk
*/
NONE,
/**
* Basic policy applies optimizations which modify the index to a more compacted representation.
* This is beneficial in all access patterns. The smaller the cells are the greater the benefit of
* this policy. This is the default policy.
*/
BASIC,
/**
* In addition to compacting the index representation as the basic policy, eager policy eliminates
* duplication while the data is still in memory (much like the on-disk compaction does after the
* data is flushed to disk). This policy is most useful for applications with high data churn or
* small working sets.
*/
EAGER,
/**
* Adaptive compaction adapts to the workload. It applies either index compaction or data
* compaction based on the ratio of duplicate cells in the data.
*/
ADAPTIVE
}
- BASIC: 该策略不清理多余的数据版本,将数据索引修改为一个更加紧凑的视线,在所有的读写模式下都能够带来收益,单元格越小收益越明显。
- EAGER:该策略会过滤重复的数据,清理多余的版本,类似于LSM对StoreFile做的合并处理逻辑,该模式适用于数据更新频繁或工作集较小的场景。
- ADAPTIVE:根据数据Cell的重复情况来决定是否使用Eager策略,该策略计算重复Cell的比列,如果比例超出阈值,则使用Eager策略,否则使用Basic策略
默认情况下HBase不启用内存压缩。 可以在hbase-site.xml中全局配置,配置后会对新建的表生效,如下所示:
<property>
<name>hbase.hregion.compacting.memstore.type</name>
<value>< NONE | BASIC | EAGER | ADAPTIVE></value>
</property>
也可以在HBase Shell中按列族配置级别,如下所示:
### 修改已有表
hbase:077:0> desc 'test'
Table test is ENABLED
test, {TABLE_ATTRIBUTES => {METADATA => {'hbase.store.file-tracker.impl' => 'DEFAULT'}}}
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', INDEX_BLOCK_ENCODING => 'NONE', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW',
IN_MEMORY => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536 B (64KB)'}
1 row(s)
Quota is disabled
Took 10.2678 seconds
hbase:078:0> disable 'test'
Took 0.6925 seconds
hbase:079:0> alter 'test', {NAME => 'cf', IN_MEMORY_COMPACTION => 'BASIC'}
Updating all regions with the new schema...
All regions updated.
Done.
Took 1.1548 seconds
hbase:080:0> desc 'test'
Table test is DISABLED
test, {TABLE_ATTRIBUTES => {METADATA => {'hbase.store.file-tracker.impl' => 'DEFAULT'}}}
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', INDEX_BLOCK_ENCODING => 'NONE', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW',
IN_MEMORY => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536 B (64KB)', METADATA => {'IN_MEMORY_COMPACTION' => 'BASIC'}}
1 row(s)
Quota is disabled
Took 0.0424 seconds
hbase:081:0> enable 'test'
Took 0.7040 seconds
### 新建表
hbase:082:0> create 'test2', {NAME => 'cf', IN_MEMORY_COMPACTION => 'EAGER'}
Created table test2
Took 0.7031 seconds
=> Hbase::Table - test2
hbase:083:0> desc 'test2'
Table test2 is ENABLED
test2, {TABLE_ATTRIBUTES => {METADATA => {'hbase.store.file-tracker.impl' => 'DEFAULT'}}}
COLUMN FAMILIES DESCRIPTION
{NAME => 'cf', INDEX_BLOCK_ENCODING => 'NONE', VERSIONS => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW',
IN_MEMORY => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536 B (64KB)', METADATA => {'IN_MEMORY_COMPACTION' => 'EAGER'}}
1 row(s)
Quota is disabled
Took 0.0489 seconds
hbase:084:0>
性能提升
社区的博客中给出了两个不同场景的测试结果。
使用YCSB测试工具,100-200GB数据集。
分别在key热度符合Zipf分布和平均分布两种情况下,测试了只有写操作情况下写放大、吞吐、GC相比默认Memstore的变化,以及读写各占50%情况下尾部读延时的变化。
测试结果如下表:
Key热度分布 | 写放大 | 吞吐 | GC | 尾部读延时 |
---|---|---|---|---|
Zipf | 降低30% | 提升20% | 下降22% | 下降12% |
平均分布 | 降低25% | 提升50% | 下降36% | 无变化 |