Hadoop2.7.1+Hbase1.2.1集群环境搭建(7)hbase 性能优化【转】

 转:http://blog.csdn.net/zilong_zilong/article/details/53808912

目录(?)[+]

 
 
 
 
 
 
 
(1)hadoop2.7.1源码编译 http://aperise.iteye.com/blog/2246856
(2)hadoop2.7.1安装准备 http://aperise.iteye.com/blog/2253544
(3)hadoop2.7.1安装 http://aperise.iteye.com/blog/2245547
(4)hbase安装准备 http://aperise.iteye.com/blog/2254451
(5)hbase安装 http://aperise.iteye.com/blog/2254460
(6)snappy安装 http://aperise.iteye.com/blog/2254487
(7)hbase性能优化 http://aperise.iteye.com/blog/2282670
(8)雅虎YCSBC测试hbase性能测试 http://aperise.iteye.com/blog/2248863
(9)spring-hadoop实战 http://aperise.iteye.com/blog/2254491

 Hbase节点出问题,一般是ZK认为该hbase节点不可用,主动从ZK中踢出了该hbase节点;

该hbase节点发现ZK上自己被踢出,自己发起shutdown关闭服务;

一般解决问题思路是查看该hbase节点的日志,从日志入手解决问题,目前已知如下状况会导致节点宕机:

1)FULL GC,优化GC设置,修改HBASE_REGIONSERVER_OPTS,采用并发回收机制等;

2)所有分区memstore一起flushing,阻塞一切读写,达到ZK超时时间,归根结底是给的内存太少,加大HBASE_HEAPSIZE;

3)split操作阻塞了读写,达到ZK超时时间,提前做规划,提前预分区,防止后期频繁split;

 

1.前言

    使用hbase有一段时间了,从最开始对hbase读写性能的怀疑,到最后对hbase读写性能的肯定,经历了一个漫长的过程,在此,对hbase相关性能优化写一点个人的总结。

 

2.官方关于性能优化(最权威)

    所有关于技术类的文档,一般官网会有个优化建议,怎么去找呢,一般文档中搜索“Performance Tuning”,意思为性能优化,即可查到。

    官方文档其实写的很全面,但点到即止,主要从操作系统、网络、Java、HBase 配置、ZooKeeper、Schema 设计阐述了相关性能优化建议,这里只是贴出文档地址,我在这里不做过多讲解。

    2.1 性能优化英文版https://hbase.apache.org/0.94/book.html#performance


 

    2.2 性能优化中文版http://abloz.com/hbase/book.html#performance


 

3.性能优化关键点

    3.1 操作系统优化

        3.1.1 机器配置

        hbase的机器配置建议2U 2cpu 6cores/cpu 16G*4 12 * 2T SATA

        hbase针对每个列簇每个区分配一个memstore=128MB供写数据,同时提供一个blockcache采用LRU等算法供读取数据,而hbase预分区越多,需要消耗的memstore和blockcache就更多,所以内存越多越好

        hbase的机器优选64位的,不过这都是目前所有机器的标配了。

 

        3.1.2 linux打开文件数和进程数

        默认Linux打开文件数和打开进程数太低,试想一下,在分布式文件系统HDFS上打开成千上万的文件,原有的linux配置,远远不能满足需求,所以必须调大。

        centos7修改/etc/security/limits.conf ,在最后增加如下内容:

 

* soft nofile 102400
* hard nofile 409600
        centos7修改/etc/security/limits.d/20-nproc.conf,在最后增加如下内容:

 

* soft nproc 409600
* hard nproc 819200

        3.1.3 机器时间

        安装NTP服务保证hbase集群机器时间时刻同步,最少不要大于30秒(hbase机器间时差默认值),因为hbase的表里默认列timestamp都需要用到机器时间,而作为分布式列式数据库机器间时间统一很重要

 

        3.1.4 交换区

        建议将 /proc/sys/vm/swappiness 设置为最大值 10或者0。默认值为 60。使用 sysctl 命令在运行时更改该设置并编辑 /etc/sysctl.conf,以在重启后保存该设置。

 

 

#vi /etc/sysctl.conf
vm.swappiness = 10
 

 

        3.1.5 禁用透明大页面压缩

        默认启用透明大页面压缩,可能会导致重大性能问题。请运行“echo never > /sys/kernel/mm/transparent_hugepage/defrag”以禁用此设置,然后将同一命令添加到 /etc/rc.local 等初始脚本中,以便在系统重启时予以设置。

 

