HBase最佳实践-内存规划

线上HBase集群应该如何进行参数配置?这其实是很多HBase初学者在实践环节都可能会遇到的问题,有些人会选择默认配置,有些人会选择其他公司的推荐配置;诚然,这样的参数配置在大多数情况下都能正常工作,但性能却未必最佳、资源未必都能被合理利用。本文结合笔者的实践经验,针对不同应用场景,对多种工作模式下的参数进行详细说明,并结合相关示例对集群规划中最核心模块-内存规划进行介绍。一方面希望读者能够了解HBase内存相关知识细节,另一方面能够将这些知识应用于实践、不断对集群进行优化。

HBase中内存规划直接涉及读缓存BlockCache、写缓存MemStore,影响系统内存利用率、IO利用率等资源以及读写性能等,重要性不言而喻。主要配置也是针对BlockCache和MemStore进行,然而针对不同业务类型(简单说来主要包括读多写少型和写多读少型),内存的相关配置却完全不同。再者,对于读缓存BlockCache,线上一般会有两种工作模式:LRUBlockCache和BucketCache,不同工作模式下的相关配置也不尽相同。为了比较完整的说明不同应用场景以及不同缓存工作模式的内存规划,下文会分分别介绍两个案列:读多写少型+BucketCache,写多读少型+LRUBlockCache。

需要说明的是,业务类型和读缓存工作模式之间没有任何直接的关联。业务到底使用BucketCache还是使用LRUBlockCache,只和分配给RegionServer的内存大小有关。一般而言,如果HBASE_HEAPSIZE > 20G,选择BucketCache,否则选择LRUBlockCache(参考hortonworks文档),理论依据可以参考这里

案例一:写多读少型 + LRUBlockCache

内存分布图

在详细说明具体的容量规划前,首先需要明确LRUBlockCache模式下的内存分布图,如下图所示:



f1

图中分配给RegionServer进程的内存就是JVM内存,主要分为三部分:LRUBlockCache,用于读缓存;MemStore,用于写缓存;Other,用于RS运行所必须的其他对象;

内存规划思路

了解了BucketCache模式下的内存分布图之后,我们具体来分析如何规划内存,首先列出来基本条件:

a.  整个物理机内存:96G

b.  业务负载分布:30%读,70%写

接下来将问题一步一步分解,从上至下按照逻辑对内存进行规划:

(1) 系统内存基础上如何规划RS内存?

这个问题需要根据自身服务器情况决定,一般情况下,在不影响其他服务的情况下,越大越好。我们目前设置为64G,为系统内存的2/3。

(2) 如何设置LRUBlockCache、MemStore?

确定RegionServer总内存之后,接下来分别规划LRUBlockCahce和MemStore的总内存。在此需要考虑两点:在写多读少的业务场景下,写缓存显然应该分配更多内存,读缓存相对分配更少;HBase在此处有个硬规定:LRUBlockCache + MemStore < 80% * JVM_HEAP,否则RS无法启动。

推荐内存规划:MemStore = 45% * JVM_HEAP = 64G * 45% = 28.8G ,LRUBlockCache = 30% * JVM_HEAP = 64G * 30% = 19.2G;默认情况下Memstore为40% * JVM_HEAP,而LRUBlockCache为25% * JVM_HEAP

 配置设置实践

(1)设置JVM参数如下:

-XX:SurvivorRatio=2  -XX:+PrintGCDateStamps  -Xloggc:$HBASE_LOG_DIR/gc-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M -server -Xmx64g -Xms64g -Xmn2g -Xss256k -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+UseParNewGC -XX:MaxTenuringThreshold=15  -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 -XX:-DisableExplicitGC

(2)hbase-site.xml中MemStore相关参数设置如下:

<property>
    <name>hbase.regionserver.global.memstore.upperLimit</name>
    <value>0.45</value>
</property>
<property>
    <name>hbase.regionserver.global.memstore.lowerLimit</name>
    <value>0.40</value>
</property>

由上述定义可知,hbase.regionserver.global.memstore.upperLimit设置为0.45,hbase.regionserver.global.memstore.lowerLimit设置为0.40hbase.regionserver.global.memstore.upperLimit表示RegionServer中所有MemStore占有内存在JVM内存中的比例上限。如果所占比例超过这个值,RS会首先将所有Region按照MemStore大小排序,并按照由大到小的顺序依次执行flush,直至所有MemStore内存总大小小于hbase.regionserver.global.memstore.lowerLimit,一般lowerLimit比upperLimit小5%。

(3)hbase-site.xml中LRUBlockCache相关参数设置如下:

<property>
    <name>hfile.block.cache.size</name>
    <value>0.3</value>
</property>

hfile.block.cache.size表示LRUBlockCache占用内存在JVM内存中的比例,因此设置为0.3

案例二:读多写少型 + BucketCache

内存分布图

与LRUBlockCache模式相比,BucketCache模式下的内存分布图会更加复杂,如下图所示:

f2

如图,整个RegionServer内存(Java进程内存)分为两部分:JVM内存和堆外内存。其中JVM内存中LRUBlockCache和堆外内存BucketCache一起构成了读缓存CombinedBlockCache,用于缓存读到的Block数据,其中LRUBlockCache用于缓存元数据Block,BucketCache用于缓存实际用户数据Block;MemStore用于写流程,缓存用户写入KeyValue数据;还有部分用于RegionServer正常运行所必须的内存;

内存规划思路

和案例一相同,本案例中物理机内存也是96G,不过业务类型为读多写少:70%读+30%写

因为BucketCache模式下内存分布图相对复杂,我们使用如下表格一步一步对内存规划进行解析:

序号

步骤

原理

计算公式

计算值

修正值

A

规划RS总内存 

在系统内存允许且不影响其他服务的情况下,越多越好。设置为系统总内存的 2/3。

2/3 * 96G

64G

64G

B

规划读缓存 CombinedBlockCache

整个RS内存分为三部分:读缓存、写缓存、其他。基本按照5 : 3 : 2的分配原则。读缓存设置为整个RS内存的50%

A * 50%

32G

34G

B1

规划读缓存LRU部分

LRU部分主要缓存数据块元数据,数据量相对较小。设置为整个读缓存的10%

B * 10%

3.2G

3G

B2

规划读缓存BucketCache部分

BucketCache部分主要缓存用户数据块,数据量相对较大。设置为整个读缓存的90%

B * 90%

28.8G

30G

C

规划写缓存MemStore

整个RS内存分为三部分:读缓存、写缓存、其他。基本按照5:4:1的分配原则。写缓存设置为整个RS内存的40%

A * 30%

19.2G

20G

D

设置JVM_HEAP

RS总内存大小 – 堆外内存大小

A – B2 

35.2G

30G

计算修正

看到这里,可能很多仔细的朋友就会疑问,案例一不是说过HBase有一个硬规定么:LRUBlockCache + MemStore < 80% * JVM_HEAP,否则RS无法启动。不错,HBase确实有这样一个规定,这个规定的本质是为了在内存规划的时候能够给除过写缓存和读缓存之外的其他对象留够至少20%的内存空间。那按照上述计算方式能不能满足这个硬规定呢,LRU + MemStore / JVM_HEAP = 3.2G + 19.2G / 35.2G = 22.4G / 35.2G =  63.6% ,远小于80%。因此需要对计算值进行简单的修正,适量减少JVM_HEAP值(减少至30G),增大Memstore到20G。因为JVM_HEAP减少了,堆外内存就需要适量增大,因此将BucketCache增大到30G。


调整之后,LRU + MemStore / JVM_HEAP = 3.2G + 20G / 30G = 23.2G / 30G =  77%

 配置设置实践

(1)设置JVM参数如下:

-XX:SurvivorRatio=2  -XX:+PrintGCDateStamps  -Xloggc:$HBASE_LOG_DIR/gc-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M -server -Xmx40g -Xms40g -Xmn1g -Xss256k -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+UseParNewGC -XX:MaxTenuringThreshold=15  -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+CMSClassUnloadingEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=75 -XX:-DisableExplicitGC

(2)hbase-site.xml中MemStore相关参数设置如下:

<property>
    <name>hbase.regionserver.global.memstore.upperLimit</name>
    <value>0.66</value>
</property>
<property>
    <name>hbase.regionserver.global.memstore.lowerLimit</name>
    <value>0.60</value>
</property>

根据upperLimit参数的定义,结合上述内存规划数据可计算出 upperLimit =  20G / 30G = 66%。因此upperLimit参数设置为0.66,lowerLimit设置为0.60

(3)hbase-site.xml中CombinedBlockCache相关参数设置如下:

<property>
    <name>hbase.bucketcache.ioengine</name>
    <value>offheap</value>
</property>
<property>
    <name>hbase.bucketcache.size</name>
    <value>34816</value>
</property>
<property>
    <name>hbase.bucketcache.percentage.in.combinedcache</name>
    <value>0.90</value>
</property>

