HBase中JVM基本配置

在JVM中,默认情况下会设置minimum heap size 为 1/64 可用物理内存,并为maximum heap size设置 1/4 的物理可用内存(不过在Java8 之前,默认最大是1g)。当然,我们可以通过手动指定 JVM 参数,配置JVM的内存,例如:

  1. -Xms10g -Xmx10g

在HBase 中,也可以在 hbase-env.sh 中显示指定堆内存大小,例如:

# The maximum amount of heap to use. Default is left to JVM default.

# export HBASE_HEAPSIZE=1G

这里有个问题是:是否要同时设置-Xms 与 -Xmx为一相同的值?若是将他们设置为同一一个固定值,则它们的优缺点有:

  1. 对于一些垃圾回收的算法,例如G1,将它们锁定为同一值会更好
  2. 不设置 -Xms可以让VM启动更快,但是接下来,需要在每轮gc 增长堆内存后,才会达到一个稳定状态。这样会导致在启动后,最开始会带来一些延迟,这个延时并不是我们在HBase中期望看到的,因为HBase提供的服务一般都是交互式服务

若是设置-Xms与-Xmx为同一值,则JVM在启动时会稍慢,不过在正常启动后,不会再有隐含的内存大小调整的情况,所以会更稳定。而在RegionServer 的进程中,我们可以在分配工作给它前,稍等一会而,以缓解启动进程的消耗。

对于HMaster进程,一般不需要太大内存,2GB 到 4GB就可以满足master的工作需求,因为它的对象分配与销毁的频率远小于RegionServer。

对于RegionServer 进程,若是物理机内存为64GB,则一开始可以给它分配10GB的-Xmx,之后可以根据监控(如jstat等)再进行向上调整。不过若是分配了较大的堆内存,我们也必须要考虑不同的GC算法,以支持更大堆内存中的垃圾回收。

基于以上场景,我们可以在hbase-env.sh 中配置以下参数:

  1. # Set environment variables here.
  1. export HBASE_MASTER_OPTS="-Xms2g -Xmx2g"
  1. export HBASE_REGIONSERVER_OPTS="-Xms10g -Xmx10g"

如前文所述,不指定-Xms的话,堆内存需要在增长多次后才能到达最大堆内存,导致在重新分配内存时的不必要开销,所以最好是按上述指定。

HBase中的堆内存管理

在RegionServer中,主要的任务为:接收并处理读写请求。不过除此之外,RegionServer也需要在内存中维护一些内部信息,例如:对于每个打开的region来说,它们都需要在内存里结构化存储,以处理一些常规操作。

在RegionServer启动后,所分配的JVM堆内存大小就已经指定了,无法再增加。所以对于这部分已有的内存,我们需要谨慎使用,其中最重要的一部分就是:如何调整可供读写操作使用的可用内存。

在HBase 1.0 之前,主要有两个参数需要调整,分别为memstores(对于写)以及block cache(对于读)。剩下的内存被用于所有其他杂项使用(例如Java用于存储打开的region的结构)。对于memstore,我们有以下两个参数可以指定:

  • hbase.regionserver.global.memstore.upperLimit默认为0.4(40%),也就是memstore可以使用的最大堆内存
  • hbase.regionserver.global.memstore.lowerLimit默认为0.35(30%),也就是memstore可以使用的最小堆内存

upper limit在这里较为重要,因为它指定了对于写操作,可以使用的最大内存。对于读来说,仅有一个参数可供调整:

  • hfile.block.cache.size:默认为0.4(40%),堆内存中用于分配给block cache的比率

memestore的upperLimit ,与 blockcache 的大小,它们的和不能超过80% 堆内存总大小,因为仍需要提供剩余的20% 内存以上,用于regionserver进程完成它自身的部分工作(并且防止Java OOM的问题)。