#vi /etc/rc.local
echo never > /sys/kernel/mm/transparent_hugepage/defrag

 

 

    3.2 网络

        网络设备最低选择千兆网卡,最好万兆网卡

        性能好的交换机

        跨机房多机架部署Hadoop集群;

        双电源确保断电故障。

 

    3.3 java

        3.3.1 JDK版本

        JDK版本,首先要看hadoop对JDK版本要求,在hadoop2.7.1里要求最少JDK1.6+

        hbase里没明确说明,至少也是JDK1.6+;

        目前多半是建议JDK1.8

 

        3.3.2 JVM参数调整

        hbase基于HDFS之上,所以首先得优化HDFS内存,而HDFS里namenode节点内存直接决定你HDFS里最多文件个数,datanode里内存也相应要调整,最后是优化GC,在hadoop-env.sh里配置HADOOP_NAMENODE_OPTSHADOOP_DATANODE_OPTS内存GC如下:

export HADOOP_NAMENODE_OPTS="-Xmx5g -Xms5g -Xmn256M -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hadoop-2.7.1/logs/gc-hadoop-namenode.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"

export HADOOP_DATANODE_OPTS="-Xmx5g -Xms5g -Xmn256M -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hadoop-2.7.1/logs/gc-hadoop-datanode.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"
 

 

    3.4 hadoop优化

        3.4.1 hadoop的HADOOP_NAMENODE_OPTS和HADOOP_DATANODE_OPTS优化

        上面3.3.2里已经涉及

 

        3.4.2 hadoop的HA

        hbase是基于HDFS的,所以hadoop的尽量做hadoop HA部署,保证两个namenode(一个active 一个standby),保证HDFS故障后自动切换,高可用

 

        3.4.3 hadoop的HDFS并发处理能力

        hbase其实是HDFS的客户端,hbase的数据最终要落地到HDFS,所以并发处理HDFS的能力必须提升,下面的配置你必须优化:

  1. hadoop的core-site.xml的io.file.buffer.size
  2. hadoop的core-site.xml的io.compression.codecs
  3. hadoop的hdfs-site.xml的dfs.namenode.handler.count
  4. hadoop的hdfs-site.xml的dfs.datanode.handler.count
  5. hadoop的hdfs-site.xml的dfs.datanode.max.transfer.threads
  6. hadoop的hdfs-site.xml的dfs.datanode.balance.bandwidthPerSec

 

[xml] view plain copy
 
 print?
  1. <property>    
  2.     <!--hadoop访问文件的IO操作都需要通过代码库。因此,在很多情况下,io.file.buffer.size都被用来设置SequenceFile中用到的读/写缓存大小。不论是对硬盘或者是网络操作来讲,较大的缓存都可以提供更高的数据传输,但这也就意味着更大的内存消耗和延迟。这个参数要设置为系统页面大小的倍数,以byte为单位,默认值是4KB,一般情况下,可以设置为64KB(65536byte),这里设置128K-->    
  3.     <name>io.file.buffer.size</name>    
  4.     <value>131072</value>    
  5. </property>   
  6. <property>    
  7.     <name>io.compression.codecs</name>    
  8.     <value>org.apache.hadoop.io.compress.SnappyCodec</value>    
  9. </property>    
 
[xml] view plain copy
 
 print?
  1. <property>  
  2.     <!--namenode并发线程数-->   
  3.     <name>dfs.namenode.handler.count</name>  
  4.     <value>600</value>  
  5.     <description>The number of server threads for the namenode.</description>  
  6. </property>  
  7. <property>  
  8.     <!--datanode并发线程数-->   
  9.     <name>dfs.datanode.handler.count</name>  
  10.     <value>600</value>  
  11. </property>  
  12. <property>    
  13.     <!--这里设置Hadoop允许打开最大文件数,默认4096,不设置的话会提示xcievers exceeded错误-->    
  14.     <name>dfs.datanode.max.transfer.threads</name>    
  15.     <value>409600</value>    
  16. </property>    
  17. <property>  
  18.     <!—start-balancer时,hdfs移动数据的速度,默认值为1M/S的速度。一般情况下设置为50M;设置的过大会影响当前job的运行-->  
  19.     <name>dfs.datanode.balance.bandwidthPerSec</name>  
  20.     <value>52428800</value>  
  21. </property>  
 

 

    3.5 zookeeper

        zookeeper至少3台,其次最好奇数台奇数是由zookeeper的少数服从多数的选举机制决定

        zookeeper.session.timeout默认为180秒,太长,修改zookeeper.session.timeout之前请首先一定要优化hbase的GC配置后才改此项值,hbase团队设置180秒是为了防止hbase初级使用者在不优化hbase GC的情况下,频繁因为GC导致hbase节点与zookeeper之间超时才设置的180秒,所以对于熟练者你改此值之前请确保你已经修改hbase GC

 

    3.6 hbase优化

        3.6.1 hbase客户端优化

        hbase客户端的源码我在另一篇博客源码解读--(1)hbase客户端源代码中进行介绍,了解源码只是为了让你能清醒的去优化hbase客户端。hbase客户端优化关键项目如下:

  • hbase客户端里传入hbase.client.write.buffer(默认2MB),加到客户端提交的缓存大小;
  • hbase客户端提交采用批量提交,批量提交的List<Put>的size计算公式=hbase.client.write.buffer*2/Put大小,Put大小可通过put.heapSize()获取,以hbase.client.write.buffer=2097152,put.heapSize()=1320举例,最佳的批量提交记录大小=2*2097152/1320=3177;
  • hbase客户端尽量采用多线程并发写
  • hbase客户端所在机器性能要好,不然速度上不去