按照上述介绍设置之后,所有关于内存相关的配置基本就完成了。但是需要特别关注一个参数hfile.block.cache.size,这个参数在本案例中并不需要设置,没有任何意义。但是HBase的硬规定却是按照这个参数计算的,这个参数的值加上hbase.regionserver.global.memstore.upperLimit的值不能大于0.8,上文提到hbase.regionserver.global.memstore.upperLimit值设置为0.66,因此,hfile.block.cache.size必须设置为一个小于0.14的任意值。hbase.bucketcache.ioengine表示bucketcache设置为offheap模式;hbase.bucketcache.size表示所有读缓存占用内存大小,该值可以为内存真实值,单位为M,也可以为比例值,表示读缓存大小占JVM内存大小比例。如果为内存真实值,则为34G,即34816。hbase.bucketcache.percentage.in.combinedcache参数表示用于缓存用户数据块的内存(堆外内存)占所有读缓存的比例,设为0.90;

至此,关于HBase集群的内存规划经过两个案例的分析到此就要结束了,希望看官能够理解整个分析的思路。结合自己的应用场景以及内存使用模式对内存进行规划。本文也只是笔者个人见解,如果有任何意见可以相互交流讨论

范欣欣

就职于网易杭州研究院后台技术中心数据库技术组,从事HBase开发、运维,对HBase相关技术有浓厚的兴趣。邮箱:libisthanks@gmail.com

在 “HBase最佳实践-内存规划” 上有 25 条评论

      1. 博主你好,我有看到你在其它博文中提到用ycsb进行测试,ycsb的read、insert操作是单条操作还是批量操作的?

  1. 看了你提供的[hortonworks文档],感觉有几点和你理解不一样的地方:
    1. 感觉在hbase-env.sh中配置HBASE_HEAPSIZE和-Xmx的作用是相同的,前者会覆盖后者的配置(The HBase startup script uses $HBASE_HEAPSIZE to override the default maximum JVM heap size (-Xmx)),那么在你的案例一中 ,既然计算出可分配的RS内存为64G(-Xmx64g), 那么为什么还采用LRUBlockCache模式呢?不是和之前说的”如果HBASE_HEAPSIZE > 20G,选择BucketCache”矛盾吗?

    1. 嗯嗯嗯 案例一只是用来说明LRUBlockCache的用法 BlockCache具体选择策略需要参考HBASE_HEAPSIZE大小

  2. 博主你好,hbase.bucketcache.size不应该只是bucketcache的大小(30G)?看到博文中说是所有读缓存(包括读缓存LRU部分)的大小(34G)?

    1. 0.98版本中hbase.bucketcache.size确实是表示所有读缓存,确实不是很好理解。1.0版本以后bucketcache相关配置就做了修正

        1. 是的 修正后的基本配置如下:

          hfile.block.cache.size
          0.05
          hbase.bucketcache.ioengine
          offheap
          hbase.bucketcache.size
          30720

          hbase.bucketcache.size就是bucketcache大小,hfile.block.cache.size是LRU部分大小

  3. 博主你好,这些参数的规划是不是在直接使用本地文件系统的基础上?
    就是说,不使用hadoop的hdfs文件系统。HBase直接部署在本地的文件系统ext3/ext4?

  4. JVM在内存>30G之后就不用指针压缩了,所以你们测试过一台机器上部署一个hbase instance + 64Gheap和一台机器上部署两个hbase instace +30G heap的性能吗?

        1. 现在memstore还没有实现offheap,只有blockcache的offheap,而且offheap现在执行的时候还是要将数据先从offheap中加载到heap中

  5. 博主,你好!
    请问下,你们生产上给region server的 jvm堆大小 是 超过32G (比如 70G+)的么?
    我们生产上按照服务商提供的意见,只给了20G。理由主要是怕内存太大,一旦发生CMS full GC,导致STW时间过长,从而引起集群故障的风险。
    但是,我们夜间有较大量的数据写入(通过spark程序进行putlist),通过监控发现,期间会发生大量的CMS full gc。
    目前调优建议是,将region server堆内存调制 32G,新生代给 2G(PS, 你之前CMS GC调优的文章也看过哈)。这也需要进一步测试看效果。目的是要减少CMS Full GC,减少STW的时间。
    目前集群单机内存是192G,是和hbase和其他组件 yarn(主要跑spark任务)等 共用的,除了基础组件外,yarn大概分了剩下的130G。
    想请问下,你们在给region server分配大内存上面的经验,比如给到(60 – 70G),风险情况如何呢?比如,给到这样大量的内存,然后调低memstore和blockcache的占用比,然后这样使得CMS Full GC 基本不发生,这种思路的方案,你觉得可行么?
    谢谢!

    1. 大堆小米有一个使用G1GC的分享,可以降低Full GC的风险。G1GC的参数调优是个技术活,可以参考这篇文章看看http://hbase-help.com/?/article/6 。 或者可以在一台机器上起多个RS。

发表评论

电子邮件地址不会被公开。 必填项已用*标注