转:HBase最佳实践-内存规划
转载来自:http://hbasefly.com/author/libisthanksgmail-com/
线上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模式下的内存分布图,如下图所示:
图中分配给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 -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
(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模式下的内存分布图会更加复杂,如下图所示:
~
如图,整个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 : 4 : 1的分配原则。读缓存设置为整个RS内存的50% |
A * 50% |
32G |
26G |
B1 |
规划读缓存LRU部分 |
LRU部分主要缓存数据块元数据,数据量相对较小。设置为整个读缓存的10% |
B * 10% |
3.2G |
3G |
B2 |
规划读缓存BucketCache部分 |
BucketCache部分主要缓存用户数据块,数据量相对较大。设置为整个读缓存的90% |
B * 90% |
28.8G |
24G |
C |
规划写缓存MemStore |
整个RS内存分为三部分:读缓存、写缓存、其他。基本按照5:4:1的分配原则。写缓存设置为整个RS内存的40% |
A * 40% |
25.6G |
25G |
D |
设置JVM_HEAP |
RS总内存大小 – 堆外内存大小 |
A – C |
35.2G |
40G |
计算修正
看到这里,可能很多仔细的朋友就会疑问,案例一不是说过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%
配置设置实践
(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 -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
(2)hbase-site.xml中MemStore相关参数设置如下:
<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>
根据upperLimit参数的定义,结合上述内存规划数据可计算出 upperLimit = 25G / 40G = 60%。因此upperLimit参数设置为0.60,lowerLimit设置为0.55
(3)hbase-site.xml中CombinedBlockCache相关参数设置如下:
<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>
按照上述介绍设置之后,所有关于内存相关的配置基本就完成了。但是需要特别关注一个参数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;
至此,关于HBase集群的内存规划经过两个案例的分析到此就要结束了,希望看官能够理解整个分析的思路。结合自己的应用场景以及内存使用模式对内存进行规划。本文也只是笔者个人见解,如果有任何意见可以相互交流讨论
下面是之前作者提到的连接页面的中文翻译:
如果您的内存不足20 GB可供HBase使用,请考虑为您的群集定制默认的堆内BlockCache实施(LruBlockCache)。
如果您有超过20 GB的RAM,请考虑添加堆外BlockCache(BucketCache)。
两个选项的前几个步骤是相同的:
-
指定要分配给每个节点上的HBase RegionServer的堆内RAM的最大数量。默认值是1 GB,这对于生产来说太小了。
要更改默认分配,请设置“RegionServers最大Java堆大小”值(Ambari),或者
HBASE_HEAPSIZE
在hbase-env.sh
(手动安装)中设置环境变量。以兆字节为单位指定值。HBase启动脚本用于$HBASE_HEAPSIZE
覆盖默认的最大JVM堆大小(-Xmx
)。以下示例将最大堆内存分配设置为20 GB
hbase-env.sh
:export HBASE_HEAPSIZE=20480
-
确定(或估计)工作负载中读写的比例,并使用这些比例为BlockCache和MemStore指定堆内存。两个分配的总和必须小于或等于0.8。下表描述了这两个属性。
属性
默认值
描述
hfile.block.cache.size
0.4
-Xmx
分配给BlockCache 的最大JVM堆大小(Java 设置)的比例。0.4的值分配最大堆大小的40%。hbase.regionserver.global. memstore.upperLimit
0.4
最大JVM堆大小(Java
-Xmx
设置)分配给MemStore的比例。0.4的值分配最大堆大小的40%。使用以下准则来确定两个比例:
-
每个属性的默认配置为0.4,它将BlockCache配置为混合工作负载,其中随机读取和写入的比例大致相等。
-
如果您的工作负载很重,并且您不打算配置堆外缓存 - 可用内存量小于20 GB - 增加
hfile.block.cache.size
和减少hbase.regionserver.global.memstore.upperLimit
以使这些值反映您的工作负载比例。这将优化读取性能。 -
如果您的工作量很大,则按比例减少
hfile.block.cache.size
和增加hbase.regionserver.global.memstore.upperLimit
。 -
如前所述,和
hfile.block.cache.size
和hbase.regionserver.global.memstore.upperLimit
必须小于或等于HBASE_HEAPSIZE
(-Xmx
)指定的最大Java堆大小的0.8(80%)。如果您在两个缓存中分配的值超过0.8,则HBase RegionServer进程将返回一个错误并且不会启动。 -
不要设置
hfile.block.cache.size
为零。至少指定一个为HFile索引块分配足够空间的比例。要查看索引块大小,请为每个服务器使用RegionServer Web GUI。
-
-
编辑
hbase-site.xml
文件中的相应值。这里是默认的定义:<属性> <名称> hfile.block.cache.size </名称> <值> 0.4 </值> <description>要分配给块的最大堆(-Xmx设置)的百分比 由HFile / StoreFile使用的缓存。默认0.4分配40%。 </描述> </属性> <属性> <名称> hbase.regionserver.global.memstore.upperLimit </名称> <值> 0.4 </值> <description>新增之前,区域服务器中所有存储区的最大大小 更新被阻止并强制刷新。默认为堆的40%。 </描述> </属性>
-
如果HBase使用的内存少于20 GB,则完成配置过程。重新启动(或滚动重新启动)您的群集。检查日志文件以查找错误消息。如果您有超过20 GB的内存供HBase使用,请考虑在下一小节中配置变量和属性。
此时,您已确定服务器上有足够的物理内存来将BlockCache扩展到JVM堆之外。您已经配置了JVM堆,LruBlockCache和MemStore的值(如图2所示,在此处复制以供参考)。
接下来,配置并启用BucketCache。为此,请记下配置BlockCache中的内存规格和值,以及图3和下表中显示的一些附加值。
图3.配置BucketCache
在下表中,第一列引用图3中的元素。第二列描述每个元素以及相关的变量或属性名称(如果适用)。第三列包含值和公式。
第四列根据以下示例配置参数计算值:
-
RegionServer进程为128 GB(其他HDP进程有额外内存可用)
-
工作量包括75%的读取,25%的写入。
-
HBASE_HEAPSIZE
= 20 GB(20480 MB)
注意 | |
---|---|
大多数以下值以兆字节为单位指定; 三个是比例。 |
项目 |
描述 |
价值或公式 |
例 |
A |
RegionServer操作的总物理内存:on-heap plus off-heap(“direct”)memory(MB) |
(取决于硬件) |
131072 |
B * |
|
建议:20480 |
20480 |
C |
|
A - B |
131072 - 20480 = 110592 |
DP * |
|
(读取比例)* 0.8 |
0.75 * 0.8 = 0.6 |
DM |
分配给BlockCache的最大JVM堆量(MB) |
B * Dp |
20480 * 0.6 = 12288 |
EP * |
|
0.8 - Dp |
0.8 - 0.6 = 0.2 |
F |
用于其他用途的堆内存量(DFSClient; MB) |
建议:1024到2048 |
2048 |
G |
分配给BucketCache(MB)的堆内存量 |
C - F |
110592 - 2048 = 108544 |
|
Dm + G |
12288 + 108544 = 120832 |
*在配置BlockCache中指定
完成配置BlockCache中的步骤后,请按照以下步骤配置BucketCache。
-
在
hbase-env.sh
每个RegionServer的hbase-env.sh
文件或提供给Ambari 的 文件中,将-XX:MaxDirectMemorySize
参数设置为HBASE_REGIONSERVER_OPTS
希望分配给HBase的直接内存量。在示例配置中,值将是110592m
。(-XX:MaxDirectMemorySize
接受一个数字后跟一个单位指示符;m
表示兆字节。)HBASE_OPTS="$HBASE_OPTS -XX:MaxDirectMemorySize=110592m"
-
在该
hbase-site.xml
文件中,指定BucketCache大小和百分比。对于示例配置,值分别为120832
和0.89830508474576
。如果您选择将比例舍入,则向上取整。这会将与舍入误差相关的空间分配给(较大的)堆外存储区。<属性> <名称> hbase.bucketcache.size </名称> <值> 120832 </值> </属性>
-
在该
hbase-site.xml
文件中,设置hbase.bucketcache.ioengine
为offheap
。这使得BucketCache成为可能。<属性> <名称> hbase.bucketcache.ioengine </名称> <值> offheap </值> </属性>
-
重新启动(或滚动重新启动)群集。分配BucketCache可能需要一分钟或更长时间,具体取决于您分配的内存量。检查日志以查找错误消息。