快乐学习
前程无忧、中华英才非你莫属!

Hbase故障排查与性能调优

一般准则

总是先从主服务器的日志开始。通常情况下,他总是一行一行的重复信息。如果不是这样,说明有问题,可以Google或是用search-hadoop.com来搜索遇到的异常。

错误很少仅仅单独出现在HBase中,通常是某一个地方出了问题,引起各处大量异常和调用栈跟踪信息。遇到这样的错误,最好的办法是往上查日志,找到最初的异常。例如区域服务器会在退出的时候打印一些度量信息。Grep这个转储 应该可以找到最初的异常信息。

区域服务器的自杀是很“正常”的。当一些事情发生错误的,他们就会自杀。如果ulimit和xcievers没有修改,HDFS将无法运转正常,在HBase看来,HDFS死掉了。假想一下,你的MySQL突然无法访问它的文件系统,他会怎么做。同样的事情会发生在HBase和HDFS上。还有一个造成区域服务器切腹自杀的常见的原因是,他们执行了一个长时间的GC操作,这个时间超过了ZooKeeper的会话时长。

Logs

重要日志的位置( <user>是启动服务的用户,<hostname> 是机器的名字)

  • NameNode: $HADOOP_HOME/logs/hadoop-<user>-namenode-<hostname>.log

  • DataNode: $HADOOP_HOME/logs/hadoop-<user>-datanode-<hostname>.log

  • JobTracker: $HADOOP_HOME/logs/hadoop-<user>-jobtracker-<hostname>.log

  • TaskTracker: $HADOOP_HOME/logs/hadoop-<user>-jobtracker-<hostname>.log

  • HMaster: $HBASE_HOME/logs/hbase-<user>-master-<hostname>.log

  • RegionServer: $HBASE_HOME/logs/hbase-<user>-regionserver-<hostname>.log

  • ZooKeeper: TODO

日志级别

启用 RPC级别日志

Enabling the RPC-level logging on a RegionServer can often given insight on timings at the server. Once enabled, the amount of log spewed is voluminous. It is not recommended that you leave this logging on for more than short bursts of time. To enable RPC-level logging, browse to the RegionServer UI and click on Log Level. Set the log level to DEBUG for the package org.apache.hadoop.ipc (Thats right, for hadoop.ipc, NOT, hbase.ipc). Then tail the RegionServers log. Analyze.

To disable, set the logging level back to INFO level.

JVM 垃圾收集日志

HBase is memory intensive, and using the default GC you can see long pauses in all threads including the Juliet Pause aka "GC of Death". To help debug this or confirm this is happening GC logging can be turned on in the Java virtual machine.

To enable, in hbase-env.sh add:

export HBASE_OPTS="-XX:+UseConcMarkSweepGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/home/hadoop/hbase/logs/gc-hbase.log"

Adjust the log directory to wherever you log. Note: The GC log does NOT roll automatically, so you&apos;ll have to keep an eye on it so it doesn&apos;t fill up the disk.

At this point you should see logs like so:

64898.952: [GC [1 CMS-initial-mark: 2811538K(3055704K)] 2812179K(3061272K), 0.0007360 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
64898.953: [CMS-concurrent-mark-start]
64898.971: [GC 64898.971: [ParNew: 5567K->576K(5568K), 0.0101110 secs] 2817105K->2812715K(3061272K), 0.0102200 secs] [Times: user=0.07 sys=0.00, real=0.01 secs]

In this section, the first line indicates a 0.0007360 second pause for the CMS to initially mark. This pauses the entire VM, all threads for that period of time.

The third line indicates a "minor GC", which pauses the VM for 0.0101110 seconds - aka 10 milliseconds. It has reduced the "ParNew" from about 5.5m to 576k. Later on in this cycle we see:

64901.445: [CMS-concurrent-mark: 1.542/2.492 secs] [Times: user=10.49 sys=0.33, real=2.49 secs] 
64901.445: [CMS-concurrent-preclean-start]
64901.453: [GC 64901.453: [ParNew: 5505K->573K(5568K), 0.0062440 secs] 2868746K->2864292K(3061272K), 0.0063360 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
64901.476: [GC 64901.476: [ParNew: 5563K->575K(5568K), 0.0072510 secs] 2869283K->2864837K(3061272K), 0.0073320 secs] [Times: user=0.05 sys=0.01, real=0.01 secs] 
64901.500: [GC 64901.500: [ParNew: 5517K->573K(5568K), 0.0120390 secs] 2869780K->2865267K(3061272K), 0.0121150 secs] [Times: user=0.09 sys=0.00, real=0.01 secs] 
64901.529: [GC 64901.529: [ParNew: 5507K->569K(5568K), 0.0086240 secs] 2870200K->2865742K(3061272K), 0.0087180 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
64901.554: [GC 64901.555: [ParNew: 5516K->575K(5568K), 0.0107130 secs] 2870689K->2866291K(3061272K), 0.0107820 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
64901.578: [CMS-concurrent-preclean: 0.070/0.133 secs] [Times: user=0.48 sys=0.01, real=0.14 secs] 
64901.578: [CMS-concurrent-abortable-preclean-start]
64901.584: [GC 64901.584: [ParNew: 5504K->571K(5568K), 0.0087270 secs] 2871220K->2866830K(3061272K), 0.0088220 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
64901.609: [GC 64901.609: [ParNew: 5512K->569K(5568K), 0.0063370 secs] 2871771K->2867322K(3061272K), 0.0064230 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
64901.615: [CMS-concurrent-abortable-preclean: 0.007/0.037 secs] [Times: user=0.13 sys=0.00, real=0.03 secs] 
64901.616: [GC[YG occupancy: 645 K (5568 K)]64901.616: [Rescan (parallel) , 0.0020210 secs]64901.618: [weak refs processing, 0.0027950 secs] [1 CMS-remark: 2866753K(3055704K)] 2867399K(3061272K), 0.0049380 secs] [Times: user=0.00 sys=0.01, real=0.01 secs] 
64901.621: [CMS-concurrent-sweep-start]

The first line indicates that the CMS concurrent mark (finding garbage) has taken 2.4 seconds. But this is a concurrent 2.4 seconds, Java has not been paused at any point in time.

There are a few more minor GCs, then there is a pause at the 2nd last line:

64901.616: [GC[YG occupancy: 645 K (5568 K)]64901.616: [Rescan (parallel) , 0.0020210 secs]64901.618: [weak refs processing, 0.0027950 secs] [1 CMS-remark: 2866753K(3055704K)] 2867399K(3061272K), 0.0049380 secs] [Times: user=0.00 sys=0.01, real=0.01 secs]

The pause here is 0.0049380 seconds (aka 4.9 milliseconds) to &apos;remark&apos; the heap.

At this point the sweep starts, and you can watch the heap size go down:

64901.637: [GC 64901.637: [ParNew: 5501K->569K(5568K), 0.0097350 secs] 2871958K->2867441K(3061272K), 0.0098370 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
...  lines removed ...
64904.936: [GC 64904.936: [ParNew: 5532K->568K(5568K), 0.0070720 secs] 1365024K->1360689K(3061272K), 0.0071930 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
64904.953: [CMS-concurrent-sweep: 2.030/3.332 secs] [Times: user=9.57 sys=0.26, real=3.33 secs]

At this point, the CMS sweep took 3.332 seconds, and heap went from about ~ 2.8 GB to 1.3 GB (approximate).

The key points here is to keep all these pauses low. CMS pauses are always low, but if your ParNew starts growing, you can see minor GC pauses approach 100ms, exceed 100ms and hit as high at 400ms.

This can be due to the size of the ParNew, which should be relatively small. If your ParNew is very large after running HBase for a while, in one example a ParNew was about 150MB, then you might have to constrain the size of ParNew (The larger it is, the longer the collections take but if its too small, objects are promoted to old gen too quickly). In the below we constrain new gen size to 64m.

Add this to HBASE_OPTS:

export HBASE_OPTS="-XX:NewSize=64m -XX:MaxNewSize=64m <cms options from above> <gc logging options from above>"

jstack

jstack 是一个最重要(除了看Log)的java工具,可以看到具体的Java进程的在做什么。可以先用Jps看到进程的Id,然后就可以用jstack。他会按线程的创建顺序显示线程的列表,还有这个线程在做什么。

ScannerTimeoutException 或 UnknownScannerException

当从客户端到RegionServer的RPC请求超时。例如如果Scan.setCacheing的值设置为500,RPC请求就要去获取500行的数据,每500次.next()操作获取一次。因为数据是以大块的形式传到客户端的,就可能造成超时。将这个 serCacheing的值调小是一个解决办法,但是这个值要是设的太小就会影响性能。

安全客户端不能连接

([由 GSSException 引起: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)]) There can be several causes that produce this symptom.

First, check that you have a valid Kerberos ticket. One is required in order to set up communication with a secure Apache HBase cluster. Examine the ticket currently in the credential cache, if any, by running the klist command line utility. If no ticket is listed, you must obtain a ticket by running the kinit command with either a keytab specified, or by interactively entering a password for the desired principal.

Then, consult the Java Security Guide troubleshooting section. The most common problem addressed there is resolved by setting javax.security.auth.useSubjectCredsOnly system property value to false.

Because of a change in the format in which MIT Kerberos writes its credentials cache, there is a bug in the Oracle JDK 6 Update 26 and earlier that causes Java to be unable to read the Kerberos credentials cache created by versions of MIT Kerberos 1.8.1 or higher. If you have this problematic combination of components in your environment, to work around this problem, first log in with kinit and then immediately refresh the credential cache with kinit -R. The refresh will rewrite the credential cache without the problematic formatting.

Finally, depending on your Kerberos configuration, you may need to install the Java Cryptography Extension, or JCE. Insure the JCE jars are on the classpath on both server and client systems.

You may also need to download the unlimited strength JCE policy files. Uncompress and extract the downloaded file, and install the policy jars into <java-home>/lib/security.

HBase hbck

1.重新修复hbase meta表(根据hdfs上的regioninfo文件,生成meta表)
hbase hbck -fixMeta

2.重新将hbase meta表分给regionserver(根据meta表,将meta表上的region分给regionservere)
hbase hbck -fixAssignments

当出现漏洞

  • hbase hbck -fixHdfsHoles (新建一个region文件夹)

  • hbase hbck -fixMeta (根据regioninfo生成meta表)

  • hbase hbck -fixAssignments (分配region到regionserver上)

region重复问题

查看meta中的region

scan &apos;hbase:meta&apos; , {LIMIT=>10,FILTER=>"PrefixFilter(&apos;INDEX_11&apos;)"}

在数据迁移的时候碰到两个重复的region
b0c8f08ffd7a96219f748ef14d7ad4f8,73ab00eaa7bab7bc83f440549b9749a3

删除两个重复的region

delete &apos;hbase:meta&apos;,&apos;INDEX_11,4380_2431,1429757926776.b0c8f08ffd7a96219f748ef14d7ad4f8.&apos;,&apos;info:regioninfo&apos;  

delete &apos;hbase:meta&apos;,&apos;INDEX_11,5479_0041431700000000040100004815E9,1429757926776.73ab00eaa7bab7bc83f440549b9749a3.&apos;,&apos;info:regioninfo&apos;

删除两个重复的hdfs

/hbase/data/default/INDEX_11/b0c8f08ffd7a96219f748ef14d7ad4f8
/hbase/data/default/INDEX_11/73ab00eaa7bab7bc83f440549b9749a3

对应的重启regionserver(只是为了刷新hmaster上汇报的RIS的状态)

肯定会丢数据,把没有上线的重复region上的数据丢失

新版本的 hbck

(1)缺失hbase.version文件
加上选项 -fixVersionFile 解决
(2)如果一个region即不在META表中,又不在hdfs上面,但是在regionserver的online region集合中
加上选项 -fixAssignments 解决
(3)如果一个region在META表中,并且在regionserver的online region集合中,但是在hdfs上面没有
加上选项 -fixAssignments -fixMeta 解决,( -fixAssignments告诉regionserver close region),( -fixMeta删除META表中region的记录 (4)如果一个region在META表中没有记录,没有被regionserver服务,但是在hdfs上面有
加上选项 -fixMeta -fixAssignments 解决,( -fixAssignments 用于assign region),( -fixMeta用于在META表中添加region的记录)
(5)如果一个region在META表中没有记录,在hdfs上面有,被regionserver服务了
加上选项 -fixMeta 解决,在META表中添加这个region的记录,先undeploy region,后assign (6)如果一个region在META表中有记录,但是在hdfs上面没有,并且没有被regionserver服务
加上选项 -fixMeta 解决,删除META表中的记录
(7)如果一个region在META表中有记录,在hdfs上面也有,table不是disabled的,但是这个region没有被服务
加上选项 -fixAssignments 解决,assign这个region
(8)如果一个region在META表中有记录,在hdfs上面也有,table是disabled的,但是这个region被某个regionserver服务了
加上选项 -fixAssignments 解决,undeploy这个region
(9)如果一个region在META表中有记录,在hdfs上面也有,table不是disabled的,但是这个region被多个regionserver服务了 加上选项 -fixAssignments 解决,通知所有regionserver close region,然后assign region

(10)如果一个region在META表中,在hdfs上面也有,也应该被服务,但是META表中记录的regionserver和实际所在的regionserver不相符
加上选项 -fixAssignments 解决
(11)region holes
需要加上 -fixHdfsHoles ,创建一个新的空region,填补空洞,但是不assign 这个 region,也不在META表中添加这个region的相关信息
(12)region在hdfs上面没有.regioninfo文件
-fixHdfsOrphans 解决
(13)region overlaps
需要加上 -fixHdfsOverlaps

说明:
(1)修复region holes时,-fixHdfsHoles 选项只是创建了一个新的空region,填补上了这个区间,还需要加上-fixAssignments -fixMeta 来解决问题,( -fixAssignments 用于assign region),( -fixMeta用于在META表中添加region的记录),所以有了组合拳 -repairHoles 修复region holes,相当于-fixAssignments -fixMeta -fixHdfsHoles -fixHdfsOrphans
(2) -fixAssignments,用于修复region没有assign、不应该assign、assign了多次的问题

(3)-fixMeta,如果hdfs上面没有,那么从META表中删除相应的记录,如果hdfs上面有,在META表中添加上相应的记录信息
(4)-repair 打开所有的修复选项,相当于-fixAssignments -fixMeta -fixHdfsHoles -fixHdfsOrphans -fixHdfsOverlaps -fixVersionFile -sidelineBigOverlaps

新版本的hbck从(1)hdfs目录(2)META(3)RegionServer这三处获得region的Table和Region的相关信息,根据这些信息判断并repair

二、错误大全

1、出现版本不一致错误如果启动时出现版本不一致的错误,如下所示:Server IPC version 10 cannot communicate with client version.........为了兼容Hadoop-2.2.0,需要将hbase的lib包中的内容lib包中的hadoop-common-2.1.0-beta.jar替换成hadoop-2.2.0/share/hadoop/common目录下的hadoop-common-2.2.0.jar。2、HBase集群启动以后,执行相关操作时抛出异常如果HBase集群正常启动,但是在想要创建一个table的时候,出现如下异常,如下所示:ERROR: org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException: org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException: Timed out (10000ms)          at org.apache.hadoop.hbase.catalog.CatalogTracker.waitForMeta(CatalogTracker.java:334)          at org.apache.hadoop.hbase.master.HMaster.createTable(HMaster.java:769)          at org.apache.hadoop.hbase.master.HMaster.createTable(HMaster.java:743)          at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)          at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)          at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)          at java.lang.reflect.Method.invoke(Method.java:597)          at org.apache.hadoop.hbase.ipc.HBaseRPC$Server.call(HBaseRPC.java:570)          at org.apache.hadoop.hbase.ipc.HBaseServer$Handler.run(HBaseServer.java:1039)  解决方法就是,修改/etc/hosts文件,修改内容以master为例,如下所示:#127.0.0.1       localhost  192.168.1.108   master  192.168.1.110   node  192.168.0.111   slave  # The following lines are desirable for IPv6 capable hosts  #::1     ip6-localhost ip6-loopback  #fe00::0 ip6-localnet  #ff00::0 ip6-mcastprefix  #ff02::1 ip6-allnodes  

#ff02::2 ip6-allrouters  

3、Hbase异常:Java.io.IOException: Connection reset by peerorg.apache.Hadoop.ipc.HBaseServer: IPC Server listener on 60000: readAndProcess threw exception java.io.IOException: Connection reset by peer. Count of bytes read: 0
java.io.IOException: Connection reset by peer
    at sun.nio.ch.FileDispatcher.read0(Native Method)
    at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:21)
    at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:198)
    at sun.nio.ch.IOUtil.read(IOUtil.java:171)
    at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:243)
    at org.apache.hadoop.hbase.ipc.HBaseServer.channelIO(HBaseServer.java:1389)
    at org.apache.hadoop.hbase.ipc.HBaseServer.channelRead(HBaseServer.java:1359)
    at org.apache.hadoop.hbase.ipc.HBaseServer$Connection.readAndProcess(HBaseServer.java:940)
    at org.apache.hadoop.hbase.ipc.HBaseServer$Listener.doRead(HBaseServer.java:522)
    at org.apache.hadoop.hbase.ipc.HBaseServer$Listener$Reader.run(HBaseServer.java:316)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:662)
hbase客户端每次和regionserver交互的时候,都会在服务器端生成一个租约(Lease),租约的有效期由参数hbase.regionserver.lease.period确定。
当客户端去regionserver取数据的时候,而此时hbase中存得数据量很大并且很多region时,客户端请求的region不在内存中,或是没有被cache命中,就需要从磁盘中加载,如果这时候加载需要的时间超过hbase.regionserver.lease.period所配置的时间,并且客户端没有和 regionserver报告其还活着,那么regionserver就会认为本次租约已经过期,并从LeaseQueue从删除掉本次租约,当regionserver加载完成后,拿已经被删除的租约再去取数据的时候,就会出现如上的错误现象。
解决的办法:
1、适当的增大 hbase.regionserver.lease.period参数的值,默认是1分钟
2、增大regionserver的cache大小

4、HBase开启thrift异常: java.lang.ClassNotFoundException: org.apache.hadoop.util.PlatformName

Exception in thread "Thread-3" java.lang.NoClassDefFoundError: org/apache/hadoop/util/PlatformName
        at org.apache.hadoop.security.UserGroupInformation.getOSLoginModuleName(UserGroupInformation.java:303)
        at org.apache.hadoop.security.UserGroupInformation.(UserGroupInformation.java:348)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.hadoop.hbase.util.Methods.call(Methods.java:39)
        at org.apache.hadoop.hbase.security.User.call(User.java:414)
        at org.apache.hadoop.hbase.security.User.callStatic(User.java:404)
        at org.apache.hadoop.hbase.security.User.access$200(User.java:48)
        at org.apache.hadoop.hbase.security.User$SecureHadoopUser.(User.java:221)
        at org.apache.hadoop.hbase.security.User$SecureHadoopUser.(User.java:216)
        at org.apache.hadoop.hbase.security.User.getCurrent(User.java:139)
        at org.apache.hadoop.hbase.client.HConnectionKey.(HConnectionKey.java:67)
        at org.apache.hadoop.hbase.client.HConnectionManager.getConnection(HConnectionManager.java:240)
        at org.apache.hadoop.hbase.client.HTable.(HTable.java:187)
        at org.apache.hadoop.hbase.client.HTable.(HTable.java:149)
        at com.yahoo.ycsb.db.HBaseClient.getHTable(HBaseClient.java:118)
        at com.yahoo.ycsb.db.HBaseClient.update(HBaseClient.java:303)
        at com.yahoo.ycsb.db.HBaseClient.insert(HBaseClient.java:358)
        at com.yahoo.ycsb.DBWrapper.insert(DBWrapper.java:148)
        at com.yahoo.ycsb.workloads.CoreWorkload.doInsert(CoreWorkload.java:461)
        at com.yahoo.ycsb.ClientThread.run(Client.java:269)
Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.util.PlatformName
        at java.NET.URLClassLoader$1.run(URLClassLoader.java:202)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.Net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)

