全栈工程师开发手册 (作者:栾鹏)
架构系列文章


实践中,HBase使用的内存不断增长,但JDK可用的垃圾收集算法仍然相同。这导致了HBase的许多用户的一个主要问题:随着Java使用堆大小继续增长,垃圾回收导致的“stop-the-world”时间变得越来越长。这在实践中意味着什么?

在垃圾回收导致的“stop-the-world”期间,任何到HBase客户端请求都不会被处理,造成用户可见的延迟,甚至超时。如果因为暂停导致请求超过一分钟响应,HBase本身也可能会停止 - 仅仅因为垃圾回收导致这样的延迟显得很不值。

HBase依赖Apache Zookeeper的管理群集成员和生命周期。如果服务器暂停的时间过长,它将无法发送心跳ping消息到Zookeeper quorum(译者注:Zookeeper的分布式算法),其余的服务器将假定该服务器已经死亡。这将导致主服务器启动特定的恢复程序,替换被认为死亡的服务器。当这个服务器从暂停中恢复时,会发现所有它拥有的租约都被撤销,进而只好自杀。HBase的开发团队已经亲切地称这种情况为朱丽叶暂停,此情况下 - 主服务器(罗密欧)假定边缘服务器(朱丽叶)死亡的(其实它真没死,只是睡觉),因此需要一些激烈的行动(恢复)。当边缘服务器从GC暂停中醒过来,它发现了一个巨大的错误已经铸成,只好结束自己的生命。这是一个非常可怕的故障!
(译者注:由于Java GC导致的心跳包没有及时响应问题,在对延时要求敏感的场景非常普遍。曾经有一个业务场景,集群中的服务器每500ms发送一次心跳包到mater服务器,而master服务器由于GC导致没有及时响应心跳包,进而认为服务器死亡,导致故障)

做过大负载HBase集群负载测试的人应该对上述问题很熟悉,在典型的硬件环境上,每GB的堆可以导致Hadoop暂停8-10秒。则分配8G堆的Hadoop会因GC暂停一分钟以上。

Hbase GC调优原理分析_堆内存

上图是JVM 分代垃圾收集系统的图表,简要说一下:

这里有 3 个堆分代:Perm(或是 Permanent)代【永久代】,Old Generation 代【老年代】,和 Young 代【年轻代】。年轻代由三个独立的空间组成,Eden 空间和两个 survivor 空间,S0 和 S1。

通常,对象被分配在年轻代的 Eden 空间,如果一个分配失败(Eden 满了),所有 java 线程停止,并且一个年轻代 GC(Minor GC)被调用。所有在年轻代存活的对象(Eden 和 S0 空间)被拷贝到 S1 空间。如果 S1 空间满了,对象被拷贝(提升)到老年代。当这个提升失败,老年代被收集(Major/Full GC)。永久代和老年代通常一起被收集。永久代被用于在存放类和对象中定义的方法。

分代回收基于一个疑问:大多数对象应该英年早逝,还是应该坚持相当长的时间?

例如:在RPC请求缓冲区中对象将只存活几毫秒,而在HBase的MemStore数据的数据可能会存活许多分钟。

很显然用两种不同的垃圾收集算法处理两种不同的生命周期的对象更好些。因此,JVM把对象分成两代:年轻代(New)和老年代(Old)。分配对象时,JVM在年轻代里分配对象。如果一个对象经过几次GC在还存活在年轻代,垃圾回收程序就把这个对象搬迁到老年代,在这里我们假设数据是会存活很长时间。

RegionServer 的JVM 垃圾回收优化理论基础

1、为何HMaster一般不需调整垃圾回收机制

HMaster 没有处理过重的负载,并且实际的数据服务不经过 HMaster,所以垃圾回收时 HMaster 通常不会产生问题。

2、描述JRE的启发式算法

JRE 在默认情况下会按照一般情况下来估计程序在做什么、怎么创建对象、如何分配堆内存处理数据,这些假设在多数情况下都是对的。JRE能够运用启发式算法,根据运行进程的状况进行调整。设置当启发式的学习调整受限于具体实现时候,JRE 也能够更好地处理某些特殊情况。

3、为何需要调整RegionServer 的垃圾回收机制

JRE的默认算法和启发式学习调整功能不能很好地处理RegionServer。当RegionServer 在写入量过大的负载时候,繁重的负载使得JRE通过对程序行为的各种假设进行内存分配的策略不再有效。

在RegionServer写入数据时,数据会先保存在memstore 中,当大于阈值时候,再写入到磁盘。因为写入的数据是由客户端在不同时间写入的,故而他们占据的Java堆空间很可能是不连续的,会出现孔洞。

4、RegionServer的新生代和老生代的特点比较

新生代大小在128M~512M;老生代大小在几GB。

最初写入的数据会保存在新生代,再刷写到磁盘;当数据刷写到磁盘的速度较慢时候,新生代中的数据停留时间过长,会被移到老生代。

新生代空间可以被迅速回收,对内存管理没有影响;老生代数据量大,回收慢,对内存管理影响大。所以二者需要不同的垃圾回收策略。

5、RegionServer 的JVM 配置解读

-Xmx8g -Xms8g -Xmn128M -XX:+UseParNewGC -XX:+UseConMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70 -verbose:gc -XLoggc:/logs/gc-hostname.log

其中:

-Xmx8g 最大堆内存8g

-Xms8g 初始堆内存设置与最大堆内存一样大。如果Xms设置得比较小,当遇到数据量较大时候,堆内存会迅速增长,当上升到最大又会回落,伸缩堆大小会带来压力。

-Xmn128M 新生代128M。新生代不能过小,否则新生代中的生存周期较长的数据会过早移到老生代,引起老生代产生大量内存碎片;新生代也不能过大,否则回收新生代也会造成太长的时间停顿,影响性能。

-XX:+UseParNewGC 新生代采用 ParallelGC 回收器。ParallelGC 将停止运行Java 进程去清空新生代堆,因为新生代很小,所以停顿的时间也很短,需几百毫秒。

