首页 > 代码库 > 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 "
     fi
    done
    

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

    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:50010
    2012-03-10 13:43:17,297 INFO org.apache.hadoop.net.NetworkTopology: 
    Adding a new node: /dc1/rack1/10.161.30.108:50010
    2012-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.internal
    2012-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.internal
    2012-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 ‘t1‘, {NAME => ‘cf1‘, COMPRESSION => ‘LZO‘}
    $ hbase> describe ‘t1‘
    DESCRIPTION 
    ENABLED 
    {NAME => ‘t1‘, FAMILIES => [{NAME => ‘cf1‘, BLOOMFILTER => 
    ‘NONE‘, true 
    REPLICATION_SCOPE => ‘0‘, VERSIONS => ‘3‘, COMPRESSION => ‘LZO‘,    
    MIN_VERSIONS => ‘0‘, TTL => ‘2147483647‘, BLOCKSIZE => ‘65536‘, 
    IN _MEMORY => ‘false‘, BLOCKCACHE => ‘true‘}]}                                                           
    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 ‘hly_temp,,1327118470453.5ef67f6d2a792fb0bd737863dc00b6a7.‘" | $HBASE_HOME/bin/hbase shell
    HBase Shell; enter ‘help<RETURN>‘ 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 ‘hly_temp,,1327118470453.5ef67f6d2a792fb0bd737863dc00b6a7.‘
    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 ‘t1‘, {NAME => ‘cf1‘, MAX_FILESIZE => ‘107374182400‘}

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

HBase 管理,性能调优