在HBase 1.0 之后,提供了更多的配置项,用于指定memstore 与 block cache 的upper limit 与 lower limit,例如:

  • hbase.regionserver.global.memstore.size.min.range默认未设置,为所有memstore的最小分配内存
  • hbase.regionserver.global.memstore.size.max.range默认未设置,为所有memsotre的最大分配内存
  • hbase.regionserver.global.memstore.size默认为0.4(40%),也就是memstore默认被分配的对内存比例
  • hfile.block.cache.size.min.range默认未设置,为block cache 大小的最小值
  • hfile.block.cache.size.max.range默认未设置,为block cache 大小的最大值
  • hfile.block.cache.size默认为0.4(40%),也就是堆内存中默认分配给block cache 的比例
  • hbase.regionserver.heapmemory.tuner.period:默认为60000(60s),定义heap tunner 运行的频率

对于memstore与block cache 的上限与下限,我们必须做配置,除非禁止tuner。

HBase 提供了一个机制是:根据请求流量大小,对堆内存进行动态调整。这就是为什么在开启tuner的情况下,我们必须要设置一个上限与下限。

不过这里仍需要保证的一点是:memstore 与 block cache 的最大使用量不能超过JVM内存的 80%。如果超过了这个比例,则tuner会报错,region server 会退出。这个限制的原因与之前的一样:需要剩余足够的内存供给 region server 进程正常工作。

下图是固定内存与动态内存的一个示例图:

动态内存对应的配置为:

  1. <property>
  1.   <name>hbase.regionserver.global.memstore.size.min.range</name>
  1.   <value>0.2</value>
  1. </property>
  1. <property>
  1.   <name>hbase.regionserver.global.memstore.size.max.range</name>
  1.   <value>0.6</value>
  1. </property>
  1. <property>
  1.   <name>hfile.block.cache.size.min.range</name>
  1.   <value>0.2</value>
  1. </property>
  1. <property>
  1.   <name>hfile.block.cache.size.max.range</name>
  1.   <value>0.6</value>
  1. </property>

它们的初始值为默认的40%内存。Tuner可以在某一个内存(如memstore的内存)达到最小20%后,给另一个分配60%的内存。

Tuner的机制仅是增加某一区域(如memstore)的内存,在增加后另一区域(如block cache)的内存即会减少。基于的是(或是说触发的条件是):由于内存压力导致的memstore 强制刷新(会要求更多内存,即增加此区域内存),或是内存压力导致的block cache 中的cache 置换。

两个主要GC算法

在 HBase 中,主要使用的GC算法为CMS或G1,它们的对比如下:

名称                                                               JVM参数                                        说明

Consurrent Mark Sweep(CMS)            -XX:+UseConcMarkSweepGC      低延时(堆内存 < 32GB)

Garbage First(G1)                                  -XX:+UseG1GC                               低延时

CMS为较早使用的GC 算法,它可以有效提升HBase的性能,特别是I/O请求的延时。不过它的缺点是:有最大内存的限制。

在CMS刚开始被提出的时候,它可以良好适应当时的服务器硬件性能,不过随着现在超出1TB的内存机器越来越常见,32GB大小的堆内存配置也越来越常见。CMS不适用于较大堆内存的原因是因为:在较大的堆内存中,垃圾回收会造成较长时间的停顿(几秒到十几秒),并最终会导致请求的高延时。

JVM stop log

在HBase region server 进程启动后,会启动一个轻量级的线程,定期(500ms)被唤醒,并比较它在唤醒前它睡眠的时间以及实际经过的时间是否一致。如果检测到它实际睡眠的时间超过了500ms,则在服务器日志记录,例如:

  1. 2016-08-05 07:11:32,390 INFO  [JvmPauseMonitor] util.JvmPauseMonitor: \
  1.   Detected pause in JVM or host machine (eg GC): \
  1.     pause of approximately 7448ms
  1. GC pool 'ParNew' had collection(s): count=1 time=49ms
  1. GC pool 'ConcurrentMarkSweep' had collection(s): count=1 time=7700ms
  1. ...
  1.  
  1. 2016-08-11 23:01:56,686 WARN  [JvmPauseMonitor] util.JvmPauseMonitor: \
  1.   Detected pause in JVM or host machine (eg GC): \
  1.     pause of approximately 3594293ms
  1. No GCs detected