-XX:+UseConMarkSweepGC 老生代采用CMS回收器(Concurrent Mark-Sweep Collector) . CMS 在不停止运行Java进程的情况下异步地完成垃圾回收,CMS会增加CPU的负载,但是可以避免重写老生代堆碎片时候的停顿。老生代回收不可使用 ParallelGC 回收机制,因为老生代的堆空间大,ParallelGC会造成Java进程长时间停顿,使得RegionServer与ZooKeeper的会话超时,该RegionServer会被误认为已经奔溃并会被抛弃。

-XX:CMSInitiatingOccupancyFraction=70 初始占用比为70%的时候开始CMS回收。此值不能太小,否则CMS发生得太频繁。此值不能太大,否则因为CMS需要额外堆内存,会发生堆内存空间不足,导致CMS 失败。

-verbose:gc -XLoggc:/logs/gc-hostname.log 写入日志

6、串行收集器(Serial Collector)、并行收集器(Parallel Collector)、并发标记清理收集器(CMS)

串行收集器(Serial Collector) -XX:+UseSerialGC

Serial Collector使用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。但是,也无法使用多处理器的优势,所以Serial Collector 适合单处理器机器。当然,此收集器也可以用在小数据量( 100M ]左右)情况下的多处理器机器上。

并行收集器(Parallel Collector) -XX:+UseParallelGC -XX:+UseParallelNewGC -XX:+UseParallelOldGC
多条垃圾收集线程并行,此时用户线程是没有运行的.

–适用情况:“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。举例:后台处理、科学计算。
–缺点:应用响应时间可能较长

并发标记清理收集器(CMS) XX:+UseConcMarkSweepGC

用户线程与垃圾收集线程并发执行,程序在继续运行,而垃圾收集程序运行于另一个个CPU上.

可以保证大部分工作都并发进行(应用不停止),垃圾回收只暂停很少的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC 打开。

-适用情况:“对响应时间有高要求”,多CPU、对应用响应时间有较高要求的中、大型应用。举例:Web服务器/应用服务器、电信交换、集成开发环境。

–缺点:需要消耗更多资源,吞吐量没那么大

JVM调优

1.1 堆内存

默RegionServer的堆内存为1G,这里Memstore默认站40%,也就是400M,在实际场景中很容易因为Memstore太小导致阻塞,修改参数,在cong/hbase-env.sh:

export HBASE_HEAPSIZE=8G

该参数会将Master和RegionServer的堆内存都设置为8G,所以有需要的话尽量使用专用的堆内存设置项:

export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -Xms4g -Xmx4G"
export HBASE_REGIONsERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xms8g -Xmx8G"

就可以将Master调整为4G,RegionServer调整为8G。需要注意的是,不管在什么时候都必须保证物理机留有10%的内存给操作系统做必要的操作。一般来说,如果物理机上还有计算框架,那么RegionServer的内存占有率应该是计算框架之外最大的。

还需要注意的是,如果JDK版本低于8,HBase会有一个内存泄漏的Bug,永久对象区(Permanent Generation,这个区域在非堆内存里边)必须设置如下项:

export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -xx:PermSize=128m -XX:MaxPermSize=128m"
export HBASE_REGIONsERVER_OPTS="$HBASE_REGIONSERVER_OPTS -xx:PermSize=128m -XX:MaxPermSize=128m"

如果Xmx调的比较大了,那么需要把PermSize和maxPermSize调整的都大一些(一般不会出现这种情况,128M已经够大了)。JDK8+就不需要这个操作了。

1.2 Full GC的影响

一般来说RegionServer的堆内存越大越好,但是因为垃圾回收的缘故,内存大了之后,相应的FullGC时间也会线性增加,一般来说每G的内存需要的FullGC时间为:100ms。FullGC可能达到好几分钟,这个阶段会停止响应任何请求,相当于所有线程挂起,这种暂停又叫做Stop-The-World(STW),FullGc的对HBase造成严重后果比较严重:

在Zookeeper检测RegionServer心跳包的时候,RegionServer正在FullGc无法回应,而如果超过阀值等待时间会被标记为宕机,这时候会将该RegionServer上的数据向其他RegionServer迁移,并且该RegionServerFullGc结束后发现自己被宕机了,为了防止脑裂,会停止自己(RegionServer自杀,又叫朱丽叶暂停)。很多场景下会将zookeeper的心跳检测阀值调大,但是这并不可取。

GC回收策略优化

为了避免长时间FullGC或者减少FullGc的发生,JVM提供了四种GC回收器:

  • 串行回收器:SerialGC
  • 并行回收器:ParallelGC 注重高吞吐量
  • 并发回收器:ConcMarkSweepGC简称CMS 注重低延迟
  • G1GC回收器:Garbage-First GC 吸收了并发和并行回收的特长
1.2.1 ParallelGC和CMS组合方案

并行回收器的性能没有串行回收器好但是FullGC时间较短;而并发回收器主要可以减少老年代的暂停时间,并且可以保证不停止的情况下进行收集,但是每次回留下一些“浮动垃圾”,只能在下次回收的时候被回收。所以Hbase比较符合的配置是:

  • 年轻代使用并行回收器ParallelGC
  • 老年代使用并发回收器ConcMarkSweepGC

依旧在conf/hbase-env.sh,如果需要修改Master的,将RegionServer修改为Master即可:

export HBASE_REGIONsERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xmx8g -Xms8g -XX:+UseParNewGC -XX:+UseConcMarkSweepGC"
1.2.2 G1GC回收方案

G1GC是在JDK1.7.0_O4(jdk7 update4)中新增的,并且如果RegionServer内存很大,大于4G的话就可以考虑G1GC。之所以引入了G1GC,是因为CMS回收依旧不能避免FullGC,发生在如下两种情况:

  • 在CMS工作时候,一些对象经过了多轮幸存,从年轻代移动到了老年代,但是老年代已经空间不足,就回引发STW,暂停,JVM整体又不能响应任何请求了。
  • 当回收回来的内存太细碎,导致新对象放不进去,也只好触发FullGC来整理空间

