`

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

阅读更多
(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

 

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

<dependency>  
    <groupId>org.apache.hbase</groupId>  
    <artifactId>hbase-client</artifactId>  
    <version>1.2.1</version>  
</dependency>

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

Configuration configuration = HBaseConfiguration.create();  
configuration.set("hbase.zookeeper.property.clientPort", "2181");  
configuration.set("hbase.client.write.buffer", "2097152");  
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");  
Connection connection = ConnectionFactory.createConnection(configuration);  
Table table = connection.getTable(TableName.valueOf("tableName"));  
try {  
  // Use the table as needed, for a single operation and a single thread  
  // construct List<Put> putLists  
  List<Put> putLists = new ArrayList<Put>();
  for(int count=0;count<100000;count++){
  	Put put = new Put(rowkey.getBytes());
  	put.addImmutable("columnFamily1".getBytes(), "columnName1".getBytes(), "columnValue1".getBytes());
  	put.addImmutable("columnFamily1".getBytes(), "columnName2".getBytes(), "columnValue2".getBytes());
  	put.addImmutable("columnFamily1".getBytes(), "columnName3".getBytes(), "columnValue3".getBytes());
        put.setDurability(Durability.SKIP_WAL);
  	putLists.add(put);
  	
  	//3177不是我杜撰的,是2*hbase.client.write.buffer/put.heapSize()计算出来的
  	if(putLists.size()>=3177-1){
  	  //达到最佳大小值了,马上提交一把
  		table.put(putLists);
  		putLists.clear();
  	}
  }
  //剩下的未提交数据,最后做一次提交
  table.put(putLists)  
} finally {  
  table.close();  
  connection.close();  
}  

 

        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,

	<property>
		<name>hbase.hregion.majorcompaction</name>
		<value>0</value>
		<description>禁止majorcompaction,这里虽然禁止了,但是还是得做,是通过linux定时任务在空闲时间执行</description>
	</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,然后配置

	<property>
		<name>hbase.hregion.max.filesize</name>
		<value>53687091200</value>
		<description>设置每个数据表中单个region存储的hfile最大值50G,只有超过此值才做split</description>
	</property>
	<property>
		<name>hbase.regionserver.region.split.policy</name>
		<value>org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy</value>
		<description>这个需要和hbase.hregion.max.filesize结合使用</description>
	</property>

 

 

hbase服务端优化补充说明

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

<property>  
        <name>hbase.regionserver.handler.count</name>  
        <value>300</value>  
        <description>Count of RPC Listener instances spun up on RegionServers.Same property is used by the Master for count of master handlers.</description>  
</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文件。
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.io.util;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HConstants;

@InterfaceAudience.Private
public class HeapMemorySizeUtil {

  public static final String MEMSTORE_SIZE_KEY = "hbase.regionserver.global.memstore.size";
  public static final String MEMSTORE_SIZE_OLD_KEY =
      "hbase.regionserver.global.memstore.upperLimit";
  public static final String MEMSTORE_SIZE_LOWER_LIMIT_KEY =
      "hbase.regionserver.global.memstore.size.lower.limit";
  public static final String MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY =
      "hbase.regionserver.global.memstore.lowerLimit";

  public static final float DEFAULT_MEMSTORE_SIZE = 0.4f;
  // Default lower water mark limit is 95% size of memstore size.
  public static final float DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT = 0.95f;

  private static final Log LOG = LogFactory.getLog(HeapMemorySizeUtil.class);
  // a constant to convert a fraction to a percentage
  private static final int CONVERT_TO_PERCENTAGE = 100;

  /**
   * Checks whether we have enough heap memory left out after portion for Memstore and Block cache.
   * We need atleast 20% of heap left out for other RS functions.
   * @param conf
   */
  public static void checkForClusterFreeMemoryLimit(Configuration conf) {
    if (conf.get(MEMSTORE_SIZE_OLD_KEY) != null) {
      LOG.warn(MEMSTORE_SIZE_OLD_KEY + " is deprecated by " + MEMSTORE_SIZE_KEY);
    }
    float globalMemstoreSize = getGlobalMemStorePercent(conf, false);
    int gml = (int)(globalMemstoreSize * CONVERT_TO_PERCENTAGE);
    float blockCacheUpperLimit = getBlockCacheHeapPercent(conf);
    int bcul = (int)(blockCacheUpperLimit * CONVERT_TO_PERCENTAGE);
    if (CONVERT_TO_PERCENTAGE - (gml + bcul)
            < (int)(CONVERT_TO_PERCENTAGE *
                    HConstants.HBASE_CLUSTER_MINIMUM_MEMORY_THRESHOLD)) {
      throw new RuntimeException("Current heap configuration for MemStore and BlockCache exceeds "
          + "the threshold required for successful cluster operation. "
          + "The combined value cannot exceed 0.8. Please check "
          + "the settings for hbase.regionserver.global.memstore.size and "
          + "hfile.block.cache.size in your configuration. "
          + "hbase.regionserver.global.memstore.size is " + globalMemstoreSize
          + " hfile.block.cache.size is " + blockCacheUpperLimit);
    }
  }

  /**
   * Retrieve global memstore configured size as percentage of total heap.
   * @param c
   * @param logInvalid
   */
  public static float getGlobalMemStorePercent(final Configuration c, final boolean logInvalid) {
    float limit = c.getFloat(MEMSTORE_SIZE_KEY,
        c.getFloat(MEMSTORE_SIZE_OLD_KEY, DEFAULT_MEMSTORE_SIZE));
    if (limit > 0.8f || limit <= 0.0f) {
      if (logInvalid) {
        LOG.warn("Setting global memstore limit to default of " + DEFAULT_MEMSTORE_SIZE
            + " because supplied value outside allowed range of (0 -> 0.8]");
      }
      limit = DEFAULT_MEMSTORE_SIZE;
    }
    return limit;
  }

  /**
   * Retrieve configured size for global memstore lower water mark as percentage of total heap.
   * @param c
   * @param globalMemStorePercent
   */
  public static float getGlobalMemStoreLowerMark(final Configuration c, float globalMemStorePercent) {
    String lowMarkPercentStr = c.get(MEMSTORE_SIZE_LOWER_LIMIT_KEY);
    if (lowMarkPercentStr != null) {
      return Float.parseFloat(lowMarkPercentStr);
    }
    String lowerWaterMarkOldValStr = c.get(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY);
    if (lowerWaterMarkOldValStr != null) {
      LOG.warn(MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " is deprecated. Instead use "
          + MEMSTORE_SIZE_LOWER_LIMIT_KEY);
      float lowerWaterMarkOldVal = Float.parseFloat(lowerWaterMarkOldValStr);
      if (lowerWaterMarkOldVal > globalMemStorePercent) {
        lowerWaterMarkOldVal = globalMemStorePercent;
        LOG.info("Setting globalMemStoreLimitLowMark == globalMemStoreLimit " + "because supplied "
            + MEMSTORE_SIZE_LOWER_LIMIT_OLD_KEY + " was > " + MEMSTORE_SIZE_OLD_KEY);
      }
      return lowerWaterMarkOldVal / globalMemStorePercent;
    }
    return DEFAULT_MEMSTORE_SIZE_LOWER_LIMIT;
  }

  /**
   * Retrieve configured size for on heap block cache as percentage of total heap.
   * @param conf
   */
  public static float getBlockCacheHeapPercent(final Configuration conf) {
    // L1 block cache is always on heap
    float l1CachePercent = conf.getFloat(HConstants.HFILE_BLOCK_CACHE_SIZE_KEY,
        HConstants.HFILE_BLOCK_CACHE_SIZE_DEFAULT);
    float l2CachePercent = getL2BlockCacheHeapPercent(conf);
    return l1CachePercent + l2CachePercent;
  }

  /**
   * @param conf
   * @return The on heap size for L2 block cache.
   */
  public static float getL2BlockCacheHeapPercent(Configuration conf) {
    float l2CachePercent = 0.0F;
    String bucketCacheIOEngineName = conf.get(HConstants.BUCKET_CACHE_IOENGINE_KEY, null);
    // L2 block cache can be on heap when IOEngine is "heap"
    if (bucketCacheIOEngineName != null && bucketCacheIOEngineName.startsWith("heap")) {
      float bucketCachePercentage = conf.getFloat(HConstants.BUCKET_CACHE_SIZE_KEY, 0F);
      MemoryUsage mu = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
      l2CachePercent = bucketCachePercentage < 1 ? bucketCachePercentage
          : (bucketCachePercentage * 1024 * 1024) / mu.getMax();
    }
    return l2CachePercent;
  }
}
       综上所述,我在hbase-site.xml中配置信息如下:

 

<property>
	<name>hfile.block.cache.size</name>
	<value>0.3</value>
</property>
<property>
	<name>hbase.regionserver.global.memstore.size.lower.limit</name>
	<value>0.5</value>
</property>
<property>
	<name>hbase.regionserver.global.memstore.size</name>
	<value>0.5</value>
</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操作分为majorminor两种,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中增加如下配置:

  <property>
    <name>hbase.hregion.memstore.mslab.enabled</name>
    <value>true</value>
    <description>
      Enables the MemStore-Local Allocation Buffer,
      a feature which works to prevent heap fragmentation under
      heavy write loads. This can reduce the frequency of stop-the-world
      GC pauses on large heaps.</description>
  </property>
  <property>
    <name>hbase.hregion.memstore.mslab.chunksize</name>
    <value>2097152</value>
    <description>
      The default value of hbase.hregion.memstore.mslab.chunksize is defined in file 
      org.apache.hadoop.hbase.regionserver.HeapMemStoreLAB,the size is 2048 * 1024 bytes.
      </description>
  </property>
  <property>
    <name>hbase.hregion.memstore.mslab.max.allocation</name>
    <value>262144</value>
    <description>
      The default value of hbase.hregion.memstore.mslab.max.allocation is defined in file 
      org.apache.hadoop.hbase.regionserver.HeapMemStoreLAB,the size is 256 * 1024.
      </description>
  </property>

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

 

  • 大小: 29 KB
  • 大小: 32.6 KB
  • 大小: 60.6 KB
  • 大小: 43 KB
  • 大小: 129.1 KB
  • 大小: 23.7 KB
  • 大小: 31 KB
  • 大小: 36.2 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics