您的位置:首页 > 运维架构

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

2017-09-08 14:23 691 查看
本文转载自: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配置,远远不能满足需求,所以必须调大。

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

Config代码


* soft nofile 102400

* hard nofile 409600

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

Config代码


* 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,以在重启后保存该设置。

Config代码


#vi /etc/sysctl.conf

vm.swappiness = 10


3.1.5 禁用透明大页面压缩

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

Config代码


#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如下:

Config代码


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

Xml代码


<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>

Xml代码


<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里引入如下依赖项:

Xml代码


<dependency>

<groupId>org.apache.hbase</groupId>

<artifactId>hbase-client</artifactId>

<version>1.2.1</version>

</dependency>

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

Java代码


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里如下配置:

Config代码


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的最好的办法就是提前预分区,一个预分区建表语句如下:

Hbase shell代码


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,

Xml代码


<property>

<name>hbase.hregion.majorcompaction</name>

<value>0</value>

<description>禁止majorcompaction,这里虽然禁止了,但是还是得做,是通过linux定时任务在空闲时间执行</description>

</property>

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

Shell代码


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,然后配置

Xml代码


<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,来决定对于你的集群什么值是合适的。(请求队列也是会消耗内存的)。我的配置如下:

Xml代码


<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内存模型,见下图:



.每一个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文件。

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中配置信息如下:

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增加如下配置:

<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 报错gc wal.FSHLog: Error while AsyncSyncer sync, request close of hlog YouArhttp://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

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代码


<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中分配不同大小的内存块从而减少内存碎片的出现,尽量使得内存充分被使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  hadoop hbase 性能调优