G1GC会申请一大块连续的内存空间装在JVMHeap,并被分割成JVM的一个个Region(并不是HBase定义的Region),然后对各个Region单独进行GC,就可以最大限度的避免(不能完全避免)FullGC,并且可以通过手动指定MaxGCPauseMills参数来空值一旦发生FullGC的时候最大的暂停时间,避免时间太长造成的RegionServer自杀:

export HBASE_REGIONSERVER_OPTS="HBASE_REGIONSERVER_OPTS -Xms24g -Xms24g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"

G1GC适合很大的堆内存情况,这里所说的很大指32G、64G以上,如果RegionServer的堆内存设置为4G-32G,需要手动测试才知道G1GC是否适合,手动实验的时候,记得把调试参数加上:

#分别打印GC详细信息、GC时间、survive空间占用日志
-XX:+PrintGCDetails -XX:PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy

其他调优参数:

-XX:+UseG1GC #使用G1GC
-Xms100g -Xmx100g #堆内存范围,这里写死为100G
-XX:MaxGCPauseMills=100 #G1GC最大的GC时间毫秒
-XX:ParallelRefProcEnabled #GC使用多线程在Young和mixed GC期间处理增加的引用
-XX:PaarallelGCThreads=8+(logical processors-8)(5/8) # 多线程回收策略,这里logical processors为40
-XX:-ResizePLAB #开启线程较多时候,该项可以关闭PLAB的大小调整,避免大量线程通信导致的性能下降
-XX:G1NewSizePercent=5 #年轻代最小值(占堆内存的比例),默认5%
-XX:G1NewSizePercent=3 #32GHeap
-XX:G1NewSizePercent=2 #64GHeap
-XX:G1NewSizePercent=1 #100G+Heap

控制年轻代大小的关键是保证其在1G的范围,因为测试发现G1清理Eden空间的速度是每1GB使用100ms
补充一个HBase的GC测试:针对HBase的Java GC调优

1.3 Memstore的专属JVM策略MSLAB

堆内存非常大的时候,FullGC时间非常长,此时解决FullGC问题不能完全依靠JVM自身的垃圾回收器,MSLAB是为MemStore专门设计的内存管理策略,对标CMS。采用CMS发生FullGC的原因主要有;

  • 同步模式失败(concurrent mode failure):如果在CMS还没有将垃圾回收完成,空间没有完全释放,这个时候新生代的对象过快转化为老年代的对象时候,发现老年代的空间不够了,此时垃圾回收会泛起并发回收转而使用单线程的STW,回到了FullGC。该过程可以通过设置-XX:CMSInitiatingOccupancyFraction=N来缓解,N表示单签JVM启动垃圾回收时候堆内存占用的百分比,设置的越小,垃圾回收越早启动,一般设置为70。
  • 碎片化导致的失败(Promotion Failure due to Fragmentation):当前要从新生代提升到老年代的对象比老年代所有的碎片空间都要大,也会触发STW进行FullGC。这个情况无论怎么调整上述的CMSInitiatingOccupancyFraction都是无法解决的,因为CMS只做回收不会合并,只要RegionServer启动的时间够长一定会导致FullGC。

之所以会出现碎片内存空间,是因为MemStore定期刷写为一个HFile,刷新完成后Memstore所占用的空间就会被回收, 但是因为内存分配都是顺序分配的,导致逐渐出现Memstore位置为碎片空间,产生不连续内存。直到没有任何一块连续内存不能存放新的数据,JVM只好进行STW,使用单进程进行内存空间重新排列。

1.3.1 LTAB方案

不过JVM为了解决碎片内存问题,有一个TLAB(Thread-Local allocation buffer)方案,每个线程都会分配一个固定大小的内存空间,专门给这个线程使用,用完之后释放,新线程也会申请这么大的空间,就不会出现碎片空间。明显的缺点是,如果线程中的对象不需要占用整个线程申请的对象会导致很大的内存浪费闲置,内存空间利用率降低,但是为了避免FullGC还是可以考虑的。
但是HBase不能直接使用该方案,因为HBase的多个Region是被一个线程管理的,多个Memstore占用的空间不能合理分开,于是HBase基于TLAB实现了MSLAB。

1.3.2 MSLAB-Memstore-Local Allocation BUffers

MSLAB完全按照TLAB实现思路,只不过内存是由Memstore来分配的,实现思路为:

  • 引入了chunk的概念,chunk就是一块内存,大小默认为2M
  • RegionServer维护者一个全局的MemStoreChunkPool实例,是一个chunk池
  • 每个MemStore实例中有一个MemStoreLAB实例
  • 当MemStore接收到KeyValue数据的时候先从ChunkPool中申请一个chunk,然后将数据放到chunk中。
  • 如果chunk放满了,新申请一个chunk
  • 如果Memstore因为刷写释放了内存,则按chunk为单位清空内存

堆内存按照chunk为单位划分为规则空间,消除了碎片空间导致的无法插入数据的问题,但是有时候只写入1kB数据也需要2M的chunk空间,但是可以容忍。相关参数如下,在hbase-site.xml中配置:

#设置为true,打开MSLAB,默认为true
hbase.hregion.memstore.mslab.enabled
#每个chunk大小,默认2MB,2048*1024B
hbase.hregion.memstore.mslab.chunksize
#能放入chunk的最大单元格大小,默认为256K
hbase.hregion.memstore.mslab.max.allocation
#在整个memstore可以占用的堆内存中,chunkPool占用的比例,默认为0.0,最大1.0
hbase.hregion.memstore.chunkpool.maxsize
#在RegionServer启动的时候可以预分配一些空的chunk出来放到chunkPool中待使用,改制代表了预分配的chunk占总的chunkPool比例,默认为0.0,最大1.0
hbase.hregion.memstore.chunkpool.initialsize
MSLAB使用注意事项

