HBase最佳实践-内存规划
t700in55
8年前
<p>线上HBase集群应该如何进行参数配置?这其实是很多HBase初学者在实践环节都可能会遇到的问题,有些人会选择默认配置,有些人会选择其他公司的推荐配置;诚然,这样的参数配置在大多数情况下都能正常工作,但性能却未必最佳、资源未必都能被合理利用。本文结合笔者的实践经验,针对不同应用场景,对多种工作模式下的参数进行详细说明,并结合相关示例对集群规划中最核心模块-内存规划进行介绍。一方面希望读者能够了解HBase内存相关知识细节,另一方面能够将这些知识应用于实践、不断对集群进行优化。</p> <p>HBase中内存规划直接涉及读缓存BlockCache、写缓存MemStore,影响系统内存利用率、IO利用率等资源以及读写性能等,重要性不言而喻。主要配置也是针对BlockCache和MemStore进行,然而针对不同业务类型(简单说来主要包括读多写少型和写多读少型),内存的相关配置却完全不同。再者,对于读缓存BlockCache,线上一般会有两种工作模式:LRUBlockCache和BucketCache,不同工作模式下的相关配置也不尽相同。为了比较完整的说明不同应用场景以及不同缓存工作模式的内存规划,下文会分分别介绍两个案列:读多写少型+BucketCache,写多读少型+LRUBlockCache。</p> <p>需要说明的是,业务类型和读缓存工作模式之间没有任何直接的关联。业务到底使用BucketCache还是使用LRUBlockCache,只和分配给RegionServer的内存大小有关。一般而言,如果HBASE_HEAPSIZE > 20G,选择BucketCache,否则选择LRUBlockCache( <a href="/misc/goto?guid=4959674678595184711" rel="nofollow,noindex"> 参考hortonworks文档 </a> ),理论依据可以参考 <a href="/misc/goto?guid=4959674678683704473" rel="nofollow,noindex"> 这里 </a> 。</p> <h3><strong>案例一:写多读少型 + LRUBlockCache</strong></h3> <p><strong>内存分布图</strong></p> <p>在详细说明具体的容量规划前,首先需要明确LRUBlockCache模式下的内存分布图,如下图所示:</p> <p><img src="https://simg.open-open.com/show/7fe54f50577e6d3c01204509438b95cf.png"></p> <p>图中分配给RegionServer进程的内存就是JVM内存,主要分为三部分:LRUBlockCache,用于读缓存;MemStore,用于写缓存;Other,用于RS运行所必须的其他对象;</p> <p><strong>内存规划思路</strong></p> <p>了解了BucketCache模式下的内存分布图之后,我们具体来分析如何规划内存,首先列出来基本条件:</p> <p>a. 整个物理机内存:96G</p> <p>b. 业务负载分布:30%读,70%写</p> <p>接下来将问题一步一步分解,从上至下按照逻辑对内存进行规划:</p> <p>(1) 系统内存基础上如何规划RS内存?</p> <p>这个问题需要根据自身服务器情况决定,一般情况下,在不影响其他服务的情况下,越大越好。我们目前设置为64G,为系统内存的2/3。</p> <p>(2) 如何设置LRUBlockCache、MemStore?</p> <p>确定RegionServer总内存之后,接下来分别规划LRUBlockCahce和MemStore的总内存。在此需要考虑两点:在写多读少的业务场景下,写缓存显然应该分配更多内存,读缓存相对分配更少;HBase在此处有个硬规定:LRUBlockCache + MemStore < 80% * JVM_HEAP,否则RS无法启动。</p> <p>推荐内存规划: MemStore = 45% * JVM_HEAP = 64G * 45% = 28.8G ,LRUBlockCache = 30% * JVM_HEAP = 64G * 30% = 19.2G; 默认情况下Memstore为40% * JVM_HEAP,而LRUBlockCache为25% * JVM_HEAP</p> <p><strong> 配置设置实践</strong></p> <p>(1)设置JVM参数如下:</p> <pre> -XX:SurvivorRatio=2 -XX:+PrintGCDateStamps -Xloggc:$HBASE_LOG_DIR/gc-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M -server -Xmx64g -Xms64g -Xmn4g -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</pre> <p>(2)hbase-site.xml中MemStore相关参数设置如下:</p> <pre> <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></pre> <p>由上述定义可知,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%。</p> <p>(3)hbase-site.xml中LRUBlockCache相关参数设置如下:</p> <pre> <property> <name>hfile.block.cache.size</name> <value>0.3</value> </property></pre> <p>hfile.block.cache.size表示LRUBlockCache占用内存在JVM内存中的比例,因此设置为0.3</p> <h3><strong>案例二:读多写少型 + BucketCache</strong></h3> <p><strong>内存分布图</strong></p> <p>与LRUBlockCache模式相比,BucketCache模式下的内存分布图会更加复杂,如下图所示:</p> <p>~ <img src="https://simg.open-open.com/show/7a4ff87ca0b3f1d90785a542976f66fa.png"></p> <p>如图,整个RegionServer内存(Java进程内存)分为两部分:JVM内存和堆外内存。其中JVM内存中LRUBlockCache和堆外内存BucketCache一起构成了读缓存CombinedBlockCache,用于缓存读到的Block数据,其中LRUBlockCache用于缓存元数据Block,BucketCache用于缓存实际用户数据Block;MemStore用于写流程,缓存用户写入KeyValue数据;还有部分用于RegionServer正常运行所必须的内存;</p> <p><strong>内存规划思路</strong></p> <p>和案例一相同,本案例中物理机内存也是96G,不过业务类型为读多写少:70%读+30%写</p> <p>因为BucketCache模式下内存分布图相对复杂,我们使用如下表格一步一步对内存规划进行解析:</p> <table cellspacing="0"> <tbody> <tr> <td> <p>序号</p> </td> <td> <p>步骤</p> </td> <td> <p>原理</p> </td> <td> <p>计算公式</p> </td> <td> <p>计算值</p> </td> <td> <p>修正值</p> </td> </tr> <tr> <td> <p>A</p> </td> <td> <p>规划RS总内存 </p> </td> <td> <p>在系统内存允许且不影响其他服务的情况下,越多越好。设置为系统总内存的 2/3。</p> </td> <td> <p>2/3 * 96G</p> </td> <td> <p>64G</p> </td> <td> <p>64G</p> </td> </tr> <tr> <td> <p>B</p> </td> <td> <p>规划读缓存 CombinedBlockCache</p> </td> <td> <p>整个RS内存分为三部分:读缓存、写缓存、其他。基本按照5 : 4 : 1的分配原则。读缓存设置为整个RS内存的50%</p> </td> <td> <p>A * 50%</p> </td> <td> <p>32G</p> </td> <td> <p>26G</p> </td> </tr> <tr> <td> <p>B1</p> </td> <td> <p>规划读缓存LRU部分</p> </td> <td> <p>LRU部分主要缓存数据块元数据,数据量相对较小。设置为整个读缓存的10%</p> </td> <td> <p>B * 10%</p> </td> <td> <p>3.2G</p> </td> <td> <p>3G</p> </td> </tr> <tr> <td> <p>B2</p> </td> <td> <p>规划读缓存BucketCache部分</p> </td> <td> <p>BucketCache部分主要缓存用户数据块,数据量相对较大。设置为整个读缓存的90%</p> </td> <td> <p>B * 90%</p> </td> <td> <p>28.8G</p> </td> <td> <p>24G</p> </td> </tr> <tr> <td> <p>C</p> </td> <td> <p>规划写缓存MemStore</p> </td> <td> <p>整个RS内存分为三部分:读缓存、写缓存、其他。基本按照5:4:1的分配原则。写缓存设置为整个RS内存的40%</p> </td> <td> <p>A * 40%</p> </td> <td> <p>25.6G</p> </td> <td> <p>25G</p> </td> </tr> <tr> <td> <p>D</p> </td> <td> <p>设置JVM_HEAP</p> </td> <td> <p>RS总内存大小 – 堆外内存大小</p> </td> <td> <p>A – C </p> </td> <td> <p>35.2G</p> </td> <td> <p>40G</p> </td> </tr> </tbody> </table> <p><strong>计算修正</strong></p> <p>看到这里,可能很多仔细的朋友就会疑问,案例一不是说过HBase有一个硬规定么:LRUBlockCache + MemStore < 80% * JVM_HEAP,否则RS无法启动。不错,HBase确实有这样一个规定,这个规定的本质是为了在内存规划的时候能够给除过写缓存和读缓存之外的其他对象留够至少20%的内存空间。那按照上述计算方式能不能满足这个硬规定呢,LRU + MemStore / JVM_HEAP = 3G + 25G / 35G = 28G / 35G = 80% ,刚好等于阈值 80%,为了更加保险,建议这个值在70%~75%之间,因此需要对计算值进行简单的修正,适量增大JVM_HEAP值(增大至40G),BucketCache减少到24G(CombinedBlockCache需调整到26G)。调整之后,LRU + MemStore / JVM_HEAP = 3G + 25G / 40G = 28G / 40G = 70%</p> <p><strong> 配置设置实践</strong></p> <p>(1)设置JVM参数如下:</p> <pre> -XX:SurvivorRatio=2 -XX:+PrintGCDateStamps -Xloggc:$HBASE_LOG_DIR/gc-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M -server -Xmx40g -Xms40g -Xmn4g -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</pre> <p>(2)hbase-site.xml中MemStore相关参数设置如下:</p> <pre> <property> <name>hbase.regionserver.global.memstore.upperLimit</name> <value>0.60</value> </property> <property> <name>hbase.regionserver.global.memstore.lowerLimit</name> <value>0.55</value> </property></pre> <p>根据upperLimit参数的定义,结合上述内存规划数据可计算出 upperLimit = 25G / 40G = 60%。因此upperLimit参数设置为0.60,lowerLimit设置为0.55</p> <p>(3)hbase-site.xml中CombinedBlockCache相关参数设置如下:</p> <pre> <property> <name>hbase.bucketcache.ioengine</name> <value>offheap</value> </property> <property> <name>hbase.bucketcache.size</name> <value>26624</value> </property> <property> <name>hbase.bucketcache.percentage.in.combinedcache</name> <value>0.90</value> </property></pre> <p>按照上述介绍设置之后,所有关于内存相关的配置基本就完成了。但是需要特别关注一个参数hfile.block.cache.size,这个参数在本案例中并不需要设置,没有任何意义。但是HBase的硬规定却是按照这个参数计算的,这个参数的值加上hbase.regionserver.global.memstore.upperLimit的值不能大于0.8,上文提到hbase.regionserver.global.memstore.upperLimit值设置为0.6,因此,hfile.block.cache.size必须设置为一个小于0.2的任意值。 hbase.bucketcache.ioengine表示bucketcache设置为offheap模式;hbase.bucketcache.size表示所有读缓存占用内存大小,该值可以为内存真实值,单位为M,也可以为比例值,表示读缓存大小占JVM内存大小比例。如果为内存真实值,则为26G,即26624M。而如果是比例值,则计算应为 26G / 40G = 65%,设为0.65即可;hbase.bucketcache.percentage.in.combinedcache参数表示用于缓存用户数据块的内存(堆外内存)占所有读缓存的比例,设为0.90;</p> <p>至此,关于HBase集群的内存规划经过两个案例的分析到此就要结束了,希望看官能够理解整个分析的思路。结合自己的应用场景以及内存使用模式对内存进行规划。本文也只是笔者个人见解,如果有任何意见可以相互交流讨论</p> <p> </p> <p>来自: <a href="/misc/goto?guid=4959674678753541992" rel="nofollow">http://hbasefly.com/2016/06/18/hbase-practise-ram/</a></p> <p> </p>