解决办法:使用hadoop-auth-2.2.0.jar替换hbase安装目录下的hadoop-auth-2.1.0-beta.jar

5、创建HMaster对象时报告60000 : Address already in use

创建HMaster对象时报告Problem binding to hbase1/192.168.0.1:60000 : Address already in use,这是因为有另外一个程序占用了默认的60000端口,这时,需要

[java] view plain copy print?派生到我的代码片

  1. Configuration configuration = new Configuration();  

  2. configuration.set(HConstants.ZOOKEEPER_CLIENT_PORT, "2181"));  

  3. configuration.set(HConstants.ZOOKEEPER_QUORUM, "192.168.0.1,192.168.0.2,192.168.0.3");  

  4. configuration.set(HConstants.MASTER_PORT, "60001");  

  5.   

  6. conf = HBaseConfiguration.create(configuration);  

  7. master = new HMaster(conf);  

将默认的60000端口重新设置成新的端口,比如60001。

6、处理XceiverCount错误

解决:在hdfs.site.xml 中添加 属性name:dfs.datanode.max.xcivers   value=4096

          将hdfs-site.xml文件同步到整个集群中  #>for slave in &apos;cat $HADOOP_HOME/conf/slaves&apos; do rsync -avz $HADOOP_HOME/conf/ $slave:$HADOOP_HOME/conf/ done

           

7、打开文件过多错误:Too many open files

原因及修改方法:由于 Linux系统最大可打开文件数一般默认的参数值是1024,通过 ulimit -n 65535 可即时修改,但重启后就无效了。或者有如下修改方式:有如下三种修改方式:1.在/etc/rc.local 中增加一行 ulimit -SHn 655352.在/etc/profile 中增加一行 ulimit -SHn 655353.在/etc/security/limits.conf最后增加如下两行记录* soft nofile 65535

* hard nofile 65535

4、 vi /etc/pam.d/login

session required pam_limits.so

5、重启集群。

7、处理“无法创建新本地线程”错误:

7、处理Hbase忽略了HDFS客户端配置问题:

   解决 :1、在Hbase的配置目录下添加一个指向HDFS的配置文件(hdfs-site.xml)的符号链接

              $>ln -s $HADOOP_HOME/conf/hdfs-site.xml $HBASE_HOME/conf/hdfs-site.xml

             2、将此改动同步到整个集群当中。

              >for slave in &apos;cat $HBASE_HOME/conf/regionservers&apos; do rsync -avz $HBASE_HOME/conf/ $slave:$HBASE_HOME/conf done

             3、重启启动hbase。

 8、处理Zookeeper客户端链接错误:Connection reset by peer

    

      解决:在每一个zk的节点上 修改 vi $ZOOKEEPER_HOME/conf/zoo.cfg

                                                        maxClientCnxns=60

9、处理ZooKeeper 会话过期错误:SessionExpiredException:

      (如果报的是它,你基本完犊子了,主服务器就会关闭。)   

      

        

 

三、性能调优        

       这块知识太多,足够写一本书了,只围绕HBASE来调整                         

官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果。所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正。

配置优化

zookeeper.session.timeout
默认值
:3分钟(180000ms)说明:RegionServer与Zookeeper间的连接超时时间。当超时时间到后,ReigonServer会被Zookeeper从RS集群清单中移除,HMaster收到移除通知后,会对这台server负责的regions重新balance,让其他存活的RegionServer接管.调优
这个timeout决定了RegionServer是否能够及时的failover。设置成1分钟或更低,可以减少因等待超时而被延长的failover时间。
不过需要注意的是,对于一些Online应用,RegionServer从宕机到恢复时间本身就很短的(网络闪断,crash等故障,运维可快速介入),如果调低timeout时间,反而会得不偿失。因为当ReigonServer被正式从RS集群中移除时,HMaster就开始做balance了(让其他RS根据故障机器记录的WAL日志进行恢复)。当故障的RS在人工介入恢复后,这个balance动作是毫无意义的,反而会使负载不均匀,给RS带来更多负担。特别是那些固定分配regions的场景。

Hbase.regionserver.handler.count
默认值
:10说明:RegionServer的请求处理IO线程数。调优
这个参数的调优与内存息息相关。
较少的IO线程,适用于处理单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景。
较多的IO线程,适用于单次请求内存消耗低,TPS要求非常高的场景。设置该值的时候,以监控内存为主要参考。
这里需要注意的是如果server的region数量很少,大量的请求都落在一个region上,因快速充满memstore触发flush导致的读写锁会影响全局TPS,不是IO线程数越高越好。
压测时,开启Enabling RPC-level logging,可以同时监控每次请求的内存消耗和GC的状况,最后通过多次压测结果来合理调节IO线程数。
这里是一个案例?Hadoop and HBase Optimization for Read Intensive Search Applications,作者在SSD的机器上设置IO线程数为100,仅供参考。

hbase.hregion.max.filesize
默认值
:256M说明:在当前ReigonServer上单个Reigon的最大存储空间,单个Region超过该值时,这个Region会被自动split成更小的region。调优
小region对split和compaction友好,因为拆分region或compact小region里的storefile速度很快,内存占用低。缺点是split和compaction会很频繁。
特别是数量较多的小region不停地split, compaction,会导致集群响应时间波动很大,region数量太多不仅给管理上带来麻烦,甚至会引发一些Hbase的bug。
一般512以下的都算小region。

大region,则不太适合经常split和compaction,因为做一次compact和split会产生较长时间的停顿,对应用的读写性能冲击非常大。此外,大region意味着较大的storefile,compaction时对内存也是一个挑战。
当然,大region也有其用武之地。如果你的应用场景中,某个时间点的访问量较低,那么在此时做compact和split,既能顺利完成split和compaction,又能保证绝大多数时间平稳的读写性能。

既然split和compaction如此影响性能,有没有办法去掉?
compaction是无法避免的,split倒是可以从自动调整为手动。
只要通过将这个参数值调大到某个很难达到的值,比如100G,就可以间接禁用自动split(RegionServer不会对未到达100G的region做split)。
再配合RegionSplitter这个工具,在需要split时,手动split。
手动split在灵活性和稳定性上比起自动split要高很多,相反,管理成本增加不多,比较推荐online实时系统使用。

内存方面,小region在设置memstore的大小值上比较灵活,大region则过大过小都不行,过大会导致flush时app的IO wait增高,过小则因store file过多影响读性能。

hbase.regionserver.global.memstore.upperLimit/lowerLimit

默认值:0.4/0.35upperlimit说明:hbase.hregion.memstore.flush.size 这个参数的作用是当单个Region内所有的memstore大小总和超过指定值时,flush该region的所有memstore。RegionServer的flush是通过将请求添加一个队列,模拟生产消费模式来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。
这个参数的作用是防止内存占用过大,当ReigonServer内所有region的memstores所占用内存总和达到heap的40%时,HBase会强制block所有的更新并flush这些region以释放所有memstore占用的内存。lowerLimit说明: 同upperLimit,只不过lowerLimit在所有region的memstores所占用内存达到Heap的35%时,不flush所有的memstore。它会找一个memstore内存占用最大的region,做个别flush,此时写更新还是会被block。lowerLimit算是一个在所有region强制flush导致性能降低前的补救措施。在日志中,表现为 “** Flush thread woke up with memory above low water.”调优:这是一个Heap内存保护参数,默认值已经能适用大多数场景。
参数调整会影响读写,如果写的压力大导致经常超过这个阀值,则调小读缓存hfile.block.cache.size增大该阀值,或者Heap余量较多时,不修改读缓存大小。
如果在高压情况下,也没超过这个阀值,那么建议你适当调小这个阀值再做压测,确保触发次数不要太多,然后还有较多Heap余量的时候,调大hfile.block.cache.size提高读性能。
还有一种可能性是?hbase.hregion.memstore.flush.size保持不变,但RS维护了过多的region,要知道 region数量直接影响占用内存的大小。

hfile.block.cache.size

默认值:0.2说明:storefile的读缓存占用Heap的大小百分比,0.2表示20%。该值直接影响数据读的性能。调优:当然是越大越好,如果写比读少很多,开到0.4-0.5也没问题。如果读写较均衡,0.3左右。如果写比读多,果断默认吧。设置这个值的时候,你同时要参考?hbase.regionserver.global.memstore.upperLimit?,该值是memstore占heap的最大百分比,两个参数一个影响读,一个影响写。如果两值加起来超过80-90%,会有OOM的风险,谨慎设置。

hbase.hstore.blockingStoreFiles

默认值:7说明:在flush时,当一个region中的Store(Coulmn Family)内有超过7个storefile时,则block所有的写请求进行compaction,以减少storefile数量。调优:block写请求会严重影响当前regionServer的响应时间,但过多的storefile也会影响读性能。从实际应用来看,为了获取较平滑的响应时间,可将值设为无限大。如果能容忍响应时间出现较大的波峰波谷,那么默认或根据自身场景调整即可。

hbase.hregion.memstore.block.multiplier