MSLAB可以和G1GC一起使用,没有冲突。不过看起来MSLAB和G1的实现思路很接近,其实G1是后来才出现的策略,并且根据测试,结合使用的性能会更高。

HBASE 配置优化

1、HMaster

HMaster的任务前面已经说过了,两个大方向:一、管理Hbase Table的 DDL操作 二、region的分配工作,任务不是很艰巨,但是如果采用默认自动split region的方式, HMaster会稍微忙一些,负载不大,可适度对此进程做适量放大heap 的操作,但不可太大,因为更耗内存的是HRegionServer

2、HRegionServer

这个进程是HBase中的核心守护进程,原则上是每个slave启动一个HRegionServer,但多种情况可能导致HRegionServer 意外退出,下面举几个简单的方面:

  • 网络不好,导致RegionServer 和 HMaster通信超时,RegionServer被认为已经挂掉,从而退出集群 --网络问题,无法从软件方面解决,关于通信超时的设置下面做个简单介绍
  • Java full GC ,这个过程会block所有的线程,如果此事件过长,导致Session expired 会话过期,导致退出集群–下文会阐述
  • 各节点时间不一致,导致RegionServer 退出。

第一种情况 和其它原因导致的RegionServer 超时挂掉的问题,我们要首先要调高Session的容忍度,默认180000其实这个回话有效期已经够长的了,但是有的集群是可以降低了这个值,可能会造成Session 超时,这个参数是 zookeeper.session.timeout 默认18000。

针对上面这个参数,有的博文认为即使设为180000也不能真正的达到目的,因为zookeeper 会将minSessionTimeout 设为 2ticktimes ,而将maxSessionTimeout 设为 20ticktimes 当 zookeeper.session.timeout 设置超过20ticktimes 的时候,系统会取 min(zookeeper.session.timeout,20ticktimes) 来出来。

针对上述观点,我从源码中找到了结论,首先如果是分布式的Hbase那会启动HQuorumPeer 进程 看下这个源码:

  • HQuorumPeer.main 方法中会调用 writeMyID(zkProperties) ,而就在此方法中已经将 maxSessionTimeout设置为 zookeeper.session.timeout 的时长。
  • 调用HQuorunPeer.runZKServer
  • 调用QuorumPeerMain.runFromConfig
  • 设置quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());

由此可看此件并没有直接和tickTime对比的机会。倒是minSessionTimeout没有设置,默认是2*ticktime

由此可见 其实如果设置了Zookeeper.session.timeout的话 不会轻易去截取20*ticktime,再不信可以用echo conf|nc zserver 2181 看一下 zookeeper系统参数

第二种情况是要讨论的,导致产生这个问题的主要原因是很多,产生的情景很多,比如在做 major compact的过程中,时间过长,导致Full GC等,那就尽量去减少这种情 况的发生。二个方面

  • 适度增大守护进程的HeapSize
  • 调整内存回收参数

第一个方面:Hbase默认各守护进程为1G 在hbase-env.sh中有配置 export HBASE_HEAPSIZE=1000,当我们启动hbase各守护进程的时候,那所有的hbase守护进程都将是1000的heapsize,对于有的进程,够用,但有些进程取远远不够,我们可以考虑增大此参数,比如export HBASE-HEAPSIZE=6000 那就把守护进程的heap 内存调大到6G,但是这样会有问题,有些进程不需要这么多,虽然设置的比较大不影响内存的实际占用,但却混淆了对各进程内存占用的认识。所以上述参数不做改变,在下面的参数中修改守护进程Heap 内存。

  • export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS $HBASE_JMX_BASE -Xmx2000m -Xms2000m -Xmn750m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70"
  • export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS $HBASE_JMX_BASE -Xmx6000m -Xms6000m -Xmn2250m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=70"
  • export HBASE_THRIFT_OPTS="$HBASE_THRIFT_OPTS $HBASE_JMX_BASE -Xms100m -Xmx2000m"
  • export HBASE_ZOOKEEPER_OPTS="$HBASE_ZOOKEEPER_OPTS $HBASE_JMX_BASE -Xms100m -Xmx2000m"

我们分别对各守护进程设置堆内存,其中-Xmx 表示最大可用内存,-Xms表示出事分配内存 -Xmn 表示 年轻代堆内存分配,这个值网上有的建议按照3/3 总heapsize来设置,因为是经验值,暂无法考证合理性,更多详细的堆内存分配参数,本地不做过多阐述,后面有机会可做一个单元来解释。那其它参数是什么意思呢?

第二个方面:调整内存回收参数,比如

-XX:+UseParNewGC 表示年轻带内存回收策略采用并发收集,此参数在JDK5.0已经自动配置,不需再手动配置;
-XX:+UseConcMarkSweepGC 表示年老代并发收集;
-XX:+CMSInitiatingOccupancyFraction表示年老代内存占用超过此比例即开始做CMS,这个参数很重要在JDK 5.0以后此值默认是90 也就是当年老代对内存占用90%以上时,

才开始做内存收集,而此时剩余的10%依然接受从年轻代迁移过来的对象,迁移过快,导致年老代heap 100%时,Full GC 即开始,才是会暂停所有的任务,直至Full GC 完 成,此时是造成RegionServer 意外退出的元凶,那为了安全起见,在调大堆内存的情况下 蒋此值降低到一个较低的阀值,减少Full GC的产生,那我建议此值设70%。

3、HQuorumPeer

此守护集成是Zookeeper的守护进程,因为我们用的是Hbase内置的ZooKeeper 所以此进程启动过程中,会读取hbase-env.sh 所以守护进程对内存和 HBASE-HEAPSIZE的一致,所以也应在hbase-env.sh中合理设置,见HRegionServer 小节中的参数设置方法。

4、ThriftServer
同上

当然,上述是在hbase-env.sh中对jvm的调优,还需要在hbase-site.xml中注意一些配置项

Hbase GC调优原理分析_内存调优_02