第一条是由gc导致,记录的是 CMS 消耗了超过7秒等待垃圾回收完成。第二条与机器的暂停无关。根据暂停的时间,日志会以INFO或是WARN级别记录下来,对应的参数如下:

jvm.pause.info-threshold.ms:默认为1000ms(1s),若是停顿时间超过此值,则以INFO级别记录日志

jvm.pause.warn-threshold.ms:默认为10000ms(10s),若是停顿时间超过此值,则以WARN级别记录日志

在做调优时要注意这些日志条目,以了解停顿的频率与时间。并根据时间调整相关timeout配置(例如zookeeper的配置,如果regionserver 超过一段时间未响应,可能会被zookeeper认为宕机)。

CMS 与 G1

再回到GC部分,G1的提出就是为了解决:CMS在较大堆内存回收垃圾时产生的长时间停顿问题。下图是两者的实现与对比:

在G1 中,相对于CMS预先配置的堆内存容量,它将整个堆内存空间划分为多个相同大小的regions,一般是2000个左右。在划分好后,G1可以让其中的部分region分别执行Young Generation、Old Generation、以及survivor的角色。G1 gc采用的策略就是在这些区域中执行较多的gc任务,以尽可能地减少jvm pause以及对用户的影响。虽然这些额外的工作是会对延时有较大的影响,不过从长远来看,它的jvm pause时间会更短,并可以提供更稳健的延时,而不是抖动的延时。

CMS

CMS是一个性能较好的GC算法,已被使用多年。启用的参数为:

-XX:+UseParNewGC -XX:+UseConcMarkSweepGC

(这里其实在指定 -XX:+UseConcMarkSweepGC 后就默认已经指定了-XX:+UseParNewGC,这里显示指定为了之后进一步的讨论)

第一个选项是为Young Generation 设置使用Parallel New Collector:它在清理Young Generation 时会停止整个Java 进程。这个在Young Generation中是可以被接受的,因为它的内存相对Old Generation 会少得多,所以此过程也不会消耗太多的时间,一般少于几百毫秒。不过,这个设置对于Old Generation是不合适的,在最坏的情况下,可能会导致多达几秒的延时。

对此,CMS可以较好地解决此问题,它会尽可能地并行做gc,而不停止Java 进程。虽然此方法会造成额外的CPU资源消耗,但是可以减少或是避免JVM pause。CMS也是hbase 中默认的gc算法。

在CMS中,我们可以做的第一个调整是:Young Generation的大小。它默认的值较小,在region server 执行写操作时,会将数据写入memstore,过小的Young Generation会让这部分数据停留在此区域的时间较短,并可能造成不必要的generation 提升(Young Generation 提升到 Old Generation)。对此,可以将Young Generation的大小设置为一个固定的、更大的一个值,以减少此类影响,例如:

-XX:MaxNewSize=128m -XX:NewSize=128m

或者也可以直接用这两个配置的简写:-Xmn128m。

128MB是一个较好的起点,之后可以根据JVM的指标,确定是否满足需求或是是否需要添加更多的内存。不过对于有较高负载的region server 来说,默认的Young Generation 的大小有些过于小了。如果在一开始不调整此参数的话,我们可能会注意到CPU的使用率会偶尔突然增高,因为JVM需要花时间将Young Generation 中的对象提升到Old Generation中(由于Young Generation中内存不足)。

CMS还有一个参数用于控制:什么时候开始做concurrent mark and sweep 检查。这个参数可以通过以下配置设置:

-XX:CMSInitiatingOccupancyFraction=85

此参数指定的是:什么时候启动后台cms垃圾回收。此参数不能配置过高,为了避免concurrent mode failure 问题。此问题发生的场景为:在后台mark and sweep 工作正在进行时,堆内存已经耗尽了所有的可用空间。则此时,JRE必须停止Java 进程,并且通过清除垃圾,或是将Young Generation 中的对象移动到 Old Generation中(如果这些对象已经够old),以释放空间。

设置此值为85%,表示在堆内存耗尽之前就会开始执行concurrent collection 的工作,但也不会是太早去做这个工作。一个常规的建议是:将这部分值设置为稍高于memstore与block cache总和大小的值。它们的总和一般为80%(memstore 40%,block cache 40%),所以我们会优先设置为85%左右或以上。