默认值:2说明:当一个region里的memstore占用内存大小超过hbase.hregion.memstore.flush.size两倍的大小时,block该region的所有请求,进行flush,释放内存。
虽然我们设置了region所占用的memstores总内存大小,比如64M,但想象一下,在最后63.9M的时候,我Put了一个200M的数据,此时memstore的大小会瞬间暴涨到超过预期的hbase.hregion.memstore.flush.size的几倍。这个参数的作用是当memstore的大小增至超过hbase.hregion.memstore.flush.size 2倍时,block所有请求,遏制风险进一步扩大。调优: 这个参数的默认值还是比较靠谱的。如果你预估你的正常应用场景(不包括异常)不会出现突发写或写的量可控,那么保持默认值即可。如果正常情况下,你的写请求量就会经常暴长到正常的几倍,那么你应该调大这个倍数并调整其他参数值,比如hfile.block.cache.size和hbase.regionserver.global.memstore.upperLimit/lowerLimit,以预留更多内存,防止HBase server OOM。

hbase.hregion.memstore.mslab.enabled

默认值:true说明:减少因内存碎片导致的Full GC,提高整体性能。调优:详见 http://kenwublog.com/avoid-full-gc-in-hbase-using-arena-allocation

其他

启用LZO压缩LZO对比Hbase默认的GZip,前者性能较高,后者压缩比较高,具体参见?Using LZO Compression 。对于想提高HBase读写性能的开发者,采用LZO是比较好的选择。对于非常在乎存储空间的开发者,则建议保持默认。

不要在一张表里定义太多的Column Family

Hbase目前不能良好的处理超过包含2-3个CF的表。因为某个CF在flush发生时,它邻近的CF也会因关联效应被触发flush,最终导致系统产生更多IO。

批量导入

在批量导入数据到Hbase前,你可以通过预先创建regions,来平衡数据的负载。详见?Table Creation: Pre-Creating Regions

避免CMS concurrent mode failure

HBase使用CMS GC。默认触发GC的时机是当年老代内存达到90%的时候,这个百分比由 -XX:CMSInitiatingOccupancyFraction=N 这个参数来设置。concurrent mode failed发生在这样一个场景:
当年老代内存达到90%的时候,CMS开始进行并发垃圾收集,于此同时,新生代还在迅速不断地晋升对象到年老代。当年老代CMS还未完成并发标记时,年老代满了,悲剧就发生了。CMS因为没内存可用不得不暂停mark,并触发一次stop the world(挂起所有jvm线程),然后采用单线程拷贝方式清理所有垃圾对象。这个过程会非常漫长。为了避免出现concurrent mode failed,建议让GC在未到90%时,就触发。

通过设置?-XX:CMSInitiatingOccupancyFraction=N

这个百分比, 可以简单的这么计算。如果你的?hfile.block.cache.size 和?hbase.regionserver.global.memstore.upperLimit 加起来有60%(默认),那么你可以设置 70-80,一般高10%左右差不多。

Hbase客户端优化

AutoFlush

HTable的setAutoFlush设为false,可以支持客户端批量更新。即当Put填满客户端flush缓存时,才发送到服务端。
默认是true。

Scan Caching

scanner一次缓存多少数据来scan(从服务端一次抓多少数据回来scan)。
默认值是 1,一次只取一条。

Scan Attribute Selection

scan时建议指定需要的Column Family,减少通信量,否则scan操作默认会返回整个row的所有数据(所有Coulmn Family)。

Close ResultScanners

通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。

Optimal Loading of Row Keys

当你scan一张表的时候,返回结果只需要row key(不需要CF, qualifier,values,timestaps)时,你可以在scan实例中添加一个filterList,并设置 MUST_PASS_ALL操作,filterList中add?FirstKeyOnlyFilterKeyOnlyFilter。这样可以减少网络通信量。

Turn off WAL on Puts

当Put某些非重要数据时,你可以设置writeToWAL(false),来进一步提高写性能。writeToWAL(false)会在Put时放弃写WAL log。风险是,当RegionServer宕机时,可能你刚才Put的那些数据会丢失,且无法恢复。

启用Bloom Filter

Bloom Filter通过空间换时间,提高读操作性能。

最后,感谢嬴北望同学对”hbase.hregion.memstore.flush.size”和“hbase.hstore.blockingStoreFiles”错误观点的修正。

本文转自:http://kenwublog.com/hbase-performance-tuning

 

 

提升hbase性能的几个地方(转载)

1、使用bloomfilter和mapfile_index_interval

Bloomfilter(开启/未开启=1/0) mapfile_index_interval Exists(0-10000)/ms Get(10001 - 20000)/ms
0 128 22460 23715
0 0 11897 11416
0 64 13692 14034
1 128 3275 3686
1 64 2961 3010
1 0 3339 3498
       
测试环境为:单机,规模为10万条数据。随机在10000条数据中有99条存在的情况下。      
结论:开启bloomfilter比没开启要快3、4倍。而适当的减少mapfile_index_interval可以提升性能      

 

注意:在1.9.3版本的hbase中,bloomfilter是不支持的,存在一个bug,可以通过如下的修改加以改正:
    (1)、在方法org.apache.Hadoop.hbase.regionserver.HStore.createReaders()中,找到如下行
    BloomFilterMapFile.Reader reader = file.getReader(fs, false, false);
    将其改成
    BloomFilterMapFile.Reader reader = file.getReader(fs, this.family.isBloomfilter(), false);
    (2)、在方法org.apache.hadoop.hbase.HColumnDescriptor.toString()中,找到如下的代码行
      if (key != null && key.toUpperCase().equals(BLOOMFILTER)) {
        // Don&apos;t emit bloomfilter.  Its not working.
        continue;
      }
    将其注释掉
2、hbase对于内存有特别的嗜好,在硬件允许的情况下配足够多的内存给它。
    通过修改hbase-env.sh中的
    export HBASE_HEAPSIZE=3000 #这里默认为1000m
3、修改Java虚拟机属性
    (1)、在环境允许的情况下换64位的虚拟机
    (2)、替换掉默认的垃圾回收器,因为默认的垃圾回收器在多线程环境下会有更多的wait等待
    export HBASE_OPTS="-server -XX:NewSize=6m -XX:MaxNewSize=6m -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode"
4、增大RPC数量
    通过修改hbase-site.xml中的    
    hbase.regionserver.handler.count属性,可以适当的放大。默认值为10有点小
5、做程序开发是注意的地方
    (1)、需要判断所求的数据行是否存在时,尽量不要用HTable.exists(final byte [] row) 而用HTable.exists(final byte [] row, final byte[] column)等带列族的方法替代。
    (2)、不要使用HTable.get(final byte [] row, final byte [] column) == null来判断所求的数据存在,而是用HTable.exists(final byte [] row, final byte[] column)替代
    (3)、HTable.close()方法少用.因为我遇到过一些很令人费解的错误
6、记住HBase是基于列模式的存储,如果一个列族能搞定就不要把它分开成两个,关系数据库的那套在这里很不实用.分成多个列来存储会浪费更多的空间,除非你认为现在的硬盘和白菜一个价。

7、如果数据量没有达到TB级别或者没有上亿条记录,很难发挥HBase的优势,建议换关系数据库或别的存储技术。

转载自:http://blog.csdn.net/rzhzhz/article/details/7481674

 

HBase性能优化完全版

近期在处理Hbase的业务方面常常遇到各种瓶颈,一天大概一亿条数据,在HBase性能调优方面进行相关配置和调优后取得了一定的成效,于是,特此在这里总结了一下关于HBase全面的配置,主要参考我的另外两篇文章:

(1)http://blog.csdn.net/u014297175/article/details/47975875

(2)http://blog.csdn.net/u014297175/article/details/47976909

在其基础上总结出来的性能优化方法。

1.垃圾回收优化

Java本身提供了垃圾回收机制,依靠JRE对程序行为的各种假设进行垃圾回收,但是HBase支持海量数据持续入库,非常占用内存,因此繁重的负载会迫使内存分配策略无法安全地依赖于JRE的判断:需要调整JRE的参数来调整垃圾回收策略。有关java内存回收机制的问题具体请参考:http://my.oschina.net/sunnywu/blog/332870

(1)HBASE_OPTS或者HBASE_REGIONSERVER_OPT变量来设置垃圾回收的选项,后面一般是用于配置RegionServer的,需要在每个子节点的HBASE_OPTS文件中进行配置。

1)首先是设置新生代大小的参数,不能过小,过小则导致年轻代过快成为老生代,引起老生代产生内存随便。同样不能过大,过大导致所有的JAVA进程停止时间长。-XX:MaxNewSize=256m-XX:NewSize=256m 这两个可以合并成为-Xmn256m这一个配置来完成。

2)其次是设置垃圾回收策略:-XX:+UseParNewGC -XX:+UseConcMarkSweepGC也叫收集器设置。

3)设置CMS的值,占比多少时,开始并发标记和清扫检查。-XX:CMSInitiatingOccupancyFraction=70

4)打印垃圾回收信息:-verbose:gc -XX: +PrintGCDetails -XX:+PrintGCTimeStamps

-Xloggc:$HBASE_HOME/logs/gc-$(hostname)-hbase.log

最终可以得到:HBASE_REGIONSERVER_OPT="-Xmx8g -Xms8g –Xmn256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC  \

-XX:CMSInitiatingOccupancyFraction=70   -verbose:gc \

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps \

-Xloggc:$HBASE_HOME/logs/gc-$(hostname)-hbase.log

(2)hbase.hregion.memstore.mslab.enabled默认值:true,这个是在hbase-site.xml中进行配置的值。

说明:减少因内存碎片导致的Full GC,提高整体性能。

2.启用压缩,详情自行搜索,暂时未曾尝试,后面持续更新。

3.优化Region拆分合并以及与拆分Region

(1)hbase.hregion.max.filesize默认为256M(在hbase-site.xml中进行配置),当region达到这个阈值时,会自动拆分。可以把这个值设的无限大,则可以关闭HBase自动管理拆分,手动运行命令来进行region拆分,这样可以在不同的region上交错运行,分散I/O负载。

(2)预拆分region

用户可以在建表的时候就制定好预设定的region,这样就可以避免后期region自动拆分造成I/O负载。

4.客户端入库调优

(1)用户在编写程序入库时,HBase的自动刷写是默认开启的,即用户每一次put都会提交到HBase server进行一次刷写,如果需要高速插入数据,则会造成I/O负载过重。在这里可以关闭自动刷写功能,setAutoFlush(false)。如此,put实例会先写到一个缓存中,这个缓存的大小通过hbase.client.write.buffer这个值来设定缓存区,当缓存区被填满之后才会被送出。如果想要显示刷写数据,可以调用flushCommits()方法。

此处引申:采取这个方法要估算服务器端内存占用则可以:hbase.client.write.buffer*hbase.regionserver.handler.count得出内存情况。

(2)第二个方法,是关闭每次put上的WAL(writeToWAL(flase))这样可以刷写数据前,不需要预写日志,但是如果数据重要的话建议不要关闭。

(3)hbase.client.scanner.caching:默认为1

这是设计客户端读取数据的配置调优,在hbase-site.xml中进行配置,代表scanner一次缓存多少数据(从服务器一次抓取多少数据来scan)默认的太小,但是对于大文件,值不应太大。

(4)hbase.regionserver.lease.period默认值:60000

说明:客户端租用HRegion server 期限,即超时阀值。

调优:这个配合hbase.client.scanner.caching使用,如果内存够大,但是取出较多数据后计算过程较长,可能超过这个阈值,适当可设置较长的响应时间以防被认为宕机。

(5)还有诸多实践,如设置过滤器,扫描缓存等,指定行扫描等多种客户端调优方案,需要在实践中慢慢挖掘。

5.HBase配置文件

上面涉及到的调优内容或多或少在HBase配置文件中都有所涉及,因此,下面的配置不涵盖上面已有的配置。

(1) zookeeper.session.timeout(默认3分钟)

ZK的超期参数,默认配置为3分钟,在生产环境上建议减小这个值在1分钟或更小。

设置原则:这个值越小,当RS故障时Hmaster获知越快,Hlog分裂和region 部署越快,集群恢复时间越短。 但是,设置这个值得原则是留足够的时间进行GC回收,否则会导致频繁的RS宕机。一般就做默认即可

(2)hbase.regionserver.handler.count(默认10)

对于大负载的put(达到了M范围)或是大范围的Scan操作,handler数目不易过大,易造成OOM。 对于小负载的put或是get,delete等操作,handler数要适当调大。根据上面的原则,要看我们的业务的情况来设置。(具体情况具体分析)。

(3)HBASE_HEAPSIZE(hbase-env.sh中配置)

我的前两篇文章Memstoresize40%(默认) blockcache 20%(默认)就是依据这个而成的,总体HBase内存配置。设到机器内存的1/2即可。

(4)选择使用压缩算法,目前HBase默认支持的压缩算法包括GZ,LZO以及snappy(hbase-site.xml中配置)

(5)hbase.hregion.max.filesize默认256M

上面说过了,hbase自动拆分region的阈值,可以设大或者无限大,无限大需要手动拆分region,懒的人别这样。

(6)hbase.hregion.memstore.flush.size

单个region内所有的memstore大小总和超过指定值时,flush该region的所有memstore。

(7)hbase.hstore.blockingStoreFiles  默认值:7

说明:在flush时,当一个region中的Store(CoulmnFamily)内有超过7个storefile时,则block所有的写请求进行compaction,以减少storefile数量。

调优:block写请求会严重影响当前regionServer的响应时间,但过多的storefile也会影响读性能。从实际应用来看,为了获取较平滑的响应时间,可将值设为无限大。如果能容忍响应时间出现较大的波峰波谷,那么默认或根据自身场景调整即可。

(8)hbase.hregion.memstore.block.multiplier默认值:2

说明:当一个region里总的memstore占用内存大小超过hbase.hregion.memstore.flush.size两倍的大小时,block该region的所有请求,进行flush,释放内存。

虽然我们设置了region所占用的memstores总内存大小,比如64M,但想象一下,在最后63.9M的时候,我Put了一个200M的数据,此时memstore的大小会瞬间暴涨到超过预期的hbase.hregion.memstore.flush.size的几倍。这个参数的作用是当memstore的大小增至超过hbase.hregion.memstore.flush.size 2倍时,block所有请求,遏制风险进一步扩大。

调优: 这个参数的默认值还是比较靠谱的。如果你预估你的正常应用场景(不包括异常)不会出现突发写或写的量可控,那么保持默认值即可。如果正常情况下,你的写请求量就会经常暴长到正常的几倍,那么你应该调大这个倍数并调整其他参数值,比如hfile.block.cache.size和hbase.regionserver.global.memstore.upperLimit/lowerLimit,以预留更多内存,防止HBase server OOM。

(9)hbase.regionserver.global.memstore.upperLimit:默认40%

当ReigonServer内所有region的memstores所占用内存总和达到heap的40%时,HBase会强制block所有的更新并flush这些region以释放所有memstore占用的内存。

hbase.regionserver.global.memstore.lowerLimit:默认35%

同upperLimit,只不过lowerLimit在所有region的memstores所占用内存达到Heap的35%时,不flush所有的memstore。它会找一个memstore内存占用最大的region,做个别flush,此时写更新还是会被block。lowerLimit算是一个在所有region强制flush导致性能降低前的补救措施。在日志中,表现为 “** Flushthread woke up with memory above low water.”。

调优:这是一个Heap内存保护参数,默认值已经能适用大多数场景。

(10)hfile.block.cache.size:默认20%

  这是涉及hbase读取文件的主要配置,BlockCache主要提供给读使用。读请求先到memstore中查数据,查不到就到blockcache中查,再查不到就会到磁盘上读,并把读的结果放入blockcache。由于blockcache是一个LRU,因此blockcache达到上限(heapsize * hfile.block.cache.size)后,会启动淘汰机制,淘汰掉最老的一批数据。对于注重读响应时间的系统,应该将blockcache设大些,比如设置blockcache=0.4,memstore=0.39,这会加大缓存命中率。

(11)hbase.regionserver.hlog.blocksize和hbase.regionserver.maxlogs

之所以把这两个值放在一起,是因为WAL的最大值由hbase.regionserver.maxlogs*hbase.regionserver.hlog.blocksize (2GB by default)决定。一旦达到这个值,Memstore flush就会被触发。所以,当你增加Memstore的大小以及调整其他的Memstore的设置项时,你也需要去调整HLog的配置项。否则,WAL的大小限制可能会首先被触发,因而,你将利用不到其他专门为Memstore而设计的优化。抛开这些不说,通过WAL限制来触发Memstore的flush并非最佳方式,这样做可能会会一次flush很多Region,尽管“写数据”是很好的分布于整个集群,进而很有可能会引发flush“大风暴”。

提示:最好将hbase.regionserver.hlog.blocksize* hbase.regionserver.maxlogs 设置为稍微大于hbase.regionserver.global.memstore.lowerLimit* HBASE_HEAPSIZE。

6.HDFS优化部分

HBase是基于hdfs文件系统的一个数据库,其数据最终是写到hdfs中的,因此涉及hdfs调优的部分也是必不可少的。

(1)dfs.replication.interval:默认3秒

可以调高,避免hdfs频繁备份,从而提高吞吐率。

(2)dfs.datanode.handler.count:默认为10

可以调高这个处理线程数,使得写数据更快

(3)dfs.namenode.handler.count:默认为8

(4)dfs.datanode.socket.write.timeout:默认480秒,并发写数据量大的时候可以调高一些,否则会出现我另外一篇博客介绍的的错误。

(5)dfs.socket.timeout:最好也要调高,默认的很小。

同上,可以调高,提高整体速度与性能。

 

以上就是hbase整体性能调优攻略,仍然会有遗漏的地方或补充,在实际中会慢慢完善,有不足的也请各位同仁指出!

 

Hbase管理指南优化策略

设置 Hadoop 来扩展磁盘 I/O

现代服务器通常有多个磁盘硬件来提供大存储能力。这些磁盘通常配置成 RAID 阵列,作为它们的出厂设置。这在很多情况下是有益的,但对 Hadoop 却不是。

Hadoop 的 slave 节点存储了 HDFS 数据块和 MapReduce 临时文件在它的本地磁盘。这些本地磁盘操作受益于使用多个独立的磁盘来扩展磁盘 I/O。

在这方面,我们将描述怎样通过使用多个磁盘设置 Hadoop 来扩展磁盘 I/O。

准备工作

我们假设你的每个 DataNode 节点都有多个磁盘。这些磁盘是 JBOD (简单磁盘捆绑)或 RAID0 配置。假设这些磁盘挂载在 /mnt/d0, /mnt/d1, …, /mnt/dn。并且用户在每个挂载点授予了 HDFS 写权限。

怎样做

为了设置 Hadoop 来扩展磁盘 I/O,遵照这些指示:

  1. 在每个 DataNode 节点,在每块磁盘上为 HDFS 创建目录来存储它的数据块:

    hadoop$ mkdir -p /mnt/d0/dfs/data
    hadoop$ mkdir -p /mnt/d1/dfs/data
    …
    hadoop$ mkdir -p /mnt/dn/dfs/data
  2. 加入以下代码到 HDFS 配置文件中(hdfs-site.xml):

    <property>
      <name>dfs.data.dir</name>
      <value>/mnt/d0/dfs/data,/mnt/d1/dfs/data,...,/mnt/dn/dfs/data</value></property>
  3. 同步修改过的 hdfs-site.xml 到集群:

    hadoop@master1$ for slave in `cat     $HADOOP_HOME/conf/slaves`do
       rsync -avz $HADOOP_HOME/conf/ $slave:$HADOOP_HOME/conf/
    done
  4. 重起 HDFS:

    hadoop@master1$ $HADOOP_HOME/bin/stop-dfs.sh
    hadoop@master1$ $HADOOP_HOME/bin/start-dfs.sh

它怎样工作

我建议 DataNode 节点为 JBOD 或是 RAID0,因为你不需要 RAID 冗余,因为 HDFS 通过使用节点间的副本确保了它的数据冗余。因此,当单个磁盘失败了,不会造成数据丢失。

选择哪一个,JBOD 或 RAID0?从理论上来说,使用 JBOD 配置会比 RAID 配置的性能更好。这是因为,在 RAID 配置中,在整个写操作完成之前,你不得不等待阵列中最慢的磁盘完成,这使得平均 I/O 时间等于最慢的磁盘 I/O 时间。JBOD 配置中,最快的磁盘中的操作独立于最慢的磁盘,这使得平均的 I/O 时间比最慢的快。尽管如此,企业级的 RAID 卡或许有很大的影响。在决定选择哪个之前,你或许想对你的 JBOD 和 RAID0 做下基准测试。

对于这两个 JBOD 和 RAID0 配置,你将把磁盘挂载在不同的路径。这里的关键点是设置 dfs.data.dirproperty 在每个磁盘上的所有目录的创建。dfs.data.dirproperty 指定了DataNode 应该存储它本地块在哪里。通过设置它来逗号分隔多个目录,DataNode 以 round robin 方法在所有磁盘上存储它的块。这会使得 Hadoop 高效的扩展磁盘 I/O 操作所有磁盘。

警告:不要在 dfs.data.dir property 值的目录路径之间留下空白,不然它或许不会按照期望那样工作。

你将需要同步这些改变到所有的集群中,并且重起 HDFS 以使它们生效。

不止这些

如果你运行了 MapReduce,MapReduce 存储它的临时文件在 TaskTracker 的本地文件系统,你或许也想设置 MapReduce 来扩展它的磁盘 I/O:

  1. 在每个 TaskTracker 节点,为 MapReduce 在每个磁盘上创建目录来存储它的中间数据文件:

    hadoop$ mkdir -p /mnt/d0/mapred/local
    hadoop$ mkdir -p /mnt/d1/mapred/local
    …
    hadoop$ mkdir -p /mnt/dn/mapred/local
  2. 把以下代码加入 MapReduce 的配置文件中(mapred-site.xml):

    hadoop@master1$ vi $HADOOP_HOME/conf/mapred-site.xml
    <property><name>mapred.local.dir</name><value>/mnt/d0/mapred/local,/mnt/d1/mapred/local,...,/mnt/dn/mapred/local</value></property>
  3. 同步变更的 mapred-site.xml 文件到集群中,并重起 MapReduce

MapReduce 在执行期间在 TaskTracker 的本地磁盘生成很多临时文件。像 HDFS,在不同的磁盘上设置多个目录有助于大大扩展 MapReduce 的磁盘 I/O。

使用网络拓扑脚本来使得 Hadoop 能机架感知

Hadoop 有“机架感知”的概念,管理者可以定义在集群中每个 DataNode 的机架位置。使得 Hadoop 能机架感知及其重要的,因为:

  • 机架感知预防数据丢失

  • 机架感知提升网络性能

在这方面,我们将描述怎样使得 Hadoop 可以机架感知以及为什么它那么重要。

准备工作

你需要知道你的每一个 slave 节点属于哪个机架。以启动 Hadoop 的用户登陆进 master 节点。

怎样做

以下步骤描述了怎样使得 Hadoop 能机架感知:

  1. 创建一个 topology.sh 脚本并把它存储在 Hadoop 的配置目录下。改变 topology.data 的路径,在第 3 行,加入你的环境变量:

    hadoop@master1$ vi $HADOOP_HOME/conf/topology.sh
    while [ $# -gt 0 ] ; do
     nodeArg=$1
     exec< /usr/local/hadoop/current/conf/topology.data
     result=""
     while read line ; do
       ar=( $line )
       if [ "${ar[0]}" = "$nodeArg" ] ; then
         result="${ar[1]}"
       fi
     done
     shift
     if [ -z "$result" ] ; then
       echo -n "/default/rack "
     else
       echo -n "$result "
     fidone

    不要忘记设置这个脚本的可执行权限

    hadoop@master1$ chmod +x $HADOOP_HOME/conf/topology.sh
  2. 创建一个 topology.data 文件,如以下片段中那样;改变 IP 地址和机架,加入你自己的环境变量:

    hadoop@master1$ vi $HADOOP_HOME/conf/topology.data
    10.161.30.108 /dc1/rack1
    10.166.221.198 /dc1/rack2
    10.160.19.149 /dc1/rack3
  3. 把以下加入你的 Hadoop 核心配置文件(ore-site.xml):

    hadoop@master1$ vi $HADOOP_HOME/conf/core-site.xml
    <property><name>topology.script.file.name</name><value>/usr/local/hadoop/current/conf/topology.sh</value></property>
  4. 在集群中同步变更的文件并重起 HDFS 和 MapReduce。

  5. 确保 HDFS 现在是机架感知的。如果一切工作良好,你应该可以在你的 NameNode 日志文件中发现像以下的一些东西:

    2012-03-10 13:43:17,284 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack3/10.160.19.149:500102012-03-10 13:43:17,297 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack1/10.161.30.108:500102012-03-10 13:43:17,429 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack2/10.166.221.198:50010
  6. 确保 MapReduce 现在是机架感知的。如果一切工作良好,你应该会在你的 JobTracker 日志文件中发现像以下的一些东西:

    2012-03-10 13:50:38,341 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack3/ip-10-160-19-149.us-west-1.compute.internal2012-03-10 13:50:38,485 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack1/ip-10-161-30-108.us-west-1.compute.internal2012-03-10 13:50:38,569 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack2/ip-10-166-221-198.us-west-1.compute.internal

它怎样工作

下面的图表显示了 Hadoop 机架感知的概念:

HDFS 文件的每个块将被复制到多个 DataNodes,来预防丢失所有的数据副本由于一台机器的失效。尽管如此,如果所有的副本数据被复制到同一个机架的 DataNodes,然后这个机架失效了,所有的数据副本将被丢失。为了避免这个,NameNode 需要知道网络拓扑为了使用那些信息来使得数据智能复制。

正如以上图表所显示的那样,使用默认的三个复制因素,两个数据副本将位于同一个机架的机器上,另外一个将被放在一个不同机架的机器上。这确保了单个机架失效,不会导致所有数据副本丢失。正常来说,同一个机架的两台机器有更多的带宽和更低的延迟,相对于两台机器在不同的机架来说。使用网络拓扑信息,Hadoop 可以通过从合适的 DataNodes 读取数据来最大化提升网络性能。如果数据在本地机器可用,Hadoop 将从它那里读取数据。如果不可用,Hadoop 将尝试从同一机架的机器读取数据,如果它也是不可用的,将从不同机架的机器读取数据。

在步骤 1 中,我们创建了 topology.sh 脚本。这个脚本以 DNS 名字作为参数并返回网络拓扑名字(机架)作为输出。DNS 名字映射到网络拓扑是由 topology.data 文件提供,在步骤 2 中创建。如果一个实体没有在 topology.data 中发现,脚本将返回 /default/rack 作为默认的机架名字。

注意:我们使用 IP 地址,而不是主机名在 topology.data 文件中。这是一个已知的 BUG,Hadoop 没有正确处理开头字母从 “a” 到 “f” 主机名。检查 HADOOP-6682 获取更多信息。

注:上述的 BUG 已经在 0.22.0 版本中解决了。所以应该可以使用主机名了,但是需要测试下。

在步骤 3 中,我们在 core-site.xml 中设置 topology.script.file.name 属性,告诉 Hadoop 调用 topology.sh 来解析 DNS 名字为网络拓扑名字。

然后重起 Hadoop,如步骤 5 和 6 中的日志显示那样,HDFS 和 MapReduce 加入了正确的机架名作为 slave 节点 DNS 名字的前缀。这个表明 HDFS 和 MapReduce 的机架感知使用上面提到的设置工作的很好。

使用 noatime 和 nodiratime 挂载磁盘

如果你纯粹是为 Hadoop 挂载磁盘,你可以使用 ext3 或 ext4,或 XFS 文件系统,我建议你使用 noatime 和 nodiratime 属性挂载磁盘。

如果你以 noatime 挂载磁盘,访问时间戳不会更新,当一个文件在文件系统中被读的时候。在这个 nodiratime 属性情况中,挂载磁盘不会更新文件系统中目录的 inode 访问时间。因为它们没有更多的磁盘 I/O 更新访问时间戳,这提升了文件系统的访问速度。

在这方面,我们将描述为什么 Hadoop 建议使用 noatime 和 nodiratime,以及怎样使用 noatime 和 nodiratime 挂载磁盘。

准备工作

你需要在你的 slave 节点有 root 权限。我们假设你的 Hadoop 仅仅只有两块磁盘 - /dev/xvdc 和 /dev/xvdd。这两块磁盘被分别地挂载在 /mnt/is1 和 /mnt/is2。而且,我们假设你使用的是 ext3 文件系统。

怎样做

为了使用 noatime 和 nodiratime 挂载磁盘,在集群中的每台 slave 节点执行以下指令:

  1. 加入以下命令到 /etc/fstab 文件中:

    $ sudo vi /etc/fstab
    /dev/xvdc /mnt/is1 ext3 defaults,noatime,nodiratime 0 0
    /dev/xvdd /mnt/is2 ext3 defaults,noatime,nodiratime 0 0
  2. 卸载磁盘并再次挂载它们使变更生效:

    $ sudo umount /dev/xvdc
    $ sudo umount /dev/xvdd
    
    $ sudo mount /dev/xvdc
    $ sudo mount /dev/xvdd
  3. 检查已经生效的挂载选项:

    $ mount
    /dev/xvdc on /mnt/is1 type ext3 (rw,noatime,nodiratime)
    /dev/xvdd on /mnt/is2 type ext3 (rw,noatime,nodiratime)

它怎样工作

因为 Hadoop (HDFS) 使用 NameNode 管理元数据(inode),被 Hadoop 保存的任何访问时间信息是独立块的独立 atime 属性。因此,DataNode 本地文件系统的访问时间戳是没有意义的。这就是为什么我建议你使用noatime 和 nodiratime 挂载磁盘,如果磁盘纯粹是为了给 Hadoop 使用。使用noatime 和 nodiratime 挂载磁盘保存了一个本地文件每次被访问的写 I/O。

这些选项被设置在 /etc/fstab 文件中,不要忘记卸载和再次挂载,为了使变更生效。

使得这些选项生效,可以提升 HDFS 的读性能。因为 HBase 存储在 HDFS 上存储它的数据,HBase 读性能也会被提升。

不止这些

另一个优化方法是降低 ext3 或 ext4 文件系统上保留块的百分比。默认,一些文件系统块被保留供特权进程使用。这是为了避免用户进程装满磁盘空间的情况,这是被系统守护进程要求的,为了保持正常工作。这是非常重要的对于主机操作系统中的磁盘,但是对于仅仅是 Hadoop 使用的磁盘作用有限。

通常这些 Hadoop 磁盘有一个非常大的存储空间。降低保留块的百分比可以给 HDFS 集群增加一些存储容量。正常来说,默认的保留块百分比是 5%。它可以被降低到 1%。

注意:不要在主机操作系统上降低磁盘空间的保留块。

为了实现这个,在集群中的每个 slave 节点的每个磁盘运行以下命令:

$ sudo tune2fs -m 1 /dev/xvdc
tune2fs 1.41.12 (17-May-2010)
Setting reserved blocks percentage to 1% (1100915 blocks)

把 vm.swappiness 设置成 0 来避免 swap

Linux 移动那些一段时间没有被访问的内存页到 swap 空间,即使它由足够可用的内存。这叫做 swap out。换一句话说,从 swap 空间读 swapped out 的数据到内存中叫做 swap in。Swapping 在大多数情形是有必要的,但是因为 Java Virtual Machine(JVM) 在 swapping 下不是表现的很好,如果 swapped 了,HBase 运行可能会遇到问题。ZooKeeper 的 session 过期或许是 被 swap 引入的典型问题。

在这方面,我们将描述怎样调整 Linux 的 vm.swappiness 参数来避免 swap。

准备工作

确保在你的集群节点中有 root 权限。

怎样做

为了调整 Linux 参数来避免 swap,在集群中的每个节点上调用以下命令:

  1. 执行以下命令来设置 vm.swappiness 参数为 0:

    root# sysctl -w vm.swappiness=0
    vm.swappiness = 0

    这个改变将一直有效直到下次服务器重启。

  2. 把以下加入 /etc/sysctl.conf 文件,以至于该设置永久生效:

    root# echo "vm.swappiness = 0" >> /etc/sysctl.conf

它怎样工作

vm.swappiness 参数被用于定义多少积极内存页被交换到磁盘。它接收从 0 到 100 的任何值 - 一个更低的值意味着内核将更少的交换,但是一个更高的值使得内核应用更经常的交换。默认值是 60。

我们在步骤 1 中把 vm.swappiness 设置成 0,这将使得内核避免把进程尽可能的从物理内存中交换出去。这对 HBase 是非常有用的,因为 HBase 的进程消费大量的内存,一个高的 vm.swappiness 值将使得 HBase 交换很多并遭遇非常慢的垃圾回收。随着 ZooKeeper session 超时,这可能会导致 RegionServer 进程被杀死。我们建议你设置它为 0 或者任何更低的数字(比如,10)并观察 swapping 状态。

注意该值被 sysctl 命令设置仅仅会持久化直到服务器下次重启。你需要在 /etc/sysctl.conf 文件设置 vm.swappiness,以至于该设置无论服务器什么时候重启都会生效。

Java GC 和 HBase 堆设置

因为 HBase 运行在 JVM,JVM 的 Garbage Collection(GC) 设置对于 HBase 流畅的运行,更高的性能是非常重要的,除了配置 HBase 堆设置的指导方针之外。有 HBase 进程输出到它们的 GC 日志中是同样重要的,并且它们基于 GC 日志的输出调整 JVM 设置。

我将描述最重要的 HBase JVM 堆设置,也描述怎样是它生效以及理解 GC 日志,在这方面。我将覆盖一些指导方针来调整 HBase 的 Java GC 设置。

准备工作

登陆你的 HBase region 服务器。

怎样做

以下被建议用于 Java GC 和 HBase 堆设置:

  1. 通过编辑 hbase-env.sh 文件给 HBase 足够大的堆大小。比如,以下片段给 HBase 配置一个 8000-MB 的堆:

    $ vi $HBASE_HOME/conf/hbase-env.sh
    export HBASE_HEAPSIZE=8000
  2. 通过以下命令使得 GC 日志生效:

    export HBASE_OPTS="$HBASE_OPTS -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/local/hbase/logs/gc-hbase.log"
  3. 把以下代码加入来比默认更早的开始 Concurrent-Mark-Sweep GC(CMS)

    $ vi $HBASE_HOME/conf/hbase-env.sh
    export HBASE_OPTS="$HBASE_OPTS -XX:CMSInitiatingOccupancyFraction=60"
  4. 在集群中同步变更并重启 HBase。

  5. 检查输出到指定日志文件中(/usr/local/hbase/logs/gc-hbase.log)的 GC 日志。GC 日志看起来像以下屏幕截图:

它怎样工作

在步骤 1 中,我们配置 HBase 堆内存大小。默认,HBase 使用 1GB 的堆,这对于现代的机器来说太低了。对于 HBase 来说,比 4GB 更大是好的。我们建议 8GB 或更大,但是低于 16 GB。

在步骤 2 中,我们是 JVM 日志生效,使用这个设置,你可以获取 region 服务器的 JVM 日志,和我们在步骤 5 中展示的类似。关于 JVM 内存分配和垃圾回收的基础知识是被要求的,为了明白日志输出。以下是 JVM 分代垃圾收集系统的图表:

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

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

回到我们示例的步骤 5,上述选项产出的 minor GC 输出为以下形式:

<timestamp>: [GC [<collector>: <starting occupancy1> -> <ending occupancy1>, <pause time1> secs] 
<starting occupancy3> -> <ending occupancy3>, <pause time3> secs] 
[Times: <user time> <system time>, <real time>]

在这个输出中:

  • timestamp 是 GC 发生的时间,相对于应用的启动时间。

  • collector 是 collector 用于 minor collection 的内部名字

  • starting occupancy1 是年轻代在垃圾回收前的占用

  • ending occupancy1 是年轻代在垃圾回收后的占用

  • pause time1 是 minor collection 中断的时间

  • starting occupancy3 是在垃圾回收前整个堆的占用

  • ending occupancy3 是在垃圾回收后整个堆的占用

  • pause time3 是整个垃圾回收的中断时间,这包括 major collection。

  • [Time:] 解释了花费在垃圾收集的时间,用户时间,系统时间,实际时间。

在步骤 5 中我们输出的第一行表明了是一个 minor GC,中断了 JVM 0.0764200 秒,它已经把年轻代的空间从 14.8MB 降低到 1.6MB。

接着,我们看看 CMS GC 日志,HBase 使用 CMS GC 作为它默认的老年代垃圾回收器。

CMS GC 执行以下步骤:

  1. 初始化标记

  2. 并发标记

  3. 重复标记

  4. 并发休眠

CMS 仅仅在它初始化标记和重复标记的阶段中断应用进程。在并发标记和睡眠阶段,CMS 线程随着应用线程一起运行。

在该示例的第二行表明了 CMS 初始化标记花费了 0.0100050 秒,并发标记花费了 6.496 秒。注意,并发标记,Java 不会被中断。

在 GC 日志的早期屏幕截图中,在行开始于 1441.435: [GC[YG occupancy:…] 的地方有一个中断。这里的中断是 0.0413960 秒,用于重复标记堆。之后,你可以看到睡眠开始了。CMS 睡眠花费了 3.446 秒,但是堆大小在这里没有变化太多(它继续占据大约 150MB)。

这里的调整点是使得所有的中断时间更低。为了保持中断时间更低,你需要使用 -XX:NewSize 和 -XX:MaxNewSize JVM 参数调整年轻代空间大小,为了将它们设置为相对较小的值(比如,调高几百 MB)。如果服务器有更多的 CPU 资源,我们建议通过设置 -XX:+UseParNewGC 选项使用 Parallel New Collector。你或许也想为你的年轻代调整 parallel GC 线程数量,通过 -XX:ParallelGCThreads JVM 参数。

我们建议加入上述设置到 HBASE_REGIONSERVER_OPTS 变量中,代替 hbase-env.sh 文件中的 HBASE_OPTS 变量。HBASE_REGIONSERVER_OPTS 仅仅影响 region 服务器的进程,这非常好,因为 HBase master 既不处理重型任务也不参与数据处理。

对于老年代来说, concurrent collection (CMS) 通常不能被加速,但是它可以更早的开始。当分配在老年代的空间比率超过了一个阀值,CMS 开始运行。这个阀值是被收集器自动计算的。对于有些情况,特别是在加载期间,如果 CMS 开始的太晚,HBase 或许会直接进行 full garbage collection。为了避免这个,我们建议设置 -XX:CMSInitiatingOccupancyFraction JVM 参数来精确指定在多少百分比 CMS 应该被开始,正如我们在步骤 3 中做的那样。在 百分之 60 或 70 开始是一个好的实践。当老年代使用 CMS,默认的年轻代 GC 将被设置成 Parallel New Collector。

不止这些

如果你之前使用的是 HBase 0.92 版本,考虑使用 MemStore-Local 分配 Buffer 来预防老年代堆碎片,在频繁写的负载下:

$ vi $HBASE_HOME/conf/hbase-site.xml
  <property>
    <name>hbase.hregion.memstore.mslab.enabled</name>
    <value>true</value>
  </property>

这个特性在 HBase 0.92 中是默认开启的。

使用压缩

HBase 另外一个最重要的特性就是使用压缩。它是非常重要的,因为:

  • 压缩降低从 HDFS 读写的字节数

  • 节约磁盘空间

  • 当从一个远程服务器获取数据的时候,提升了网络带宽的效率

HBase 支持 GZip 和 LZO 格式,我的建议是使用 LZO 压缩算法,因为它解压数据快并且 CPU 使用率低。更好的压缩比是系统的首选,你应该考虑 GZip。

不幸的是,HBase 不能使用 LZO,因为 license 问题。HBase 是 Apache-licensed,然而 LZO 是 GPL-licensed。因此,我们需要自己安装 LZO。我们将使用 hadoop-lzo 库,给 Hadoop 带来了变形的 LZO 算法。

在这方面,我们将描述怎样安装 LZO 和怎样配置 HBase 使用 LZO 压缩。

准备工作

确保在 hadoop-lzo 被构建的机器上 Java 安装了。Apache Ant 被要求用来从源码构建 hadoop-lzo。通过运行一下命令来安装 Ant:

$ sudo apt-get -y install ant

集群中的所有节点需要有原生的 LZO 库被安装。你可以通过使用以下命令安装:

$ sudo apt-get -y install liblzo2-dev

怎样做

我们将使用 hadoop-lzo 库来给 HBase 添加 LZO 压缩支持:

  1. 从 https://github.com/toddlipcon/hadoop-lzo 获取最新的 hadoop-lzo 源码

  2. 从源码构建原生的 hadoop-lzo 库。依赖于你的 OS,你应该选择构建 32-bit 或 64-bit 的二进制包。比如,为了构建 32-bit 二进制包,运行以下命令:

    $ export JAVA_HOME="/usr/local/jdk1.6"$ export CFLAGS="-m32"$ export CXXFLAGS="-m32"$ cd hadoop-lzo
    $ ant compile-native
    $ ant jar

    这些命令将创建 hadoop-lzo/build/native 目录和 hadoop-lzo/build/hadoop-lzo-x.y.z.jar 文件。为了构建 64-bit 二进制包,你需要改变 CFLAGS 和 CXXFLAGS 成 m64。

  3. 拷贝构建的包到你master 节点的 $HBASE_HOME/lib 和 $HBASE_HOME/lib/native 目录:

    hadoop@master1$ cp hadoop-lzo/build/hadoop-lzo-x.y.z.jar     $HBASE_HOME/lib
    hadoop@master1$ mkdir $HBASE_HOME/lib/native/Linux-i386-32
    hadoop@master1$ cp  hadoop-lzo/build/native/Linux-i386-32/lib/* $HBASE_HOME/lib/native/Linux-i386-32/

    对于一个 64-bit OS,把 Linux-i386-32 改变成(在前面步骤中) Linux-amd64-64。

  4. 添加 hbase.regionserver.codecs 的配置到你的 hbase-site.xml 文件:

    hadoop@master1$ vi $HBASE_HOME/conf/hbase-site.xml
    <property><name>hbase.regionserver.codecs</name><value>lzo,gz</value></property>
  5. 在集群中同步 $HBASE_HOME/conf 和 $HBASE_HOME/lib 目录。

  6. HBase ships 使用一个工具来测试压缩是否被正确设置了。使用这个工具来在集群中的每个节点上测试 LZO 设置。如果一切都正确无误的配置了,你将得到成功的输出:

    hadoop@client1$ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.util.CompressionTest /tmp/lzotest lzo
    12/03/11 11:01:08 INFO hfile.CacheConfig: Allocating LruBlockCache with maximum size 249.6m
    12/03/11 11:01:08 INFO lzo.GPLNativeCodeLoader: Loaded native gpl library
    12/03/11 11:01:08 INFO lzo.LzoCodec: Successfully loaded & initialized native-lzo library [hadoop-lzo rev Unknown build revision]
    12/03/11 11:01:08 INFO compress.CodecPool: Got brand-new compressor
    12/03/11 11:01:18 INFO compress.CodecPool: Got brand-new decompressor
    SUCCESS
  7. 通过使用 LZO 压缩创建一个表来测试配置,并在 HBase Shell 中验证它:

    $ hbase> create &apos;t1&apos;, {NAME => &apos;cf1&apos;, COMPRESSION => &apos;LZO&apos;}
    $ hbase> describe &apos;t1&apos;
    DESCRIPTION 
    ENABLED 
    {NAME => &apos;t1&apos;, FAMILIES => [{NAME => &apos;cf1&apos;, BLOOMFILTER => 
    &apos;NONE&apos;, true 
    REPLICATION_SCOPE => &apos;0&apos;, VERSIONS => &apos;3&apos;, COMPRESSION => &apos;LZO&apos;,    
    MIN_VERSIONS => &apos;0&apos;, TTL => &apos;2147483647&apos;, BLOCKSIZE => &apos;65536&apos;, 
    IN _MEMORY => &apos;false&apos;, BLOCKCACHE => &apos;true&apos;}]}                                                           
    1 row(s) in 0.0790 seconds

它怎样工作

hbase.hregion.majorcompaction 属性指定了在 region 上所有存储文件之间的 major compactions 时间。默认是时间是 86400000,即一天。我们在步骤 1 中把它设置为 0,是禁止自动的 major compaction。这将预防 major compaction 在繁忙加载时间运行,比如当 MapReduce 任务正运行在 HBase 集群上。

换句话说, major compaction 被要求来帮助提升性能。在步骤 4 中,我们已经展示了通过 HBase Shell 怎样在一个特别的 region 上手动触发 major compaction 的示例。在这个示例中,我们已经传递了一个 region 名字给 major_compact 命令来仅仅在一台单独的 region 上调用 major compaction。它也可能在一张表中的所有 region 上运行 major compaction,通过传递表名给该命令。major_compact 命令为 major compaction 给指定的表或 region 排队;但是通过 region 服务器托管它们,这些将在后台执行。

正如我们在早前提到的,你或许仅仅想在一个低负载时期手动执行 major compaction。这可以很容易的通过一个定时任务调用 major_compact 来实现。

不止这些

另外一个调用 major compaction 的方法就是使用 org.apache.hadoop.hbase.client.HBaseAdmin 类提供的 majorCompact API。在 Java 中非常容易调用这个 API。因此你可以从 Java 中管理复杂的 major compaction 调度。

管理 region 拆分

通常一个 HBase 表从一个单独的 region 开始。尽管如此,因为数据保持增长和 region 达到了它配置的最大值,它自动分成两份,以至于它们能处理更多的数据。以下图表展示了一个 HBase region 拆分:

这是 HBase region 拆分的默认行为。这个原理在大多数情况下工作的很好,然而有遇到问题的情况,比如 split/ compaction 风暴问题。

随着统一的数据分布和增长,最后在表中的所有 region 都需要在同一时间拆分。紧接着一个拆分,压缩将在子 region 运行以重写他们的数据到独立的文件中。这会引起大量的磁盘 I/O 读写和网络流量。

为了避免这样的情况,你可以关闭自动拆分和手动调用它。因为你可以控制在何时调用拆分,它可以帮助扩展 I/O 负载。另一个优势是,手动拆分可以让你有更好的 regions 控制,帮助你跟踪和解决 region 相关的问题。

在这方面,我将描述怎样关闭自动 region 拆分和手动调用它。

准备工作

使用你启动集群的用户登录进你的 HBase master 服务器。

怎样做

为了关闭自动 region 拆分和手动调用它,遵循以下步骤:

  1. 在 hbase-site.xml 文件中加入以下代码:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property><name>hbase.hregion.max.filesize</name><value>107374182400</value></property>
  2. 在集群中同步这些变更并重启 HBase。

  3. 使用上述设置,region 拆分将不会发生直到 region 的大小到达了配置的 100GB 阀值。你将需要在选择的 region 上明确调用它。

  4. 为了通过 HBase Shell 运行一个 region 拆分,使用以下命令:

    $ echo "split &apos;hly_temp,,1327118470453.5ef67f6d2a792fb0bd737863dc00b6a7.&apos;" | $HBASE_HOME/bin/hbase shell
    HBase Shell; enter &apos;help<RETURN>&apos; for list of supported commands.
    Type "exit<RETURN>" to leave the HBase Shell  Version 0.92.0, r1231986, Tue Jan 17 02:30:24 UTC 2012
    split &apos;hly_temp,,1327118470453.5ef67f6d2a792fb0bd737863dc00b6a7.&apos;0 row(s) in 1.6810 seconds

它怎样工作

hbase.hregion.max.filesize 属性指定了最大的 region 大小(bytes)。默认,值是 1GB( HBase 0.92 之前的版本是 256MB)。这意味着当一个 region 超过这个大小,它将拆分成两个。在步骤 1 中我们设置 region 最大值为 100GB,这是一个非常高的数字。

因为拆分不会发生直到超过了 100GB 的边界,我们需要明确的调用它。在步骤 4,我们在一个指定的 region 上使用 split 命令通过 HBase Shell 调用拆分。

不要忘记拆分大的 region。一个 region 在 HBase 是基础的数据分布和负载单元。Region 应该在低负载时期被拆分成合适的大小。

换句话说;太多的拆分不好,在一台 region 服务器上有太多的拆分会降低它的性能。

在手动拆分 region 之后,你或许想触发 major compaction 和负载均衡。

不止这些

我们在前面的设置会引起整个集群有一个默认的 100GB 的region 最大值。除了改变整个集群,当在创建一张表的时候,也可以在一个列簇的基础上指定 MAX_FILESIZE 属性。

  $ hbase> create &apos;t1&apos;, {NAME => &apos;cf1&apos;, MAX_FILESIZE => &apos;107374182400&apos;}

像 major compaction,你也可以使用 org.apache.hadoop.hbase.client.HBaseAdmin 类提供的 split API。

四、高级配置和调整

打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » Hbase故障排查与性能调优

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!