这里的HBase的优化主要从三个大的维度来进行分析

1、系统硬件

采用普通的PC Server即可,Master要求高一点(比如8 CPU,48G内存,SAS raid),Regionserver(如8CPU,24G内存,1T*12 SATA JBOD)

对于存储regionserver节点采用JBOD,master采用sas raid1+0

数据的存储在hdfs中本身考虑到了冗余,一般情况下replication设置为3,所以不用做raid;需要考虑的是每个节点的可用的存储空间的大小,所以这里用磁盘簇的方式。

网卡千兆的,做网卡bond,并且管理网段和请求网段分开,对于大的混合集群来说,可以设计多个vlan。

2、客户端client

a、 scan cache:顺序读的场景中,比如mapreduce做计算,可以设置在regionserver中的一次缓存的数量,可以加快scan的进度,

不过考虑到regionserver的内存限制,需要注意regionserver并发线程的控制。

b、在插入数据时,最好用批量插入操作,效率会更高一些。

c、auto flush最好disable掉

对于client来说,有一个writebuffer的缓冲,buffer满了之后,自动的发送数据到regionserver端

可以在某个时间点,手动调用flush的操作进行flush数据

调用close操作,会隐含调用flush操作

3、regionserver

a、对于操作,不要集中在一个region中插入,需要考虑rowkey的设计(rowkey如果是顺序设计的,这样会集中插入到一个region中),以及最好预先创建好split region

b、对于操作时,又分为两种情况,一种是顺序读,一种是随机读。

顺序读,最好rowkey是连续的设计的,这样可以从一个region中批量读数据;并且关闭block cache(客户端调用setBlockCache(false))。

随机读,打开block cache(设置cache合理大小,hfile.block.cache.size);采用booleam filter,提供随机查询的效率。

当然HFile块的大小设计很重要,随机读的情况下,可以设置小一点,顺序读的情况下,设计大一下。

c、对于操作,建议禁止major compact(hbase.hregion.majorcompaction设为0),该为手动方式,每天不忙的时候进行。

设置单个region的合理的大小(hbase.hregion.max.filesize),超过这个值,就自动进行split操作。

d、手动进行操作

e、大小的相关考虑

1) 对于region column family 的buffer,设置合理的checkpoint百分比,减少硬盘IO的操作;对buffer memstore设计合理大小,防止内存溢出。

主要有这三个参数,hbase.regionserver.global.memstore.upperLimit,hbase.regionserver.global.memstore.lowerLimit,memstore.flush .size

2)regionserver JVM参数设置

设置最大合理内存大小

垃圾回收策略,并发CMS的设置,比如-XX:CMSInitiatingOccupancyFraction=70,memstore limit 40%,block cache 20%,

防止promted fail(new区S区域放不下,放到旧生代,而旧生代空间不足),防止CMS的同时 ,对象迁移到旧生代,而旧生代空间不足。

为了避免CMS带来的碎片,可以考虑采用MSLAB

3) block cache,创建表时,设置该表对应的块是cache的,通过LRU算法淘汰;不过块中的数据索引dataIndex是放在内存中的。

f、的设置

1)可以压缩存储,减少磁盘和网络IO,有GZIP、LZO、Snappy,一般采用LZO或者Snappy。

2)对于HFile中块的大小设置,可以根据顺序读和随机读的比重,来考虑(顺序读,块设置大,随机读,块设置小一点)

g、regionserver并发处理线程的的数量(payload大,建议handler数量小一些,反之,建议handler数量大一些)

h、可以把一些操作,放在regionserver端执行,避免来回数据交互(filter,coprocessor,count)

相关参数设置参考

client

Scan Caching(默认是1条,设置成20条)
Scan Attribute Selection
Close ResultScanners
AutoFlush(默认是true,设置成false)
Bloom Filter

regionserver
内存

JVM(CMS GC,24G heap,-XX:CMSInitiatingOccupancyFraction=70)
hbase.regionserver.global.memstore.upperLimit(默认0.4)
hbase.regionserver.global.memstore.lowerLimit(默认0.3)
hbase.hregion.memstore.flush.size(64M,如果region的数量不多,可以设置的大一些,假如有1000个region*200M)
hbase.hregion.memstore.block.multiplier(默认是2)
hfile.block.cache.size(默认是0.2,场景中是读多写少,建议开大一些,0.4)
hbase.hregion.memstore.mslab.enabled

文件大小

hbase.hregion.max.filesize(默认256M,实际环境中为1G,每天手动split,compact)
hbase.hstore.blockingStoreFiles(默认7),通常情况下,一个storeFile大概在200M的样子,当进行major compact时,会合并成一个

线程设置

hbase.regionserver.handler.count(默认是10,设置成100,和内存大小有关系)

【注:如果hbase出现不稳定现象,还可能是以下原因】

hadoop的 namenode重新格式化以后,重启hbase,发现它的hmaster进程启动后马上消失,查看一大堆日志,最后在zookeeper的日志里发现如下问题

Unable to read additional data from client sessionid 0x14e86607c850007, likely client has closed socket

解决 方法:删除掉hbase的hbase-site.xml中一下内容所配置路径下的目录,重启zookeeper集群,再重启hbase让该目录重新生成即可

<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/infocenter/zookeeper/data</value>
</property>

hbase.zookeeper.property.dataDir:这个是ZooKeeper配置文件zoo.cfg中的dataDir。zookeeper存储数据库快照的位置。

hbase的配置项简介(hbase-site.xml)

Hbase配置项(1)
hbase.tmp.dir:本地文件系统的临时目录,默认是java.io.tmpdir/hbase?{user.name};
hbase.rootdir:hbase持久化的目录,被所有regionserver共享,默认${hbase.tmp.dir}/hbase,一般设置为hdfs://namenode.example.org:9000/hbase类似,带全限定名;
hbase.cluster.distributed:hbase集群模式运作与否的标志,默认是false,开启需要设置为true,false时启动hbase会在一个jvm中运行hbase和zk;
hbase.zookeeper.quorum:重要的也是必须设置的,启动zk的服务器列表,逗号分隔,cluster模式下必须设置,默认是localhost,hbase客户端也需要设置这个值去访问zk;
hbase.local.dir:本地文件系统被用在本地存储的目录,默认${hbase.tmp.dir}/local/;
hbase.master.port:hbase master绑定的端口,默认是60000;
hbase.master.info.port:hbase master web 界面的端口,默认是60010,设置为-1可以禁用ui;
hbase.master.info.bindAddress:master web界面的绑定地址,默认是0.0.0.0;
hbase.master.logcleaner.plugins:清理日志的插件列表,逗号分隔,被LogService调用的LogCleanerDelegate,可以自定义,顺序执行,清理WAL和HLog;默认org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner
hbase.master.logcleaner.ttl:HLog在.oldlogdir目录中生存的最长时间,过期则被Master起线程回收,默认是600000;
hbase.master.hfilecleaner.plugins:HFile的清理插件列表,逗号分隔,被HFileService调用,可以自定义,默认org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner
hbase.master.catalog.timeout:Catalog Janitor从master到META的超时时间,我们知道这个Janitor是定时的去META扫描表目录,来决定回收无用的regions,默认是600000;
fail.fast.expired.active.master:如果master过期,那么不需要从zk恢复,直接终止,默认是false;
hbase.master.dns.interface:master的dns接口,向该接口提供ip,默认是default;
hbase.master.dns.nameserver:master使用的dns主机名或者ip,默认是default;
hbase.regionserver.port:regionserver绑定的端口,默认是60020;
hbase.regionserver.info.port:regionserver的web界面端口,-1取消界面,默认是60030;
hbase.regionserver.info.bindAddress:regionserver的web绑定,默认是0.0.0.0;
hbase.regionserver.info.port.auto:master或者regionserver是否自动搜索绑定的端口,默认是false;
hbase.regionserver.handler.count:regionserver上rpc listener的个数,http://kenwublog.com/hbase-performance-tuning把这个配置称为io线程数,其实雷同,就是说在regionserver上一个处理rpc的handler,默认是30;
hbase.regionserver.msginterval:regionserver向master发消息的间隔,默认3000毫秒;
hbase.regionserver.optionallogflushinterval:如果没有足够的entry触发同步,那么过了这个间隔后HLog将被同步到HDFS,默认是1000毫秒;
hbase.regionserver.regionSplitLimit:regionsplit的最大限额,默认是MAX_INT=2147483647,设置这个限制后,在到达限制时region split就不会再进行;
hbase.regionserver.logroll.period:不管有多少版本,直接roll掉commit log的周期,也就是说一个固定的时间周期,到期就roll,默认是3600000毫秒;
hbase.regionserver.logroll.errors.tolerated:可接受的WAL关闭错误个数,到达后将触发服务器终止;设置为0那么在WAL writer做log rolling失败时就停止region server,默认是2;
hbase.regionserver.hlog.reader.impl:HLog 文件reader的实现类,默认是org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader;
hbase.regionserver.hlog.writer.impl:HLog 文件writer的实现类,默认是org.apache.hadoop.hbase.regionserver.wal.ProtobufLogWriter;
hbase.regionserver.global.memstore.upperLimit:memstore在regionserver内存中的上限,届时新的update被阻塞并且flush被强制写,默认是0.4就是堆内存的40%;阻塞状态持续到regionserver的所有memstore的容量到达hbase.regionserver.global.memstore.lowerLimit;
hbase.regionserver.global.memstore.lowerLimit:memstore在regionserver内存中的最大上限,到达时flush就被强制写,默认是0.38等价于38%的内存容量;
hbase.regionserver.optionalcacheflushinterval:一个edit版本在内存中的cache时长,默认3600000毫秒,设置为0的话则禁止自动flush;
hbase.regionserver.catalog.timeout:regionserver的Catalog Janitor访问META的超时时间,默认是600000;
hbase.regionserver.dns.interface:同master类似~~不讲
hbase.regionserver.dns.nameserver:同master类似
zookeeper.session.timeout:这是个值得说道一下的配置,首先ZK客户端要用,Hbase使用zk的客户端联系总体,同时也被用来启动一个zk server,作为zk的maxSessionTimeout,总的来说就是regionserver与zk的关键参数,如果连接超时,master会重新的balance,regionserver也会被从集群名单中清除,默认是90000;一个问题是如果zk 由hbase自己维护,那么该参数作为regionserver连接是一个值,如果zk在另外的集群,那么zk自己的maxSessionTimeout参数将优先于Hbase的该参数,届时可能会发生超时时间不同的问题;
zookeeper.znode.parent:znode存放root region的地址,默认是root-region-server;
zookeeper.znode.acl.parent:root znode的acl,默认acl;
hbase.zookeeper.dns.interface:zk的dns接口,默认default;
hbase.zookeeper.dns.nameserver:zk的dns服务地址,默认default;
hbase.zookeeper.peerport:zk的peer之间的通讯端口,默认是2888;
hbase.zookeeper.leaderport:zk选leader的通讯端口,默认是3888;
hbase.zookeeper.useMulti:zk支持多重update,要求zk在3.4版本以上,默认是false;
hbase.config.read.zookeeper.config:让hbaseconfig去读zk的config,默认false,也不支持开启,这个功能很搞笑~~个人观点;
hbase.zookeeper.property.initLimit:zk的配置,同步的属性个数限制,默认10个~~没用;
hbase.zookeeper.property.syncLimit:zk的配置,同步时的每次请求的条数,默认5个;
hbase.zookeeper.property.dataDir:zk的配置,snapshot存放的目录,默认是${hbase.tmp.dir}/zookeeper;
hbase.zookeeper.property.clientPort:zk的配置,client连zk的端口,默认2181;
hbase.zookeeper.property.maxClientCnxns:zk的配置,允许接入zk的最大并发连接数的限制,按ip分配,默认300;
Hbase配置项(2)
hbase的配置接上篇
hbase.client.write.buffer:htable客户端写缓冲区大小,默认是2097152BYTE,这个缓冲区就是为了写数据的临时存放,设置大了,浪费客户端和服务端的存储,设置小了,如果写的数据多,太多的RPC又带来网络开销,官方给的一个服务端存储耗费评估计算是:hbase.client.write.buffer*hbase.regionserver.handler.count,服务端的rs的处理handler个数也很关键;
hbase.client.pause:pause时长,在hbase发生get或其他操作fail掉的时候进行pause的时间长度,默认是100;
hbase.client.retries.number:发生操作fail时的重试次数,结合上一个指标一起来控制总的重试时间,默认是35;
hbase.client.max.total.tasks:一个HTable实例可以提交给集群的最大并发任务数,默认是100;
hbase.client.max.perserver.tasks:一个HTable实例给一台regionserver提交的最大并发任务数,默认是5;
hbase.client.max.perregion.tasks:客户端连接一台region的最大连接数,换句话说,当你有这么多个连接在region时,新的操作不被发送直到有操作完成,默认是1;
hbase.client.scanner.caching:做scanner的next操作时(如果再本地client没找到)缓存的数据行数,这个值的设置也需要权衡,缓存的多则快,但吃内存,缓存的少则需要多的拉数据,需要注意的事项是如果两次调用的时间差大于scanner的timeout,则不要设置该值,默认是100;
hbase.client.keyvalue.maxsize:一个KeyValue实例的最大大小,这是存储文件中一个entry的容量上限,合理的设置这个值可以控制regionserver的split,split不会拆keyvalue,所以把keyvalue的大小设置为regionserver大小的一个比例分数(可除)是个不错的选择,默认是10485760;
hbase.client.scanner.timeout.period:结合刚才的caching做的一个,scanner的超时时间,默认是60000毫秒;
hbase.client.localityCheck.threadPoolSize:做localityCheck的线程池大小,默认是2;
hbase.bulkload.retries.number:做bulk load的最大重试次数,默认是0,即代表不断重试;
hbase.balancer.period:Master运行balancer的周期,默认是300000毫秒;
hbase.regions.slop:如果有regionserver的region数目超过average+(average*slop),则rebalance,默认是0.2;
hbase.server.thread.wakefrequency:服务线程的sleep时间,默认10000毫秒,比如log roller;
hbase.server.versionfile.writeattempts:退出前写 version file的重试次数,默认3,每次尝试的间隔由上一个参数控制;
hbase.hregion.memstore.flush.size:Memstore写磁盘的flush阈值,超过这个大小就flush,默认是134217728;
hbase.hregion.preclose.flush.size:如果一个region的memstore的大小等于或超过这个参数的量,在关闭region时(放置关闭flag),要提前flush,然后region关闭下线,默认大小是5242880;
hbase.hregion.memstore.block.multiplier:如果memstore的大小满足hbase.hregion.block.memstore * hbase.hregion.flush.size个byte,那么阻塞update,这个配置可以避免不必要的长时间split或者compact,甚至是OOME,默认是2;
hbase.hregion.memstore.mslab.enabled:开启MemStore-Local Allocation Buffer,这个配置可以避免在高写入的情况下的堆内存碎片,可以降低在大堆情况下的stop-the-world GC频率,默认是true;
hbase.hregion.max.filesize:HStoreFile的最大尺寸,换句话说,当一个region里的列族的任意一个HStoreFile超过这个大小,那么region进行split,默认是10737418240;
hbase.hregion.majorcompaction:一个region的所有HStoreFile进行major compact的时间周期,默认是604800000 毫秒(7天);
hbase.hregion.majorcompaction.jitter:major compaction的发生抖动范围,这么理解比较容易,就是说上一个参数不是一个严格周期,会有个抖动,这个参数就是这个抖动的比例,默认是0.5;
hbase.hstore.compactionThreshold:一个HStore存储HStoreFile的个数阈值,超过这个阈值则所有的HStoreFile会被写到一个新的HStore,需要平衡取舍,默认是3;
hbase.hstore.blockingStoreFiles:一个HStore存储HStoreFile阻塞update的阈值,超过这个阈值,HStore就进行compaction,直到做完才允许update,默认是10;
hbase.hstore.blockingWaitTime:一个更强力的配置,配合上一个参数,当HStore阻塞update时,超过这个时间限制,阻塞取消,就算compaction没有完成,update也不会再被阻塞,默认是90000毫秒;
hbase.hstore.compaction.max:每个minor compaction的HStoreFile个数上限,默认是10;
hbase.hstore.compaction.kv.max:在flushing或者compacting时允许的最大keyvalue个数,如果有大的KeyValue或者OOME的话则配置一个小的值,如果行数多且小则配置大值,默认是10;
hbase.storescanner.parallel.seek.threads:如果并行查找开启的线程池大小,默认是10;
hfile.block.cache.size:一个配置比例,允许最大堆的对应比例的内存作为HFile和HStoreFile的block cache,默认是0.4,即40%,设置为0则disable这个比例,不推荐这么做;
hfile.block.index.cacheonwrite:在index写入的时候允许put无根(non-root)的多级索引块到block cache里,默认是false;
hfile.index.block.max.size:在多级索引的树形结构里,如果任何一层的block index达到这个配置大小,则block写出,同时替换上新的block,默认是131072;
hfile.format.version:新文件的HFile 格式版本,设置为1来测试向后兼容,默认是2;
hfile.block.bloom.cacheonwrite:对于组合布隆过滤器的内联block开启cache-on-write,默认是false;
io.storefile.bloom.block.size:一个联合布隆过滤器的单一块(chunk)的大小,这个值是一个逼近值,默认是131072;
hbase.rs.cacheblocksonwrite:当一个HFile block完成时是否写入block cache,默认是false;
Hbase配置项(3)
HBase的配置 完结篇:
hbase.rpc.server.engine:hbase 做rpc server的调度管理类,实现自org.apache.hadoop.ipc.RpcServerEngine,默认是org.apache.hadoop.hbase.ipc.ProtobufRpcServerEngine;
hbase.rpc.timeout:Hbase client发起远程调用时的超时时限,使用ping来确认连接,但是最终会抛出一个TimeoutException,默认值是60000;
hbase.rpc.shortoperation.timeout:另一个版本的hbase.rpc.timeout,控制短操作的超时时限,比如region server 汇报master的操作的超时时限可以设置小,这样有利于master的failover,默认是10000;
hbase.ipc.client.tcpnodelay:默认是true,具体就是在tcp socket连接时设置 no delay;
hbase.master.keytab.file:kerberos keytab 文件的全路径名,用来为HMaster做log,无默认值;
hbase.master.kerberos.principal:运行HMaster进程时需要kerberos的principal name,这个配置就是这个name的值,形如:hbase/_HOST@EXAMPLE.COM;
hbase.regionserver.keytab.file:kerberos keytab 文件的全路径名,用来为HRegionServer做log,无默认值;
hbase.regionserver.kerberos.principal:运行HRegionServer进程时需要kerberos的principal name,这个配置就是这个name的值,形如:hbase/_HOST@EXAMPLE.COM;
hadoop.policy.file:RPC服务器做权限认证时需要的安全策略配置文件,在Hbase security开启后使用,默认是habse-policy.xml;
hbase.superuser:Hbase security 开启后的超级用户配置,一系列由逗号隔开的user或者group;
hbase.auth.key.update.interval:Hbase security开启后服务端更新认证key的间隔时间:默认是86400000毫秒;
hbase.auth.token.max.lifetime:Hbase security开启后,认证token下发后的生存周期,默认是604800000毫秒;
hbase.ipc.client.fallback-to-simple-auth-allowed:client使用安全连接去链接一台非安全服务器时,服务器提示client切换到SASL SIMPLE认证模式(非安全),如果设置为true,则client同意切换到非安全连接,如果false,则退出连接;
hbase.coprocessor.region.classes:逗号分隔的Coprocessores列表,会被加载到默认所有表上。在自己实现了一个Coprocessor后,将其添加到Hbase的classpath并加入全限定名。也可以延迟加载,由HTableDescriptor指定;
hbase.rest.port:Hbase REST服务器的端口,默认是8080;
hbase.rest.readonly:定义REST服务器启动的模式,有两种方式,false:所有http方法都将被通过-GET/PUT/POST/DELETE,true:只有get方法ok。默认值是false;
hbase.rest.threads.max:REST服务器线程池的最大线程数,池满的话新请求会自动排队,限制这个配置可以控制服务器的内存量,预防OOM,默认是100;
hbase.rest.threads.min:同上类似,最小线程数,为了确保服务器的服务状态,默认是2;
hbase.rest.support.proxyuser:使REST服务器支持proxy-user 模式,默认是false;
hbase.defaults.for.version.skip:是否跳过hbase.defaults.for.version的检查,默认是false;
hbase.coprocessor.master.classes:由HMaster进程加载的coprocessors,逗号分隔,全部实现org.apache.hadoop.hbase.coprocessor.MasterObserver,同coprocessor类似,加入classpath及全限定名;
hbase.coprocessor.abortonerror:如果coprocessor加载失败或者初始化失败或者抛出Throwable对象,则主机退出。设置为false会让系统继续运行,但是coprocessor的状态会不一致,所以一般debug时才会设置为false,默认是true;
hbase.online.schema.update.enable:设置true来允许在线schema变更,默认是true;
hbase.table.lock.enable:设置为true来允许在schema变更时zk锁表,锁表可以组织并发的schema变更导致的表状态不一致,默认是true;
hbase.thrift.minWorkerThreads:线程池的core size,在达到这里配置的量级后,新线程才会再新的连接创立时创建,默认是16;
hbase.thrift.maxWorkerThreads:顾名思义,最大线程数,达到这个数字后,服务器开始drop连接,默认是1000;
hbase.thrift.maxQueuedRequests:Thrift连接队列的最大数,如果线程池满,会先在这个队列中缓存请求,缓存上限就是该配置,默认是1000;
hbase.thrift.htablepool.size.max:Thrift服务器上table pool的最大上限,默认是1000;
hbase.offheapcache.percentage:JVM参数-XX:MaxDirectMemorySize的百分比值,默认是0,即不开启堆外分配;
hbase.data.umask.enable:开启后,文件在regionserver写入时会有权限相关设定,默认是false不开启;
hbase.data.umask:开启上面一项配置后,文件的权限umask,默认是000;
hbase.metrics.showTableName:是否为每个指标显示表名前缀,默认是true;
hbase.metrics.exposeOperationTimes:是否进行关于操作在使用时间维度的指标报告,比如GET PUT DELETE INCREMENT等,默认是true;
hbase.snapshot.enabled:是否允许snapshot被使用、存储和克隆,默认是true;
hbase.snapshot.restore.take.failsafe.snapshot:在restore过程中,如果失败则启用snapshot替换,成功则删除掉snapshot,默认开启true;
hbase.snapshot.restore.failsafe.name:刚才所说过程中snapshot的名字,默认是hbase-failsafe-{snapshot.name}-{restore.timestamp};
hbase.server.compactchecker.interval.multiplier:检查是否需要compact的时间间隔,一般情况是在比如memstore flush后或者其他事件触发compact的,但是有时也需要不同的compact策略,所以需要周期性的检查具体间隔=hbase.server.compactchecker.interval.multiplier * hbase.server.thread.wakefrequency,默认1000;
hbase.lease.recovery.timeout:在dfs 租约超时时限,超时则放弃,默认是900000;
hbase.lease.recovery.dfs.timeout:dfs恢复租约调用的超时时限,默认是64000;

参考:http://www.voidcn.com/article/p-fhsniugm-bby.html