下面是我当时在调研hbase时候做过的压测记录:

 

 
        操作hbase你只需在maven里引入如下依赖项:

[xml] view plain copy
 
 print?
  1. <dependency>    
  2.     <groupId>org.apache.hbase</groupId>    
  3.     <artifactId>hbase-client</artifactId>    
  4.     <version>1.2.1</version>    
  5. </dependency>  

    建议的客户端操作代码如下:

[java] view plain copy
 
 print?
  1. Configuration configuration = HBaseConfiguration.create();    
  2. configuration.set("hbase.zookeeper.property.clientPort", "2181");    
  3. configuration.set("hbase.client.write.buffer", "2097152");    
  4. configuration.set("hbase.zookeeper.quorum","192.168.199.31,192.168.199.32,192.168.199.33,192.168.199.34,192.168.199.35");    
  5. Connection connection = ConnectionFactory.createConnection(configuration);    
  6. Table table = connection.getTable(TableName.valueOf("tableName"));    
  7. try {    
  8.   // Use the table as needed, for a single operation and a single thread    
  9.   // construct List<Put> putLists    
  10.   List<Put> putLists = new ArrayList<Put>();  
  11.   for(int count=0;count<100000;count++){  
  12.     Put put = new Put(rowkey.getBytes());  
  13.     put.addImmutable("columnFamily1".getBytes(), "columnName1".getBytes(), "columnValue1".getBytes());  
  14.     put.addImmutable("columnFamily1".getBytes(), "columnName2".getBytes(), "columnValue2".getBytes());  
  15.     put.addImmutable("columnFamily1".getBytes(), "columnName3".getBytes(), "columnValue3".getBytes());  
  16.         put.setDurability(Durability.SKIP_WAL);  
  17.     putLists.add(put);  
  18.       
  19.     //3177不是我杜撰的,是2*hbase.client.write.buffer/put.heapSize()计算出来的  
  20.     if(putLists.size()>=3177-1){  
  21.       //达到最佳大小值了,马上提交一把  
  22.         table.put(putLists);  
  23.         putLists.clear();  
  24.     }  
  25.   }  
  26.   //剩下的未提交数据,最后做一次提交  
  27.   table.put(putLists)    
  28. finally {    
  29.   table.close();    
  30.   connection.close();    
  31. }    

 

        3.6.2 hbase服务端优化

            3.6.2.1 hbase服务端源代码对于内存的分配规律

  • 1)Hbase内存分配=memstore(写)+blockcache(读)+other(其他)
  • 2)Memstore占内存百分比(写)+blockcache占内存百分比(读)<=0.8
  • 3)Memstore有两个临界点,第一个临界点是hbase.regionserver.global.memstore.size.lower.limit,默认=0.95,达到这个点,会选择当前region里memstore最大那个flushing;第二个临界点hbase.regionserver.global.memstore.size,默认=0.4,达到这个点,所有region做flushing;
  • 4)Blockcache通过hfile.block.cache.size设置,默认=0.4

 

            3.6.2.2 hbase内存配置多大合适

            经验公式如下:

hbase.hregion.memstore.flush.size*单机hbase的region个数/hbase.regionserver.global.memstore.size/hbase.regionserver.global.memstore.size.lower.limit

            举例如下:我的hbase要求写入快,读取速度在写入速度之后考虑,那么我把内存尽可能多的给到写,所以我调整hbase.regionserver.global.memstore.size=0.6,hbase.regionserver.global.memstore.size.lower.limit=0.6,hfile.block.cache.size=0.1,这样0.6+0.1<0.8首先没有违背hbase的大原则hbase.hregion.memstore.flush.size=128MB保持不变毕竟HDFS的block刚好也是128MB,我预估每个机器最后单节点上负载hbase100个区,那么我hbase节点的内存要配置的最大值为128MB*100/0.6/0.6=35555MB=35GB,所以修改hbase-env.sh里如下配置:

export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS –Xmx35g –Xms35g –Xmn2g -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/home/hadoop/hbase-1.2.1/logs/gc-hbase-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"

 

            3.6.2.3 hbase 服务端GC优化

            配置详见3.6.2.2

 

           3.6.2.4 hbase的split和预分区

           hbase从设计那一刻起就要尽最大可能规避hbase的split操作,split的意思是hbase单区文件大小过大,需要拆分为两个文件,而避免hbase不做split的最好的办法就是提前预分区,一个预分区建表语句如下:

