(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 409600centos7修改/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_OPTS和HADOOP_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的能力必须提升,下面的配置你必须优化:
- hadoop的core-site.xml的io.file.buffer.size
- hadoop的core-site.xml的io.compression.codecs
- hadoop的hdfs-site.xml的dfs.namenode.handler.count
- hadoop的hdfs-site.xml的dfs.datanode.handler.count
- hadoop的hdfs-site.xml的dfs.datanode.max.transfer.threads
- 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操作,那么预分区多少呢?这里有个逐步计算方法:
- 首先你要知道你这个hbasetest数据表数据保留多久,比如保留半年,也即180天;
- 第二步,你得观察每天数据量,例如每天hbasetest的数据量会产生10GB的数据;
- 第三步,你得知道你配置的单个region文件的大小,比如hbase.hregion.max.filesize=53687091200,意思是单个region最大50GB;
- 第四步,开始计算,180天的数据量=10GB*180*HADOOP备份数3=5400GB,这些数据占用分区数=108,
- 第五步,开始建表时候你就知道你必须在设计时候就得建立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,你要理解如下:
- hbase对于rowkey的处理是把rowkey按照ASCII码字典序来处理的,意思是ASCII对应的顺序字符的二进制顺序来处理,例如0-9字符的Byte值<大写字母A-Z<小写字母a-z;
- 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,我的配置如下:
3)hbase内存配置,内存配置先要了解hbase内存模型,见下图:
- .每一个Region都有一个Memstore,Memstore默认大小为128MB,可通过hbase.hregion.memstore.flush.size更改;
- 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控制;
- 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;
- Memstore总和达到第一个临界值,会在所有memstore中选择一个最大的那个进行flushing,此时不会阻塞写;
- Memstore总和达到第二个临界值,会阻塞所有的读写,将当前所有memstore进行flushing。
- 每一个Region都有一个BlockCache,BlockCache总和默认打下为HEAP_SIZE乘以0.4,默认是通过hfile.block.cache.size设置;
- 所有的读请求,先到BlockCache中查找,基本Memstore中有的值在BlockCache中也都有,找不到再去Hfile中找。
- 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)我们能做到的优化措施:
- 正式线上环境,一定要预估算你的数据保留时间,这样可以在hbase table上设置TTL删除过期数据;
- 数据保留时间定下来,就是预估每天数据量,然后算出在保留时间内数据的最大值,比如1TB;
- 通过上面得到的最大值,设置每个预分区hbase.hregion.max.filesize文件最大值,比如50G;
- 最终得出你大致要建预分区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增加如下配置:
<name>hbase.hregion.majorcompaction</name>
<value>0</value>
</property>
3)空闲时候用linux shell脚本进行major compact
#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
这样就可以在比较空闲的时候发起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 报错gc wal.FSHLog: Error while AsyncSyncer sync, request close of hlog YouAr http://blackproof.iteye.com/blog/2188952
- 在HBase中应用MemStore-Local Allocation Buffers解决Full GC问题 http://blackproof.iteye.com/blog/2079612
- hbase gc MemStore-Local Allocation Buffer http://blackproof.iteye.com/blog/2079617
我的优化是,首先调整hbase-env.sh中参数HBASE_REGIONSERVER_OPTS
然后是在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中分配不同大小的内存块从而减少内存碎片的出现,尽量使得内存充分被使用。