下面是一个起始的CMS参考配置:

  1. export HBASE_REGIONSERVER_OPTS="-Xms10g -Xmx10g -Xmn128m \
  1.   -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 \
  1.   -XX:+UseCMSInitiatingOccupancyOnly"

G1

G1的启用方法为:

-XX:+UseG1GC

除此之外,还需要设置一个预期最大的gc pause 时间。不过这个仅是一个soft goal,JVM会尽量满足此目标。不过若是为了满足工作负载,此暂停时间可能会更长。此参数为:

-XX:MaxGCPauseMillis=200

默认即为200ms,可以根据需求做进一步调整。最后一个参数需要调整的是:什么时候gc collector 开始一个concurrent GC cycle,以整个堆内存已使用部分百分比为阈值,默认为45%,例如:

-XX:InitiatingHeapOccupancyPercent=45

可以先以此为初始配置,然后再根据负载调整G1 中每个region的大小等,此部分需要更深入的G1 算法知识,之后再详述。

另一个可以用于参考的配置是 HubSpot 发布的一个一篇 HBase 中 G1 GC 调优文档:

https://product.hubspot.com/blog/g1gc-tuning-your-hbase-cluster

根据文档中的建议,我们可以尝试使用以下配置:

-XX:MaxGCPauseMillis=50

-XX:+UnlockExperimentalVMOptions

-XX:-OmitStackTraceInFastThrow

-XX:+ParallelRefProcEnabled

-XX:+PerfDisableSharedMem

-XX:-ResizePLAB

-XX:ParallelGCThreads=8 + (<no. of logical processors> - 8) * (5/8)

另一方面,根据另一篇 Intel 团队文档:

https://software.intel.com/en-us/blogs/2014/06/18/part-1-tuning-java-garbage-collection-for-hbase

对 Young Generation 的调优的建议为(默认 Young Generation 的比例为 5%):

32GB heap, -XX:G1NewSizePercent=3;

64GB heap, -XX:G1NewSizePercent=2;

100GB and above heap, -XX:G1NewSizePercent=1;

最终 Intel 给出的 region server JVM 的参数为:

-XX:+UseG1GC

-XX:+UseG1GC

-Xms100g -Xmx100g (Heap size used in our tests)

-XX:MaxGCPauseMillis=100 (Desired GC pause time in tests)

-XX:+ParallelRefProcEnabled

-XX:-ResizePLAB

-XX:ParallelGCThreads= 8+(40-8)(5/8)=28

-XX:G1NewSizePercent=1

针对这里的建议是:对 Young Generation,使用 Intel 的配置;对 RegionServer 其他的配置,采用 HubSpot 的配置(为2016年发布,Intel 对 RegionServer 的配置建议为 2014 年发布)。

HBase 中的 JVM 与 GC的更多相关文章

  1. JVM的GC概述

    JVM的GC概述 GC即垃圾回收,是指jvm用于释放那些不再使用的对象所占用的内存.在充分理解了垃圾收集算法和执行过程后,才能有效的优化它的性能. 有些垃圾收集专用于特殊的应用程序.比如,实时应用程序 ...

  2. Linux使用jstat命令查看jvm的GC情况

    Linux使用jstat命令查看jvm的GC情况 http://www.open-open.com/lib/view/open1390916852007.html http://www.aiuxian ...

  3. JVM、GC与HashMap

    阿里巴巴突然来了个面试邀请电话,问了些java底层的东西,不知所措,所以专门花了些时间做了下学习,顺便记录下,好记性不如烂笔头. 一.对JAVA的垃圾回收机制(GC)的理解 不同于C/C++需要手工释 ...

  4. 初步探究java中程序退出、GC垃圾回收时,socket tcp连接的行为

    初步探究java中程序退出.GC垃圾回收时,socket tcp连接的行为 今天在项目开发中需要用到socket tcp连接相关(作为tcp客户端),在思考中发觉需要理清socket主动.被动关闭时发 ...

  5. 明白生产环境中的jvm参数

    明白生产环境中的jvm参数 写代码的时候,程序写完了,发到线上去运行,跑一段时间后,程序变慢了,cpu负载高了--一堆问题出来了,所以了解一下生产环境的机器上的jvm配置是有必要的.比如说: JDK版 ...

  6. spark批量写写数据到Hbase中(bulkload方式)

    1:为什么大批量数据集写入Hbase中,需要使用bulkload BulkLoad不会写WAL,也不会产生flush以及split. 如果我们大量调用PUT接口插入数据,可能会导致大量的GC操作.除了 ...

  7. Linux使用jstat命令查看jvm的GC情况(转)

    B. jstack jstack主要用来查看某个Java进程内的线程堆栈信息.语法格式如下: 1 jstack [option] pid 2 jstack [option] executable co ...

  8. 深入探究jvm之GC的参数调优

    在上一篇博客记录了GC的算法及种类,这篇博客主要记录一下GC的参数如何调整以提高jvm的性能. 一.堆的回顾: 堆的内存空间总体分为新生代和老年代,老年代存放的老年对象,新构造的对象分配在eden区中 ...

  9. jvm的GC日志分析 [转]

      jvm的GC日志分析 标签: jvm内存javagc 2015-06-22 16:37 1566人阅读 评论(1) 收藏 举报  分类: Java(4)  JVM的GC日志的主要参数包括如下几个: ...

  10. JVM的GC简介和实例

    本文是一次内部分享中总结了jvm gc的分类和一些实例, 内容是introduction级别的,供初学人士参考.成文仓促,难免有些错误,如果有大牛发现,请留言,我一定及时更正,谢谢!JVM内存布局主要 ...

随机推荐

  1. 探索 DTD 在 XML 中的作用及解析:深入理解文档类型定义

    DTD 是文档类型定义(Document Type Definition)的缩写.DTD 定义了 XML 文档的结构以及合法的元素和属性. 为什么使用 DTD 通过使用 DTD,独立的团体可以就数据交 ...

  2. 习题8 #第8章 Verilog有限状态机设计-1 #Verilog #Quartus #modelsim

    1. 设计一个"111"串行数据检测器.要求是:当检测到连续3个或3个以上的"1"时输出为1,其他输入情况下输出为0. (1)思路分析:参照本章前文的范例,如第 ...

  3. CMake 教程(待完善)

    Cmake 教程 写在前面 如果工程只有几个文件,直接编写Makefile更直接明了 如果使用C.C++.之外的语言,请不要使用cmake 如果使用的语言有非常完备的构建体系,不需要使用cmake C ...

  4. CentOS-7卸载了python2.7,yum不可用的解决方法

    1.mount挂载iso镜像 [root@localhost software]# mount -t iso9660 -o loop CentOS-7-x86_64-DVD-2003.iso /med ...

  5. C#语言:散修笔记

    文章目录 前言 数组的几种定义方法 out 和 ref 的区别 可变参数params 静态方法与非静态方法 >❀什么时候使用静态和非静态 构造函数 >❀类中方法的重载 >❀在类中输出 ...

  6. js毫秒转时分秒

    const formatSeconds = (value) => { if (value === 0 || value < 1000) return '0秒'; var timestamp ...

  7. Laravel 模块化开发模块 – Caffienate

    Laravel多模块配置   1. 安装对应版本"caffeinated/modules" composer require caffeinated/modules 相应版本号 L ...

  8. 二:nacos 的服务注册

    一:怎么引用依赖 dependencyManagement:spring cloud alibaba 的版本,由于spring cloud alibaba 还未纳入spring cloud 的主版本管 ...

  9. RBD与Cephfs

    目录 1. RBD 1. RBD特性 2. 创建rbd池并使用 2.1 创建rbd 2.2 创建用户 2.3 下发用户key与ceph.conf 2.4 客户端查看pool 2.5 创建rbd块 2. ...

  10. 记录一次WhatTheFuck经历

    起因 很早之前就一直在维护一个git仓库,平时调研什么组件就会在里面新建一个springboot的工程用来编写示例代码. 最一开始使用的是SpringInitializr,后来网站更新之后,只能生成J ...