disable 'habsetest'
drop 'habsetest'
n_splits = 108 
create 'hbasetest', {NAME => 'info', TTL=>'15552000', COMPRESSION => 'SNAPPY'}, {SPLITS => (1..n_splits).map {|i| "#{i*999/n_splits}"}}

           最好的预分区是做到今后都不会发生split操作,那么预分区多少呢?这里有个逐步计算方法:

  1. 首先你要知道你这个hbasetest数据表数据保留多久,比如保留半年,也即180天;
  2. 第二步,你得观察每天数据量,例如每天hbasetest的数据量会产生10GB的数据;
  3. 第三步,你得知道你配置的单个region文件的大小,比如hbase.hregion.max.filesize=53687091200,意思是单个region最大50GB;
  4. 第四步,开始计算,180天的数据量=10GB*180*HADOOP备份数3=5400GB,这些数据占用分区数=108,
  5. 第五步,开始建表时候你就知道你必须在设计时候就得建立108个预分区同时设置数据只保留15552000秒之内的数据,也即保留180天内的数据,这样,只要你的估算准确,永远不会进行split操作,就算做,也只是少数一两个区split做而已,基本不影响hbase读写性能。

            3.6.2.5 hbase的compact

            hbase的memstore会不断刷小文件,而compact会不断合并小文件和清理过期数据和标记删除的数据,compact又分major compact和minor compact,我们要尽量关闭major compact变成手动在空闲期让它做major compact,

[xml] view plain copy
 
 print?
  1. <property>  
  2.     <name>hbase.hregion.majorcompaction</name>  
  3.     <value>0</value>  
  4.     <description>禁止majorcompaction,这里虽然禁止了,但是还是得做,是通过linux定时任务在空闲时间执行</description>  
  5. </property>  

             在hbase空闲期通过设置linux 的crontab定时任务来做major compact

cd /opt/hbase-1.2.1/bin
./hbase shell
major_compact 'hbasetest'
quit

 

        3.6.2.6 合理设计rowkey

        rowkey一定要设计合理,关于rowkey,你要理解如下:

  1. hbase对于rowkey的处理是把rowkey按照ASCII码字典序来处理的,意思是ASCII对应的顺序字符的二进制顺序来处理,例如0-9字符的Byte值<大写字母A-Z<小写字母a-z;
  2. hbase会按照这种ASCII字典序把rowkey和每个区的start rowkey和end rowkey对比,就知道该把这条记录写到哪个区

        所以,rowkey的设计一定要尽量使得记录随机化离散化,不然会导致数据倾斜

        3.6.2.7 hbase的split策略

              hbase的split策略有2个:

  • IncreasingToUpperBoundRegionSplitPolicy策略的意思是,数据表如果预分区为2,配置的memstore flush size=128M,那么下一次分裂大小是2的平方然后乘以128MB,即2*2*128M=512MB;  
  • ConstantSizeRegionSplitPolicy策略的意思是按照上面指定的region大小超过30G才做分裂

             默认的策略是IncreasingToUpperBoundRegionSplitPolicy,很多人向我讨教,为啥设置了hbase.hregion.max.filesize=53687091200,也即50GB一个区,但是还没达到50GB就做split了呢,原因就是这个策略并不是你所认为的策略,可能你压根就没改过split策略的配置

             所以如果你想超过50GB做split,那么首先你得配置hbase.hregion.max.filesize=53687091200,然后配置

[xml] view plain copy
 
 print?
  1. <property>  
  2.     <name>hbase.hregion.max.filesize</name>  
  3.     <value>53687091200</value>  
  4.     <description>设置每个数据表中单个region存储的hfile最大值50G,只有超过此值才做split</description>  
  5. </property>  
  6. <property>  
  7.     <name>hbase.regionserver.region.split.policy</name>  
  8.     <value>org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy</value>  
  9.     <description>这个需要和hbase.hregion.max.filesize结合使用</description>  
  10. </property>  

 

 

hbase服务端优化补充说明

                1)参数hbase.regionserver.handler.count的本质是设置一个RegsionServer可以同时处理多少请求。 如果定的太高,吞吐量反而会降低;如果定的太低,请求会被阻塞,得不到响应。你可以打开RPC-level日志读Log,来决定对于你的集群什么值是合适的。(请求队列也是会消耗内存的)。我的配置如下:

[xml] view plain copy
 
 print?
  1. <property>    
  2.         <name>hbase.regionserver.handler.count</name>    
  3.         <value>300</value>    
  4.         <description>Count of RPC Listener instances spun up on RegionServers.Same property is used by the Master for count of master handlers.</description>    
  5. </property>    

                2)hbase-env.sh中HEAP_SIZE优化

                     修改hbase-1.2.1/conf/hbase-env.sh中HBASE_HEAPSIZE,我的配置如下:

export HBASE_HEAPSIZE=4G

                3)hbase内存配置,内存配置先要了解hbase内存模型,见下图:


  1.  .每一个Region都有一个Memstore,Memstore默认大小为128MB,可通过hbase.hregion.memstore.flush.size更改;
  2. Region会随着split操作逐步增多,为了控制Memstore之和导致OOM错误,在hbase老版本中是通过hbase.regionserver.global.memstore.upperLimit和hbase.regionserver.global.memstore.lowerLimit进行控制,新版本中使用hbase.regionserver.global.memstore.size和hbase.regionserver.global.memstore.lowerLimit控制;
  3. Hbase-env.sh中HEAP_SIZE=4G时,老版本Hbase.regionserver.global.memstore.upperLimit(默认HEAP_SIZE*0.4)=1.6G,hbase.regionserver.global.memstore.lowerLimit(默认HEAP_SIZE*0.35)=1.4G,新版本hbase.regionserver.global.memstore.size(默认HEAP_SIZE*0.4)=1.6G和Hbase.regionserver.global.memstore.lowerLimit(hbase.regionserver.global.memstore.size*HEAP_SIZE*0.95)=1.52G;
  4. Memstore总和达到第一个临界值,会在所有memstore中选择一个最大的那个进行flushing,此时不会阻塞写;
  5. Memstore总和达到第二个临界值,会阻塞所有的读写,将当前所有memstore进行flushing。
  6. 每一个Region都有一个BlockCache,BlockCache总和默认打下为HEAP_SIZE乘以0.4,默认是通过hfile.block.cache.size设置;
  7. 所有的读请求,先到BlockCache中查找,基本Memstore中有的值在BlockCache中也都有,找不到再去Hfile中找。
  8. hbase中默认规定Memstore总和最大值(hbase.regionserver.global.memstore.size默认0.4)和BlockCache总和最大值(hfile.block.cache.size默认0.4)之和不能大于0.8,因为要预留0.2的HEAP_SIZE供其他操作使用,这个可详见hbase源代码Org.apache.hadoop.hbase.io.util.HeapMemorySizeUtil.java文件。
[java] view plain copy
 
 print?
  1. /** 
  2.  * Licensed to the Apache Software Foundation (ASF) under one 
  3.  * or more contributor license agreements.  See the NOTICE file 
  4.  * distributed with this work for additional information 
  5.  * regarding copyright ownership.  The ASF licenses this file 
  6.  * to you under the Apache License, Version 2.0 (the 
  7.  * "License"); you may not use this file except in compliance 
  8.  * with the License.  You may obtain a copy of the License at 
  9.  * 
  10.  *     http://www.apache.org/licenses/LICENSE-2.0 
  11.  * 
  12.  * Unless required by applicable law or agreed to in writing, software 
  13.  * distributed under the License is distributed on an "AS IS" BASIS, 
  14.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  15.  * See the License for the specific language governing permissions and 
  16.  * limitations under the License. 
  17.  */  
  18. package org.apache.hadoop.hbase.io.util;  
  19.   
  20. import java.lang.management.ManagementFactory;  
  21. import java.lang.management.MemoryUsage;  
  22.   
  23. import org.apache.commons.logging.Log;  
  24. import org.apache.commons.logging.LogFactory;  
  25. import org.apache.hadoop.hbase.classification.InterfaceAudience;  
  26. import org.apache.hadoop.conf.Configuration;  
  27. import org.apache.hadoop.hbase.HConstants;  
  28.   
  29. @InterfaceAudience.Private  
  30. public class HeapMemorySizeUtil {  
  31.   
  32.   public static final String MEMSTORE_SIZE_KEY = "hbase.regionserver.global.memstore.size";  
  33.   public static final String MEMSTORE_SIZE_OLD_KEY =  
  34.       "hbase.regionserver.global.memstore.upperLimit";  
  35.   public static final String MEMSTORE_SIZE_LOWER_LIMIT_KEY =  
  36.       "hbase.regionserver.global.memstore.size.lower.limit";  
  37.   public static final String MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY =  
  38.       "hbase.regionserver.global.memstore.lowerLimit";  
  39.   
  40.   public static final float DEFAULT_MEMSTORE_SIZE = 0.4f;  
  41.   // Default lower water mark limit is 95% size of memstore size.  
  42.   public static final float DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT = 0.95f;  
  43.   
  44.   private static final Log LOG = LogFactory.getLog(HeapMemorySizeUtil.class);  
  45.   // a constant to convert a fraction to a percentage  
  46.   private static final int CONVERT_TO_PERCENTAGE = 100;  
  47.   
  48.   /** 
  49.    * Checks whether we have enough heap memory left out after portion for Memstore and Block cache. 
  50.    * We need atleast 20% of heap left out for other RS functions. 
  51.    * @param conf 
  52.    */  
  53.   public static void checkForClusterFreeMemoryLimit(Configuration conf) {  
  54.     if (conf.get(MEMSTORE_SIZE_OLD_KEY) != null) {  
  55.       LOG.warn(MEMSTORE_SIZE_OLD_KEY + " is deprecated by " + MEMSTORE_SIZE_KEY);  
  56.     }  
  57.     float globalMemstoreSize = getGlobalMemStorePercent(conf, false);  
  58.     int gml = (int)(globalMemstoreSize * CONVERT_TO_PERCENTAGE);  
  59.     float blockCacheUpperLimit = getBlockCacheHeapPercent(conf);  
  60.     int bcul = (int)(blockCacheUpperLimit * CONVERT_TO_PERCENTAGE);  
  61.     if (CONVERT_TO_PERCENTAGE - (gml + bcul)  
  62.             < (int)(CONVERT_TO_PERCENTAGE *  
  63.                     HConstants.HBASE_CLUSTER_MINIMUM_MEMORY_THRESHOLD)) {  
  64.       throw new RuntimeException("Current heap configuration for MemStore and BlockCache exceeds "  
  65.           + "the threshold required for successful cluster operation. "  
  66.           + "The combined value cannot exceed 0.8. Please check "  
  67.           + "the settings for hbase.regionserver.global.memstore.size and "  
  68.           + "hfile.block.cache.size in your configuration. "  
  69.           + "hbase.regionserver.global.memstore.size is " + globalMemstoreSize  
  70.           + " hfile.block.cache.size is " + blockCacheUpperLimit);  
  71.     }  
  72.   }  
  73.   
  74.   /** 
  75.    * Retrieve global memstore configured size as percentage of total heap. 
  76.    * @param c 
  77.    * @param logInvalid 
  78.    */  
  79.   public static float getGlobalMemStorePercent(final Configuration c, final boolean logInvalid) {  
  80.     float limit = c.getFloat(MEMSTORE_SIZE_KEY,  
  81.         c.getFloat(MEMSTORE_SIZE_OLD_KEY, DEFAULT_MEMSTORE_SIZE));  
  82.     if (limit > 0.8f || limit <= 0.0f) {  
  83.       if (logInvalid) {  
  84.         LOG.warn("Setting global memstore limit to default of " + DEFAULT_MEMSTORE_SIZE  
  85.             + " because supplied value outside allowed range of (0 -> 0.8]");  
  86.       }  
  87.       limit = DEFAULT_MEMSTORE_SIZE;  
  88.     }  
  89.     return limit;  
  90.   }  
  91.   
  92.   /** 
  93.    * Retrieve configured size for global memstore lower water mark as percentage of total heap. 
  94.    * @param c 
  95.    * @param globalMemStorePercent 
  96.    */  
  97.   public static float getGlobalMemStoreLowerMark(final Configuration c, float globalMemStorePercent) {  
  98.     String lowMarkPercentStr = c.get(MEMSTORE_SIZE_LOWER_LIMIT_KEY);  
  99.     if (lowMarkPercentStr != null) {  
  100.       return Float.parseFloat(lowMarkPercentStr);  
  101.     }  
  102.     String lowerWaterMarkOldValStr = c.get(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY);  
  103.     if (lowerWaterMarkOldValStr != null) {  
  104.       LOG.warn(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " is deprecated. Instead use "  
  105.           + MEMSTORE_SIZE_LOWER_LIMIT_KEY);  
  106.       float lowerWaterMarkOldVal = Float.parseFloat(lowerWaterMarkOldValStr);  
  107.       if (lowerWaterMarkOldVal > globalMemStorePercent) {  
  108.         lowerWaterMarkOldVal = globalMemStorePercent;  
  109.         LOG.info("Setting globalMemStoreLimitLowMark == globalMemStoreLimit " + "because supplied "  
  110.             + MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " was > " + MEMSTORE_SIZE_OLD_KEY);  
  111.       }  
  112.       return lowerWaterMarkOldVal / globalMemStorePercent;  
  113.     }  
  114.     return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;  
  115.   }  
  116.   
  117.   /** 
  118.    * Retrieve configured size for on heap block cache as percentage of total heap. 
  119.    * @param conf 
  120.    */  
  121.   public static float getBlockCacheHeapPercent(final Configuration conf) {  
  122.     // L1 block cache is always on heap  
  123.     float l1CachePercent = conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,  
  124.         HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);  
  125.     float l2CachePercent = getL2BlockCacheHeapPercent(conf);  
  126.     return l1CachePercent + l2CachePercent;  
  127.   }  
  128.   
  129.   /** 
  130.    * @param conf 
  131.    * @return The on heap size for L2 block cache. 
  132.    */  
  133.   public static float getL2BlockCacheHeapPercent(Configuration conf) {  
  134.     float l2CachePercent = 0.0F;  
  135.     String bucketCacheIOEngineName = conf.get(HConstants.BUCKET_CACHE_IOENGINE_KEY, null);  
  136.     // L2 block cache can be on heap when IOEngine is "heap"  
  137.     if (bucketCacheIOEngineName != null && bucketCacheIOEngineName.startsWith("heap")) {  
  138.       float bucketCachePercentage = conf.getFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 0F);  
  139.       MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();  
  140.       l2CachePercent = bucketCachePercentage < 1 ? bucketCachePercentage  
  141.           : (bucketCachePercentage * 1024 * 1024) / mu.getMax();  
  142.     }  
  143.     return l2CachePercent;  
  144.   }  
  145. }  
       综上所述,我在hbase-site.xml中配置信息如下:

 

[xml] view plain copy
 
 print?
  1. <property>  
  2.     <name>hfile.block.cache.size</name>  
  3.     <value>0.3</value>  
  4. </property>  
  5. <property>  
  6.     <name>hbase.regionserver.global.memstore.size.lower.limit</name>  
  7.     <value>0.5</value>  
  8. </property>  
  9. <property>  
  10.     <name>hbase.regionserver.global.memstore.size</name>  
  11.     <value>0.5</value>  
  12. </property>  
      这样在HEAP_SIZE=4G时候,

           hfile.block.cache.size计算值为4G*0.3=1.2G;

           hbase.regionserver.global.memstore.size计算值为4G*0.5=2G;

           hbase.regionserver.global.memstore.size.lower.limit计算值为4G*0.5*0.5=1G;

          并且0.3+0.5<=0.8,没有超过hbase设置的不能超过0.8这个值

 

预分区补充说明

 上图说明的问题:

         1)创建表指定和不指定预分区是有本质区别的;

         2)创建表不指定预分区,hbase默认只创建一个区,默认区大小为4GB,最开始读写数据都在这一个区,而这个区只是在集群一台机器上有,造成集群中单台机器负载过大,而其他机器都一直空闲;当文件大于10GB时,hbase暂停几分钟用来做split和compact,分裂为两个区,但新的数据写全部又集中到新的第二区,问题依旧是其他机器空闲;

          3)创建表指定预分区,数据会根据提供的rowkey与建表时预分区做对比,将数据分布到不同预分区读写,达到负载均衡

 结论:

        建表必须指定预分区才能提高hbase并发读写性能,否则,就别玩hbase了。

 

rowkey设计补充说明

               hbase默认是一级索引,一级索引指的是hbase对于rowkey方面的精确查询和范围查询都是很快的,所以,你用hbase尽量要将你的关注点设计到rowkey里面去。

              也补充下哈,hbase目前外面也有开源的二级索引,比如华为的hindex —— 来自华为的 HBase 二级索引


 上图是一个电话拨打记录存hbase的例子,说明问题如下:

        1)不是有了预分区就行了的,rowkey的设计很关键,设计不合理,仍然会导致数据倾斜;

        2)rowkey设计尽量达到数据的均匀分布

         

 

split和compact补充说明

        3.4.1 hbase的split

                    1)了解hbase的split

                    hbase默认建表时如果不指定预分区,那么这个表就默认只有一个区,默认分区大小为10G,这个区里存储数据不断增大后,分区会进行split,split是根据不同算法来分裂的,算法通过hbase.regionserver.region.split.policy参数在hbase-site.xml指定。

                    算法一IncreasingToUpperBoundRegionSplitPolicy:策略的意思是,数据表如果预分区为2个,配置的memstore flush size=128M,那么下一次分裂大小是2的平方然后乘以128MB,即2*2*128M=512MB。也即就算默认每个区不是通过参数hbase.hregion.max.filesize设置了大小10G么,但是这个对于本算法来说不起作用啦!!!!!!!!!!!!!!是不是要崩溃!!!!!!

                   算法二ConstantSizeRegionSplitPolicy:策略的意思是按照上面指定的region大小超过10G才做分裂,不超过则坚决不分裂

                2)hbase的split触发带来后果

                 阻塞该分区所在表所有读写,时间范围影响长,所以要尽量避免!!!!

                3)我们能做到的优化措施:

  1. 正式线上环境,一定要预估算你的数据保留时间,这样可以在hbase  table上设置TTL删除过期数据;
  2. 数据保留时间定下来,就是预估每天数据量,然后算出在保留时间内数据的最大值,比如1TB;
  3. 通过上面得到的最大值,设置每个预分区hbase.hregion.max.filesize文件最大值,比如50G;
  4. 最终得出你大致要建预分区20个(1TB/50GB=20),这样尽量保证最开始建的预分区就是最优,在后期也不会做分裂split动作

 

        3.4.2 hbase的compact

                   1)了解hbase的compact

                         HBase的compact是针对HRegion的HStore进行操作的。

                         compact操作分为major和minor两种,major会把HStore所有的HFile都compact为一个HFile,并同时忽略标记为delete的KeyValue(被删除的KeyValue只有在compact过程中才真正被"删除"),可以想象major会产生大量的IO操作,对HBase的读写性能产生影响。minor则只会选择数个HFile文件compact为一个HFile,minor的过程一般较快,而且IO相对较低。在日常任务时间,都会禁止mjaor操作,只在空闲的时段定时执行。

 

                   2)生产环境中首先禁用major compact,在hbase-site.xml增加如下配置:

<property>
<name>hbase.hregion.majorcompaction</name>
<value>0</value>
</property>

                  3)空闲时候用linux shell脚本进行major compact

                    

mkdir -p /home/hadoop/crontab
#vi hbase_major_compact_small.sh
cd /opt/hbase-1.2.1/bin
./hbase shell
major_compact 'small_table1'
major_compact 'small_table2'
quit
#vi hbase_major_compact_big.sh
cd /opt/hbase-1.2.1/bin
./hbase shell
major_compact 'big_table1'
major_compact 'big_table2'
quit
#编辑crontab服务文件  
crontab  -e   
#然后贴入如下内容:  
#晚上23:30执行脚本/home/hadoop/crontab/hbase_major_compact_small.sh  
30 23 * * * /home/hadoop/crontab/hbase_major_compact_small.sh   
#林晨00:30执行脚本/home/hadoop/crontab/hbase_major_compact_big.sh   
30 0 * * * /home/hadoop/crontab/hbase_major_compact_big.sh   

    这样就可以在比较空闲的时候发起major_compact动作。

 

      网上一篇比较好的文章:http://itindex.NET/detail/49632-hbase-%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98

 

 

HBASE GC补充说明

      上面hbase经过一番优化之后,读写性能都提升上去了,又会面临新的问题,在高并发写时候,频繁的创建了大量对象,这时候Java GC就会在某一时刻进行垃圾回收GC。

        垃圾回收GC没有错,我们需要关注的点时,如何避免GC造成的所有读写阻塞,当读写阻塞达到一定时间时候,会触发如下动作:

  • java的老生代被占满,触发FULL GC,导致hbase读写阻塞很长一段时间;
  • zookeeper会认为这台regionserver已经处于不可用状态,将当前regionserver从zookeeper中踢出;
  • 踢出的regionserver发现自己被zookeeper踢出,此时就主动shutdown HOOK

      为了避免上面那段情况,我们能优化的是尽早GC,解决方法参见

 我的优化是,首先调整hbase-env.sh中参数HBASE_REGIONSERVER_OPTS

 
export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -Xmx4g -Xms4g -Xmn512M -XX:SurvivorRatio=1 -XX:PermSize=128M -XX:MaxPermSize=128M -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false -XX:MaxTenuringThreshold=15 -XX:+CMSParallelRemarkEnabled -XX:+UseFastAccessorMethods -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+HeapDumpOnOutOfMemoryError -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -Xloggc:/opt/hbase-1.2.1/logs/gc-hbase-regionserver.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=1 -XX:GCLogFileSize=512M"

 然后是在hbase-site.xml中增加如下配置:

[xml] view plain copy
 
 print?
  1. <property>  
  2.   <name>hbase.hregion.memstore.mslab.enabled</name>  
  3.   <value>true</value>  
  4.   <description>  
  5.     Enables the MemStore-Local Allocation Buffer,  
  6.     a feature which works to prevent heap fragmentation under  
  7.     heavy write loads. This can reduce the frequency of stop-the-world  
  8.     GC pauses on large heaps.</description>  
  9. </property>  
  10. <property>  
  11.   <name>hbase.hregion.memstore.mslab.chunksize</name>  
  12.   <value>2097152</value>  
  13.   <description>  
  14.     The default value of hbase.hregion.memstore.mslab.chunksize is defined in file   
  15.     org.apache.hadoop.hbase.regionserver.HeapMemStoreLAB,the size is 2048 * 1024 bytes.  
  16.     </description>  
  17. </property>  
  18. <property>  
  19.   <name>hbase.hregion.memstore.mslab.max.allocation</name>  
  20.   <value>262144</value>  
  21.   <description>  
  22.     The default value of hbase.hregion.memstore.mslab.max.allocation is defined in file   
  23.     org.apache.hadoop.hbase.regionserver.HeapMemStoreLAB,the size is 256 * 1024.  
  24.     </description>  
  25. </property>  

     上面做法的目的有点类似于memcached中分配不同大小的内存块从而减少内存碎片的出现,尽量使得内存充分被使用。

posted @ 2017-06-29 11:15  XGogo  阅读(508)  评论(0编辑  收藏  举报