HBase-管理秘籍-三-

72 阅读1小时+

HBase 管理秘籍(三)

原文:HBase Administration Cookbook

协议:CC BY-NC-SA 4.0

七、故障排除

在本章中,我们将介绍:

  • 故障排除工具
  • 处理 XceiverCount 错误
  • 处理“打开的文件太多”错误
  • 处理“无法创建新的本机线程”错误
  • 处理“HBase 忽略 HDFS 客户端配置”问题
  • 处理 ZooKeeper 客户端连接错误
  • 处理 ZooKeeper 会话过期错误
  • 处理 EC2 上的 HBase 启动错误

简介

每个人都希望自己的 HBase 集群能够平稳稳定地运行,但有时集群并不能像预期的那样工作,特别是在集群配置不好的情况下。 本章介绍对以意外状态运行的群集进行故障排除时可以执行的操作。

在开始对群集进行故障排除之前,最好熟悉帮助我们恢复群集的工具。 有用的工具与深入了解 HBase 和您正在操作的集群一样重要。 在第一个食谱中,我们将介绍几个推荐的工具及其示例用法。

问题通常发生在缺少基本设置的群集上。 如果您的集群遇到问题,您应该做的第一件事是分析主日志文件,因为主日志文件充当集群的协调器服务。 希望一旦在日志文件中找到警告或错误级别日志,您就能够确定错误的根本原因。 区域服务器日志文件是您需要检查的另一个来源。 区域服务器日志文件通常包含与加载相关的错误日志,因为区域服务器处理集群的实际数据存储和访问。

HBase 运行在 HDFS 之上,它依赖 ZooKeeper 作为其协调服务。 有时,还需要调查 HDFS、MapReduce 和 ZooKeeper 日志。 默认情况下,所有这些日志文件都存储在安装文件夹的 logs目录下。 当然,它可以在 log4j属性文件中配置。

如果发现错误消息,请搜索search-hadoop.com/上的在线资源;很可能以前已经报告和讨论过此问题。 有一个很棒的 HBase 社区,你可以随时寻求帮助。 不过,在提问之前,不要忘记先订阅-hbase.apache.org/mail-lists.…

在本章中,我们将回顾几个最棘手的问题。 我们将介绍这些问题的错误消息、发生原因以及如何使用故障排除工具修复这些问题。

故障排除工具

为了对 HBase 集群进行故障排除,除了扎实地了解您正在操作的集群之外,您使用的工具也很重要。 我们推荐以下故障排除工具:

  • ps:这可用于查找使用最多内存和 CPU 的顶级进程
  • **ClusterSSH 工具:**此工具用于同时控制多个 SSH 会话
  • jps:此工具显示当前用户的 Java 进程
  • jmap:此工具打印 Java 堆摘要
  • jstat:这是 Java 虚拟机统计监控工具
  • hbase hbck:此工具用于检查和修复区域一致性和表完整性
  • hadoop fsck:此工具用于检查 HDFS 一致性

在本食谱中,我们将描述这些工具的示例用法。

做好准备

启动您的 HBase 集群。

怎么做……

以下是我们将介绍的故障排除工具:

  • ps:此工具用于查找占用大量内存的顶级进程。 以下命令按内存使用情况降序对进程进行排序:

    $ ps auxk -rss | less
    USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
    hadoop 1346 1.2 6.6 1222680 115096 ? Sl 11:01 0:10 /usr/local/jdk1.6/bin/java -XX:OnOutOfMemoryError=kill -9 %p -Xmx1000m -ea -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -Dhbase.log.dir=/usr/local/hbase/logs
    ...
    org.apache.hadoop.hbase.regionserver.HRegionServer start
    
    
  • ClusterSSH tool: This tool is used to control multiple SSH sessions, simultaneously. In order to install ClusterSSH (cssh) on a Ubuntu desktop, run the following command:

    $ sudo apt-get install clusterssh
    
    

    还有一个针对 MacOSX 的 ClusterSSH 工具,名为csshx。 您可以从code.google.com/p/csshx/下载 csshx。 启动 ClusterSSH 工具以监视小型 HBase 集群。 例如,在 Mac OS X 上,运行以下命令以在 HBase 群集的每个节点上启动多个 SSH 会话:

    $ csshX --login hac master1 slave1 slave2 slave3
    
    

    还将创建一个主窗口(以下屏幕截图中的第二个)。 在红色主窗口中,键入以下命令以在每个节点上运行top命令:

    $ top
    
    

    您将看到与以下屏幕截图类似的屏幕:

    How to do it...

  • jps:此工具显示当前用户的 Java 进程。 要显示 hadoop用户的从节点上的 Java 进程,请运行以下命令:

    hadoop@slave1$ $JAVA_HOME/bin/jps
    1254 DataNode
    2445 Child
    1970 TaskTracker
    2481 Jps
    1812 HRegionServer
    
    
  • jmap:此工具打印 Java 堆摘要。 要打印前面提到的 HRegionServer 进程的 Java 堆摘要,请使用以下命令:

    hadoop@slave1$ $JAVA_HOME/bin/jmap -heap 1812
    Attaching to process ID 1812, please wait...
    Debugger attached successfully.
    Client compiler detected.
    JVM version is 20.4-b02
    using thread-local object allocation.
    
    Concurrent Mark-Sweep GC
    Heap Configuration:
    MinHeapFreeRatio = 40
    MaxHeapFreeRatio = 70
    MaxHeapSize = 1048576000 (1000.0MB)
    NewSize = 16777216 (16.0MB)
    MaxNewSize = 16777216 (16.0MB)
    OldSize = 50331648 (48.0MB)
    NewRatio = 7
    SurvivorRatio = 8
    PermSize = 12582912 (12.0MB)
    MaxPermSize = 67108864 (64.0MB)
    Heap Usage:
    New Generation (Eden + 1 Survivor Space):
    capacity = 15138816 (14.4375MB)
    used = 6533712 (6.2310333251953125MB)
    free = 8605104 (8.206466674804688MB)
    43.1586723823052% used
    Eden Space:
    capacity = 13500416 (12.875MB)
    used = 6514736 (6.2129364013671875MB)
    free = 6985680 (6.6620635986328125MB)
    48.25581670964806% used
    From Space:
    capacity = 1638400 (1.5625MB)
    used = 18976 (0.018096923828125MB)
    free = 1619424 (1.544403076171875MB)
    1.158203125% used
    To Space:
    capacity = 1638400 (1.5625MB)
    used = 0 (0.0MB)
    free = 1638400 (1.5625MB)
    0.0% used
    concurrent mark-sweep generation:
    capacity = 274624512 (261.90234375MB)
    used = 68805464 (65.61800384521484MB)
    free = 205819048 (196.28433990478516MB)
    25.05437824865393% used
    Perm Generation:
    capacity = 26222592 (25.0078125MB)
    used = 15656912 (14.931594848632812MB)
    free = 10565680 (10.076217651367188MB)
    59.70772073180256% used
    
    
  • jstat: This is the Java Virtual Machine statistics monitoring tool. Run the following command to show the summary of the garbage collection statistics of a HRegionServer process:

    hadoop@slave1$ jstat -gcutil 1812 1000
    
    

    输出如以下屏幕截图所示:

    How to do it...

它是如何工作的.

ps命令有一个 k选项,我们可以使用它来指定排序顺序。 我们将 -rss格式说明符传递给此选项。 这会导致 ps命令按照进程的驻留集大小按降序对进程进行排序。 我们使用此方法查找使用内存最多的顶级进程。

群集 SSH 工具便于管理小型群集。 您可以使用它在多个服务器上同时调用相同的命令。 与前面一样,我们打开一个到集群中的主节点和每个从节点的会话。 然后,我们通过主窗口在每台服务器上调用 top命令。 它充当群集的简单监控系统。

jps是用于管理 Java 进程的小型动手工具。 它显示了当前用户的 Java 进程。

步骤 4 显示了在运行 DataNode、RegionServer 和 TaskTracker 守护进程的从节点上执行 jps命令的输出。 Hadoop 和 HBase 都是用 Java 编写的;这使得 jps对我们来说是一个有用的工具。

jmap是打印 Java 堆摘要的工具。 如步骤 5 所示,我们使用 jmap -heap <PID>命令显示 HRegionServer 守护进程的堆摘要。 jmap将打印 HRegionServer 守护进程的堆配置和使用情况。

jstat是一个显示Java 虚拟机(JVM)统计监控数据的工具。 在本例中, jstat连接到 HRegionServer(PID 1812)的 JVM,每隔 1000 毫秒采集样本,并显示由 -gcutil选项指定的输出。 输出显示年轻一代收集不断发生。 例如,在第 4 和第 5 个样本之间,收集花费了 0.216 秒(YGCT列中的8.1347.918),并将对象从伊甸园空间(E)提升到旧空间(O),导致旧空间利用率从55.99%增加到58.73%。 在收集之前,幸存者空间(S0)被**100%利用,但在此收集之后仅被25.85%**利用。

另请参阅

  • HBase hbck-检查 HBase 群集配方的一致性,在第 3 章中使用管理工具
  • 第 5 章监控和诊断中,报告群集配方状态的简单脚本

处理 XceiverCount 错误

在本食谱中,我们将介绍如何对 DataNode 日志中显示的以下 XceiverCount 错误进行故障排除:

2012-02-18 17:08:10,695 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: DatanodeRegistration(10.166.111.191:50010, storageID=DS-2072496811-10.168.130.82-50010-1321345166369, infoPort=50075, ipcPort=50020):DataXceiver
java.io.IOException: xceiverCount 257 exceeds the limit of concurrent xcievers 256
at org.apache.hadoop.hdfs.server.datanode.DataXceiver.run (DataXceiver.java:92)
at java.lang.Thread.run(Thread.java:662)

做好准备

登录到您的主节点。

怎么做……

以下是修复 XceiverCount 错误的步骤:

  1. 将以下代码片段添加到 HDFS 设置文件(hdfs-site.xml):

    hadoop@master1$ vi $HADOOP_HOME/conf/hdfs-site.xml
    <property>
    <name>dfs.datanode.max.xcievers</name>
    <value>4096</value>
    </property>
    
    
  2. 在群集中同步 hdfs-site.xml文件:

    hadoop@master1$ for slave in `cat $HADOOP_HOME/conf/slaves`
    do
    rsync -avz $HADOOP_HOME/conf/ $slave:$HADOOP_HOME/conf/
    done
    
    
  3. 从主节点重新启动 HDFS 和 HBase:

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

它是如何工作的.

dfs.datanode.max.xcievers(是的,这是拼写错误)设置定义了 HDFS DataNode 在任何给定时间将服务的文件数的上限。 其默认值为 256,太小,无法在 HDFS 上运行 HBase。 如果您的 DataNode 达到此上限,您将在 DataNode 日志中看到一个错误日志,通知您已超过 Xciever 计数。

我们建议在现代机器上将其设置为更高的值,例如 4096。 更改此设置后,您需要在整个群集中同步修改后的 hdfs-site.xml文件,然后重新启动 HDFS 以应用更改。

处理“打开的文件太多”错误

在本食谱中,我们将介绍如何对以下 DataNode 日志中显示的错误进行故障排除:

2012-02-18 17:43:18,009 ERROR org.apache.hadoop.hdfs.server.datanode.DataNode: DatanodeRegistration(10.166.111.191:50010, storageID=DS-2072496811-10.168.130.82-50010-1321345166369, infoPort=50075, ipcPort=50020):DataXceiver
java.io.FileNotFoundException: /usr/local/hadoop/var/dfs/data/current/subdir6/blk_-8839555124496884481 (Too many open files)
at java.io.RandomAccessFile.open(Native Method)
at java.io.RandomAccessFile.<init>(RandomAccessFile.java:216)
at org.apache.hadoop.hdfs.server.datanode.FSDataset.getBlockInputStream(FSDataset.java:1068)

做好准备

要解决此问题,您需要在群集的每个节点上拥有 root 权限。 我们假设您使用 hadoop用户启动 HDFS 集群。

怎么做……

要修复“打开的文件太多”错误,请在群集的每个节点上执行以下步骤:

  1. 通过向 /etc/security/limits.conf文件添加以下属性来增加 hadoop用户的打开文件号:

    $ vi /etc/security/limits.conf
    hadoop soft nofile 65535
    hadoop hard nofile 65535
    
    
  2. 将以下行添加到 /etc/pam.d/login文件:

    $ vi /etc/pam.d/login
    session required pam_limits.so
    
    
  3. 注销,然后以 hadoop用户身份重新登录。

  4. 通过运行以下命令确认已提高打开文件限制:

    $ ulimit -n
    65535
    
    
  5. 从主节点重新启动 HDFS 和 HBase:

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

它是如何工作的.

HBase 是一个运行在 Hadoop 上的数据库,就像其他数据库一样,它可以同时打开许多文件。 另一方面,Linux 限制了进程可以打开的文件描述符的数量。 默认限制为 1024,对于 HBase 来说太小了。 如果 hadoop用户的打开文件数超过这个上限,您将在 DataNode 日志中看到一个错误,指示打开的文件太多。

要平稳运行 HBase,您需要为运行 HDFS 和 HBase 的用户增加打开文件描述符的最大数量;在我们的示例中,即 hadoop用户。

在步骤 1 中,我们在 /etc/security/limits.conf文件中设置了 hadoop用户的打开文件限制,然后在步骤 2 中修改了 /etc/pam.d/login文件,以便更改在用户下次登录系统时生效。 软限制(软 nofile))是操作系统内核对打开的文件描述符的数量强制执行的值。 硬限制(HARD nofile))用作软限制的上限。 ulimit是一个为当前用户设置和显示系统范围内资源使用限制的程序。 正如您在步骤 4 中看到的, ulimit -n命令显示了 hadoop用户的打开文件限制。

不要忘记重新启动 HDFS 群集以应用更改。

还有更多...

您可能还想知道 hadoop用户当前打开的文件号。 要获取此信息,请使用 lsof命令:

$ lsof -uhadoop | wc -l

另请参阅

  • 更改内核设置配方,在第 1 章设置 HBase 群集
  • 在本章中,处理“无法创建新的本机线程”错误

处理“无法创建新的本机线程”错误

在本指南中,我们将介绍如何对以下 RegionServer 日志中显示的错误进行故障排除:

2012-02-18 18:46:04,907 WARN org.apache.hadoop.hdfs.DFSClient: DataStreamer Exception: java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:640)
at org.apache.hadoop.hdfs.DFSClient$DFSOutputStream$DataStreamer.run(DFSClient.java:2830)

做好准备

要修复此错误,您将需要群集的每个节点上的 root 权限。 我们假设您使用 hadoop用户启动 HDFS 和 HBase 集群。

怎么做……

要修复“无法创建新的本机线程”错误,请在群集的每个节点上执行以下步骤:

  1. 通过向 /etc/security/limits.conf文件添加以下属性来增加 hadoop用户的最大进程数:

    $ vi /etc/security/limits.conf
    hadoop soft nproc 32000
    hadoop hard nproc 32000
    
    
  2. 将以下行添加到 /etc/pam.d/login文件:

    $ vi /etc/pam.d/login
    session required pam_limits.so
    
    
  3. 注销,然后以 hadoop用户身份重新登录。

  4. 通过运行以下命令,确保已增加打开进程限制:

    $ ulimit -u
    32000
    
    
  5. 从主节点重新启动 HDFS 和 HBase:

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

它是如何工作的.

Linux 限制了用户可以同时执行的进程数量。 在高负载 HBase 集群中,较低的 nproc设置可能表现为 OutOfMemoryError异常,如前面所示。 如果 hadoop用户的运行进程数超过了 nproc限制,您将在 RegionServer 日志中看到“无法创建新的本机线程”的错误。

为了顺利运行 HBase,您需要提高运行 HDFS 和 HBase 的用户的 nproc限制。

我们在步骤 1 中在 /etc/security/limits.conf文件中设置了 hadoop用户的 nproc限制,然后在步骤 2 中修改了 /etc/pam.d/login文件,以便更改在用户下次登录系统时生效。 软限制(软 nproc)是操作系统内核对进程数强制执行的值。 硬限制(HARD nproc))用作软限制的上限。

注销后,我们再次登录并运行 ulimit -u程序,以显示当前用户的进程数限制。

最后,我们重新启动 HDFS 和 HBase 以应用更改。

还有更多...

要查看 hadoop用户的当前线程数,请键入以下命令:

$ ps -o pid,comm,user,thcount -u hadoop
PID COMMAND USER THCNT
1349 su hadoop 1
1350 bash hadoop 1
1429 java hadoop 32
1580 java hadoop 14
1690 java hadoop 48
1819 ps hadoop 1

输出的 THCNT列是 hadoop用户的每个进程的线程号。

另请参阅

  • 更改内核设置第 1 章设置 HBase 群集
  • 在本章中,处理“打开的文件太多”错误

处理“HBase 忽略 HDFS 客户端配置”问题

您可能已经注意到,HBase 会忽略您的 HDFS 客户端配置,例如 dfs.replication设置。 在以下示例中,我们为 HDFS 客户端设置了复制因子 2

$ grep -A 1 "dfs.replication" $HADOOP_HOME/conf/hdfs-site.xml
<name>dfs.replication</name>
<value>2</value>

但是,HDFS 上的 HBase 文件显示的因子为 3,这是 HDFS 的默认复制因子:

Handling the "HBase ignores HDFS client configuration" issue

这与我们预期的不同-复制因子预期为 2,但实际值为 3。

在本食谱中,我们将描述为什么会发生这种情况,以及如何修复它。

做好准备

以启动 HDFS 和 HBase 的用户身份登录到您的主节点。 我们假设您使用的是 HDFS 和 HBase 的 hadoop用户。

怎么做……

以下是将 HDFS 客户端配置应用到 HBase 的步骤:

  1. 在 HBase 配置目录下添加 HDFS 设置文件(hdfs-site.xml)的符号链接:

    $ hadoop@master1$ ln -s $HADOOP_HOME/conf/hdfs-site.xml $HBASE_HOME/conf/hdfs-site.xml
    
    
  2. 在群集中同步此更改:

    hadoop@master1$ for slave in `cat $HBASE_HOME/conf/regionservers`
    do
    rsync -avz $HBASE_HOME/conf/ $slave:$HBASE_HOME/conf/
    done
    
    
  3. 重新启动 HBase。 现在,新创建的 HBase 文件将具有 HDFS 客户端配置。 实际复制系数值将为 2,正如我们预期的那样:

    hadoop@master1$ $HBASE_HOME/bin/stop-hbase.sh
    hadoop@master1$ $HBASE_HOME/bin/start-hbase.sh
    
    

它是如何工作的.

HDFS 客户端配置在 hdfs-site.xml文件中设置。 要将 HDFS 客户端配置应用到 HBase,我们需要添加此文件中的设置,并将 hdfs-site.xml文件添加到 HBase 的类路径中。 要做到这一点,最简单的方法是在 HBase 配置目录下创建 hdfs-site.xml文件的符号链接,然后在整个集群中同步它。

重新启动 HBase 后,您将注意到将应用 HDFS 客户端配置。

处理 ZooKeeper 客户端连接错误

在本食谱中,我们将介绍如何对以下 RegionServer 日志中显示的 ZooKeeper 客户端连接错误进行故障排除:

2012-02-19 15:17:06,199 WARN org.apache.zookeeper.ClientCnxn: Session 0x0 for server ip-10-168-47-220.us-west-1.compute.internal /10.168.47.220:2181, unexpected error, closing socket connection and attempting reconnect
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:166) at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:243) at org.apache.zookeeper.ClientCnxnSocketNIO.doIO(ClientCnxnSocketNIO.java:66)

做好准备

登录到您的动物园管理员仲裁节点。

怎么做……

以下是修复 ZooKeeper 客户端连接错误的步骤:

  1. 将以下内容添加到每个 ZooKeeper 仲裁节点上的 ZooKeeper 配置文件(zoo.cfg)中:

    $ vi $ZOOKEEPER_HOME/conf/zoo.cfg
    maxClientCnxns=60
    
    
  2. 重新启动 ZooKeeper 以应用更改:

    $ $ZOOKEEPER_HOME/bin/zkServer.sh stop
    $ $ZOOKEEPER_HOME/bin/zkServer.sh start
    
    

它是如何工作的.

在 HBase 群集上运行 MapReduce 作业时,通常会出现此错误。 ZooKeeper 有一个 maxClientCnxns设置,用于限制单个客户端可以与 ZooKeeper 集合中的单个成员建立的并发连接的数量。 每个区域服务器都是 ZooKeeper 客户端;如果区域服务器的并发连接数超过此最大客户端连接限制,它将无法创建到 ZooKeeper 集合的新连接。 这就是发生上述错误的原因。

要修复此错误,我们需要为 maxClientCnxns设置设置一个更高的值,并重新启动 ZooKeeper 以应用更改。

还有更多...

从 ZooKeeper 3.4.0 开始, maxClientCnxns的默认值已更改为 60。 它应该可以很好地适用于许多应用。

要查看从客户端到特定 ZooKeeper 仲裁节点的当前连接数,请运行以下命令:

$ echo "cons" | nc localhost 2181 | grep "your.client.ip.address" | wc l

localhost替换为 ZooKeeper 仲裁节点的主机名,并将"your.client.ip.address"替换为客户端的 IP 地址。

处理 ZooKeeper 会话过期错误

在本食谱中,我们将介绍如何对 RegionServer 日志中显示的以下 ZooKeeper 会话过期错误进行故障排除:

2012-02-19 16:49:15,405 WARN org.apache.hadoop.hbase.regionserver.HRegionServer: Failed deleting my ephemeral node
org.apache.zookeeper.KeeperException$SessionExpiredException: KeeperErrorCode = Session expired for /hbase/rs/ip-10-168-37-91.us-west-1.compute.internal,60020,1329635463251 at org.apache.zookeeper.KeeperException.create(KeeperException.java:127) at org.apache.zookeeper.KeeperException.create(KeeperException.java:51) at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:868) at org.apache.hadoop.hbase.zookeeper.RecoverableZooKeeper.delete(RecoverableZooKeeper.java:107)

这个问题非常重要,因为如果主服务器或区域服务器与 ZooKeeper 仲裁断开连接,它们将自动关闭。

做好准备

登录到发生此错误的服务器。

怎么做……

以下是修复 ZooKeeper 会话过期问题的步骤:

  1. 检查 hbase-env.sh;确保为 HBase 守护进程提供足够的 RAM。 对于繁重的群集,默认的 1 GB 是不够的:

    $ vi $HBASE_HOME/conf/hbase-env.sh
    export HBASE_HEAPSIZE=8000
    
    
  2. Run the vmstat command to show the virtual memory statistics:

    $ vmstat 1
    
    

    How to do it...

    • 检查名为si(换入)和so(换出)的交换列,并确保不交换。
  3. Use the jstat command to show the Java Garbage Collection (GC) statistics:

    $ jstat -gcutil <java_process_pid> 1000
    
    

    How to do it...

    • 检查FGCT列,确保 RegionServer 不会遇到长时间的 GC 暂停。
  4. 使用 top命令显示 CPU 统计信息。 确保 RegionServer 线程有足够的 CPU 资源。 MapReduce 可能会使用大量 CPU 资源,导致 RegionServer 饥饿并陷入长时间的 GC 暂停。

  5. 如果 MapReduce 占用太多 CPU 资源,请考虑减少区域服务器上的 MAP/Reduce 插槽数量。 调整下列值:

    $ vi $HADOOP_HOME/conf/mapred-site.xml
    <property>
    <name>mapred.tasktracker.map.tasks.maximum</name>
    <value>2</value>
    </property>
    <property>
    <name>mapred.tasktracker.reduce.tasks.maximum</name>
    <value>1</value>
    </property>
    
    
  6. 考虑通过编辑 hbase-site.xml文件

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>zookeeper.session.timeout</name>
    <value>120000</value>
    </property>
    
    

    来增加 ZooKeeper 会话超时

  7. 增加每个 ZooKeeper 仲裁节点上的 ZooKeeper 最大会话超时。 修改 zoo.cfg文件以增加 maxSessionTimeout值:

    $ vi $ZOOKEEPER_HOME/conf/zoo.cfg
    maxSessionTimeout= 120000
    
    
  8. 如果您更改了 zoo.cfg文件

    $ $ZOOKEEPER_HOME/bin/zkServer.sh stop
    $ $ZOOKEEPER_HOME/bin/zkServer.sh start
    
    

    ,请在每个 ZooKeeper 仲裁节点上重新启动 ZooKeeper

  9. 跨群集中同步您修改的文件,然后重新启动 Hadoop/HBase 群集。

它是如何工作的.

此错误通常发生在过载的 HBase 群集上。 HBase Master 和 RegionServer 守护进程充当 ZooKeeper 客户端;如果客户端无法在配置的时间内与 ZooKeeper 仲裁进行通信,则连接将超时,并会出现此错误。

ZooKeeper 连接超时的两个最可能原因如下:

  • 长时间的 JVM GC 暂停
  • 配置的超时时间太短

从第 1 步到第 5 步,我们尝试找出是不是长 GC 导致了错误。 HBase 需要足够的 RAM 才能平稳运行。 对于繁重的群集,1 GB 的默认大小是不够的。 因此,将 hbase-env.sh文件中的 HBASE_HEAPSIZE值更改为更高的值(例如,8 GB),但小于 16 GB。 我们建议将 HBase 堆大小设置为小于 16 GB 的原因是,如果我们在这里使用非常大的堆大小,GC 将需要非常长的时间才能完成,这是我们必须避免的。

确保您的 RegionServer 不交换。 vmstat命令可用于查看是否发生了交换。 在步骤 2 中,我们使用 vmstat命令以 1 秒为间隔显示虚拟内存统计信息。 您应该更改 vm.swappiness内核设置,以避免交换。 我们将在第 8 章基本性能调优中的将 vm.swappness 设置为 0 以避免交换配方中进行描述。

jstat是一款 Java 虚拟机统计监控工具。 我们在步骤 3 中使用 -gcutil选项,以 1 秒为间隔显示特定 Java 进程的 Java GC 统计数据。 输出的FGCT列是总的完整 GC 时间;检查该列以查看是否发生了长时间的 GC 暂停。

GC 暂停时间长的另一个原因是 RegionServer 进程可能没有足够的 CPU 资源。 在 HBase 群集上运行繁重的 MapReduce 作业时尤其如此。 在这种情况下,如果您正在运行繁重的 MapReduce 作业,请使用 MapReduce 配置 mapred.tasktracker.map.tasks.maximummapred.tasktracker.reduce.tasks.maximum来控制在 TaskTracker 上同时生成的贴图/缩减的数量。

如果希望增加 ZooKeeper 会话超时,请在 hbase-site.xml文件中设置 zookeeper.session.timeout,在 ZooKeeper 配置文件(zoo.cfg)中设置 maxSessionTimeoutmaxSessionTimeout选项是 ZooKeeper 服务器端配置。 它是客户端会话超时的上限,因此它的值必须大于 HBase zookeeper.session.timeout值。

备注

请注意,设置更高的超时意味着群集将至少花费同样多的时间来故障转移出现故障的 RegionServer。 您需要考虑您的系统是否可以接受。

另请参阅

  • 故障排除工具,在本章中
  • 将 vm.swappity 设置为 0 以避免交换配方,请参见第 8 章基本性能调整

处理 EC2 上的 HBase 启动错误

在本食谱中,我们将介绍如何对以下主日志中显示的 HBase 启动错误进行故障排除:

2011-12-10 14:04:57,422 ERROR org.apache.hadoop.hbase.HServerAddress: Could not resolve the DNS name of ip-10-166-219-206.us-west-1.compute.internal
2011-12-10 14:04:57,423 FATAL org.apache.hadoop.hbase.master.HMaster: Unhandled exception. Starting shutdown.
java.lang.IllegalArgumentException: hostname can't be null
at java.net.InetSocketAddress.<init>(InetSocketAddress.java:121)
at org.apache.hadoop.hbase.HServerAddress.getResolvedAddress(HServerAddress.java:108)
at org.apache.hadoop.hbase.HServerAddress.<init>(HServerAddress.java:64)

此错误通常发生在停止并重新启动 EC2 实例之后。 原因是 HBase 将区域位置存储在它的“系统”-根-和 META 表中。 位置信息中包含内部 EC2 DNS 名称。 停止 EC2 实例将更改此 DNS 名称。 由于 DNS 名称更改,HBase 将无法将其系统表中的旧 DNS 名称解析为新名称,从而导致上述错误消息。

在本指南中,我们将介绍如何解决此问题,以便您可以随意停止/启动用于 HBase 的 EC2 实例(如果您的 HDFS 将数据保存在 EC2 实例存储上,则在重新启动 EC2 实例之前,您需要将数据保存在其他存储上,如 Amazon S3)。

做好准备

确保您已经为 HBase 设置了 Amazon EC2 环境。 如果您还没有准备好,请参考第 1 章设置 HBase 集群中的在 Amazon EC2 上做好准备食谱,了解如何在 EC2 上做好准备以便在 EC2 上运行 HBase。

您需要在群集的每个节点上拥有 root 权限的帐户,才能运行我们稍后创建的脚本。

怎么做……

以下是修复 EC2 上的 HBase 启动错误的步骤:

  1. 创建一个 ec2-running-hosts.sh脚本,如以下代码片段所示:

    $ vi ec2-running-hosts.sh
    #!/bin/bash
    ec2-describe-instances > /tmp/all-instances
    ip_type=$1
    if [ "$ip_type" == "private" ]; then
    addresses=`grep ^INSTANCE /tmp/all-instances | cut -f18`
    elif [ "$ip_type" == "public" ]; then
    addresses=`grep ^INSTANCE /tmp/all-instances | cut -f17`
    else
    echo "Usage: `basename $0` private|public"
    exit -1
    fi
    for address in $addresses
    do
    instance_id=`grep $address /tmp/all-instances | cut -f2`
    dns=`grep $address /tmp/all-instances | cut -f5`
    host=`grep ^TAG /tmp/all-instances | grep $instance_id | cut -f5`
    echo -e "${address}\t${dns}\t${host}"
    done
    
    
  2. 创建一个 update-ec2-hosts.sh脚本,如以下代码片断所示。 将脚本中的 YOUR_PASSWORD替换为您用来运行脚本的帐户的密码:

    $ vi update-ec2-hosts.sh
    #!/bin/bash
    if [ $# -lt 2 ]; then
    echo "Usage: `basename $0` host-file target-host"
    exit 1
    fi
    host_file=$1
    target_host=$2
    bin=`dirname $0`
    bin=`cd $bin;pwd`
    echo "updating hosts file on $target_host using $host_file"
    # backup
    ssh -f $target_host "cp /etc/hosts /tmp/hosts.org"
    cp $bin/hosts.template /tmp/hosts.update
    cat $host_file >> /tmp/hosts.update
    scp /tmp/hosts.update $target_host:/tmp
    # chmod 700 this file as we write password here
    ssh -f -t -t -t $target_host "sudo -S cp /tmp/hosts.update /etc/hosts <<EOF
    YOUR_PASSWORD
    EOF
    "
    echo "[done] update hosts file on $target_host"
    
    
  3. 确保 update-ec2-hosts.sh脚本只对您可读:

    $ chmod 700 update-ec2-hosts.sh
    
    
  4. /etc/hosts文件从 EC2 实例复制到与 update-ec2-hosts.sh脚本相同的目录中,并将其重命名为 hosts.template:

    $ cp /etc/hosts hosts.template
    
    
  5. 创建包含以下内容的 update-ec2-private-hosts.sh脚本。 将 NS_HOSTNAME的值替换为您的名称服务器的主机名:

    $ vi update-ec2-private-hosts.sh
    #!/bin/bash
    bin=`dirname $0`
    bin=`cd $bin;pwd`
    NS_HOSTNAME="ns1"
    $bin/ec2-running-hosts.sh private | tee /tmp/ec2-running-host
    cp $bin/hosts.template /tmp/hosts.update
    cat /tmp/ec2-running-host >> /tmp/hosts.update
    while read line
    do
    host=`echo "$line" | cut -f3`
    if [ "$host" == "$NS_HOSTNAME" ]; then
    # do not update name server
    continue
    fi
    echo
    echo "Updating $host"
    bash $bin/update-ec2-hosts.sh "/tmp/ec2-running-host" "$host"
    sleep 1
    done < /tmp/ec2-running-host
    
    
  6. 创建一个 update-ec2-hmaster-hosts.sh脚本,如以下代码片断所示。 将 HMASTER_HOSTNAMERS_HOSTNAMES的值分别替换为 Master 和 RegionServer 节点的主机名:

    $ vi update-ec2-hmaster-hosts.sh
    #!/bin/bash
    bin=`dirname $0`
    bin=`cd $bin;pwd`
    IFS_BAK=$IFS
    IFS="
    "
    HMASTER_HOSTNAME="master1"
    RS_HOSTNAMES="slave[123]"
    scp $HMASTER_HOSTNAME:/tmp/hosts.org /tmp
    scp $HMASTER_HOSTNAME:/etc/hosts /tmp
    rm -f /tmp/hosts.done
    while read line
    do
    echo "$line" | egrep -q "$RS_HOSTNAMES"
    if [ $? -ne 0 ]; then
    echo -e "${line}" >> /tmp/hosts.done
    else
    slave=`echo "$line" | cut -f3`
    original_private_dns=`grep "$slave" /tmp/hosts.org | cut -f2`
    echo -e "${line}\t${original_private_dns}" >> /tmp/hosts.done
    fi
    done < /tmp/hosts
    bash $bin/update-ec2-hosts.sh "/tmp/hosts.done" "$HMASTER_HOSTNAME"
    
    
  7. 将前面步骤中创建的脚本带到 EC2 上的名称服务器,并从那里运行 update-ec2-private-hosts.sh文件:

    hac@ns1$ ./update-ec2-private-hosts.sh
    10.176.202.34 ip-10-176-202-34.us-west-1.compute.internal master1
    10.160.49.250 ip-10-160-49-250.us-west-1.compute.internal ns1
    10.160.47.194 ip-10-160-47-194.us-west-1.compute.internal slave1
    10.176.23.117 ip-10-176-23-117.us-west-1.compute.internal client1
    10.160.39.197 ip-10-160-39-197.us-west-1.compute.internal slave2
    10.168.41.175 ip-10-168-41-175.us-west-1.compute.internal slave3
    Updating master1
    updating hosts file on master1 using /tmp/ec2-running-host
    hosts.update 100% 657 0.6KB/s 00:00
    [done] update hosts file on master1
    [sudo] password for hac:
    Updating slave1
    ...
    
    
    • 脚本使用 EC2API 更新了每个节点上的/etc/hosts文件。
  8. 如果 update-ec2-private-hosts.sh已成功完成,则运行 update-ec2-hmaster-hosts.sh

    hac@ns1$ ./update-ec2-hmaster-hosts.sh
    hosts.org 100% 1457 1.4KB/s 00:00
    hosts 100% 657 0.6KB/s 00:00
    updating hosts file on master1 using /tmp/hosts.done
    hosts.update 100% 1058 1.0KB/s 00:00
    [done] update hosts file on master1
    
    
    • 每个从节点的旧 DNS 条目将被附加到主节点上的/etc/hosts 文件中。
  9. 运行以下命令检查主机文件:

    hac@master1$ grep "slave" /etc/hosts
    
    

以下屏幕截图显示了该命令的输出:

How to do it...

hadoop用户身份登录到您的主节点,并在此节点上启动 HBase:

hadoop@master1$ $HADOOP_HOME/bin/start-dfs.sh
starting namenode, logging to /usr/local/hadoop/logs/hadoop-hadoop-namenode-master1.out
slave1: Warning: Permanently added the RSA host key for IP address '10.160.47.194' to the list of known hosts.
slave2: Warning: Permanently added the RSA host key for IP address '10.160.39.197' to the list of known hosts.
slave3: Warning: Permanently added the RSA host key for IP address '10.168.41.175' to the list of known hosts.
slave1: starting datanode, logging to /usr/local/hadoop/logs/hadoop-hadoop-datanode-slave1.out
slave3: starting datanode, logging to /usr/local/hadoop/logs/hadoop-hadoop-datanode-slave3.out
slave2: starting datanode, logging to /usr/local/hadoop/logs/hadoop-hadoop-datanode-slave2.out
hadoop@master1$ $ZOOKEEPER_HOME/bin/zkServer.sh start
JMX enabled by default
Using config: /usr/local/zookeeper/current/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED
hadoop@master1$ $HBASE_HOME/bin/start-hbase.sh
starting master, logging to /usr/local/hbase/logs/hbase-hadoop-master-master1.out
slave3: starting regionserver, logging to /usr/local/hbase/logs/hbase-hadoop-regionserver-slave3.out
slave1: starting regionserver, logging to /usr/local/hbase/logs/hbase-hadoop-regionserver-slave1.out
slave2: starting regionserver, logging to /usr/local/hbase/logs/hbase-hadoop-regionserver-slave2.out

它是如何工作的.

作为一个简单的解决方案,每次在启动 HBase 之前停止和重新启动实例时,我们都会更新集群中每个节点上的 /etc/hosts文件,然后将每个从节点的旧 DNS 条目附加到主节点的 /etc/hosts文件中。 这样,HBase Master 就能够将其系统表中的旧 DNS 条目解析为更新后的新 IP 地址,并使用系统表中的新 DNS 条目对其进行更新。

为了实现这个想法,我们在步骤 1 中创建了 ec2-running-hosts.sh脚本,并调用 ec2-describe-instancesEC2API,以获取有关所有正在运行的 EC2 实例的信息,并对每个正在运行的实例的 IP 地址、DNS 名称和用户数据(主机名)进行 grep。 这些是我们需要更新到每个节点的 /etc/hosts文件的新 DNS 条目。

我们在步骤 2 中创建的 update-ec2-hosts.sh脚本是一个实用程序脚本,用于通过 SSH 连接到特定服务器并覆盖该服务器中的 /etc/hosts文件,而无需人工交互。 该脚本还会在覆盖原始 /etc/hosts文件之前对其进行备份。 我们在脚本中写入帐户密码,这样就不需要在每个节点上输入密码。 这就是为什么在步骤 3 中注意到脚本应该只对您自己可读的原因。

在步骤 5 中创建的 update-ec2-private-hosts.sh脚本使我们在前面步骤中创建的两个脚本协同工作以获得新的 DNS 条目,将它们附加到 hosts.template文件,然后使用 hosts.template文件覆盖除 DNS 名称服务器之外的每个运行的 EC2 实例上的 /etc/hosts文件。

在步骤 6 中创建的 update-ec2-hmaster-hosts.sh脚本执行简单的工作,即将每个从节点的旧 DNS 名称映射到新的 /etc/hosts文件,将新的 IP 地址附加到*,以便 HBase Master 可以将存储在其系统表中的旧 DNS 条目解析为新的 IP 地址。 之后,脚本调用 update-ec2-hosts.sh,使用新附加的文件覆盖主节点上的 /etc/hosts文件。*

在按顺序执行 update-ec2-private-hosts.shupdate-ec2-hmaster-hosts.sh脚本之后,我们现在就可以正常启动 HDFS 和 HBase 了。

如您所见,如果您只想在需要在 HBase 上运行 HBase 时启动 HBase EC2 实例,那么这些脚本是简单但有用的。

还有更多...

您可能还希望将 EC2 实例的公共 DNS 名称更新为本地 PC 上的 /etc/hosts文件,以便您可以从 Web 浏览器访问 HBase Web UI。 以下是执行此操作的步骤:

  1. 创建一个 update-ec2-public-hosts.sh脚本,如以下代码片断所示,并将其放在与我们在上一节中创建的脚本相同的目录下:

    $ vi update-ec2-public-hosts.sh
    #!/bin/bash
    bin=`dirname $0`
    bin=`cd $bin;pwd`
    $bin/ec2-running-hosts.sh public | tee /tmp/ec2-running-host
    # backup
    cp /etc/hosts /tmp/hosts.org
    # update
    cp $bin/hosts.local /tmp/hosts.update
    cat /tmp/ec2-running-host >> /tmp/hosts.update
    sudo cp /tmp/hosts.update /etc/hosts
    echo "[done] update EC2 public hostname in hosts file"
    
    
  2. 将您的 /etc/hosts文件复制到与 update-ec2-public-hosts.sh脚本相同的目录,并将其重命名为 hosts.local:

    $ cp /etc/hosts hosts.local
    
    
  3. 在本地 PC 上运行 update-ec2-public-hosts.sh脚本。 脚本将要求您输入帐户密码,因为它需要 root 权限才能更新 /etc/hosts文件:

    184.72.22.179 ip-10-176-202-34.us-west-1.compute.internal master1
    ...
    184.169.243.211 ip-10-168-41-175.us-west-1.compute.internal slave3
    Password:
    [done] update EC2 public hostname in hosts file
    
    
  4. 现在,打开您的 Web 浏览器并输入 URL http://master1:60010/master.jsp。 您将获得 HBase Web 用户界面。 将 master1替换为您的主节点的主机名。

备注

请注意,您需要一台 MacOSX 或 Linux PC 才能使用此脚本。

另请参阅

  • 为 Amazon EC2 做好准备,在第 1 章设置 HBase 群集

八、基本性能调整

在本章中,我们将介绍:

  • 设置 Hadoop 以分散磁盘 I/O
  • 使用网络拓扑脚本使 Hadoop 机架感知
  • 使用 noatimenodiratime挂载磁盘
  • vm.swappiness设置为 0 以避免交换
  • Java GC 和 HBase 堆设置
  • 使用压缩
  • 管理压缩
  • 管理区域拆分

简介

性能是 HBase 集群行为最有趣的特征之一。 这对管理员来说是一项具有挑战性的操作,因为性能调优不仅需要深入了解 HBase,还需要深入了解 Hadoop、**Java 虚拟机垃圾收集(JVM GC)**以及操作系统的重要调优参数。

典型的 HBase 集群结构如下图所示:

Introduction

集群中有几个组件-ZooKeeper 集群、HBase 主节点、区域服务器、**Hadoop 分布式文件系统(HDFS)**和 HBase 客户端。

ZooKeeper 集群充当整个 HBase 集群的协调服务,处理主服务器选择、根区域服务器查找、节点注册等。 主节点不执行繁重的任务。 它的工作包括区域分配和故障转移、日志拆分和负载均衡。 区域服务器保存实际区域;它们处理对托管区域的 I/O 请求,将内存中的数据存储(MemStore)刷新到 HDFS,以及拆分和压缩区域。 HDFS 是 HBase 存储其数据文件(StoreFile)和预写日志(WAL)的地方。 我们通常有一个 HBase 区域服务器与 HDFS DataNode 运行在同一台机器上,但这不是强制性的。

HBase 客户端提供访问 HBase 集群的 API。 要与集群通信,客户端需要找到持有特定行键范围的区域服务器;这称为区域查找。 HBase 有两个系统表来支持区域查找- -ROOT-表和 .META表。 桌子。

-ROOT-表用于引用 .META中的区域。 表,而 .META。 该表包含对所有用户区域的引用。 首先,客户端查询 ZooKeeper 以找到 -ROOT-表位置(部署它的区域服务器);然后查询 -ROOT-表,然后查询 .META。 表中,查找包含特定区域的区域服务器。 客户端还缓存区域位置,以避免查询 ZooKeeper、 -ROOT-.META。 每次都有桌子。

有了这些背景知识,我们将在本章描述如何调优 HBase 以获得更好的性能。

除了 HBase 本身,其他调优点包括 Hadoop 配置、JVM 垃圾收集设置和操作系统内核参数。 这些与调整 HBase 本身一样重要。 在本章中,我们还将介绍调整这些配置的方法。

设置 Hadoop 以分散磁盘 I/O

现代服务器通常具有多个磁盘设备以提供大存储容量。 这些磁盘通常按照出厂设置配置为 RAID 阵列。 这对许多情况都有好处,但对 Hadoop 不好。

Hadoop 从节点在其本地磁盘上存储 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):

    hadoop@master1$ vi $HADOOP_HOME/conf/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
    
    

它是如何工作的.

我们推荐将 JBOD 或 RAID0 用于 DataNode 磁盘,因为您不需要 RAID 的冗余,因为 HDFS 使用节点之间的复制来确保其数据冗余。 因此,单个磁盘发生故障时不会丢失数据。

选择 JBOD 还是 RAID0? 从理论上讲,JBOD 配置比 RAID 配置具有更好的性能。 这是因为,在 RAID 配置中,您必须等待阵列中最慢的磁盘完成,然后才能完成整个写入操作,这使得平均 I/O 时间相当于最慢的磁盘的 I/O 时间。 在 JBOD 配置中,速度较快的磁盘上的操作将独立于速度较慢的磁盘完成,这使得平均 I/O 时间比速度最慢的磁盘快。 然而,企业级 RAID 卡可能会带来很大的不同。 在决定使用哪种配置之前,您可能希望对 JBOD 和 RAID0 配置进行基准测试。

对于 JBOD 和 RAID0 配置,您将在不同的路径挂载磁盘。 这里的关键点是将 dfs.data.dir属性设置为每个磁盘上创建的所有目录。 dfs.data.dir属性指定 DataNode 应将其本地块存储在何处。 通过将其设置为逗号分隔的多个目录,DataNode 以循环方式跨所有磁盘存储数据块。 这会使 Hadoop 有效地将磁盘 I/O 分散到所有磁盘。

提示

警告

不要在 dfs.data.dir属性值的目录路径之间留空,否则它不会按预期工作。

您需要在整个群集中同步更改并重新启动 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 机架感知,以及为什么它很重要。

做好准备

您需要知道每个从节点所属的机架。 以启动 Hadoop 的用户身份登录到主节点。

怎么做……

以下步骤介绍如何使 Hadoop 机架感知:

  1. Create a topology.sh script and store it under the Hadoop configuration directory. Change the path for topology.data, in line 3, to fit your environment:

    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
    
    

    别忘了设置脚本文件的 EXECUTE 权限:

    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 核心配置文件(core-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 机架感知的概念:

How it works...

HDFS 文件的每个数据块将被复制到多个 DataNode,以防止因一台机器故障而丢失所有数据副本。 但是,如果数据的所有副本恰好在同一机架中的 DataNode 上复制,并且该机架出现故障,则所有数据副本都将丢失。 因此,为了避免这种情况,NameNode 需要知道网络拓扑,以便使用该信息进行智能数据复制。

如上图所示,在默认复制系数为 3 的情况下,两个数据副本将放置在同一机架中的计算机上,另一个数据副本将放置在不同机架中的计算机上。 这确保了单个机架故障不会导致丢失所有数据副本。

通常,与不同机架中的两台机器相比,同一机架中的两台机器之间具有更高的带宽和更低的延迟。 有了网络拓扑信息,Hadoop 能够通过从适当的 DataNode 读取数据来最大化网络性能。 如果本地机器上有数据可用,Hadoop 将从其中读取数据。 如果不是,Hadoop 将尝试从同一机架中的机器读取数据,如果两者都不可用,则将从不同机架中的机器读取数据。

在步骤 1 中,我们创建一个 topology.sh脚本。 该脚本将 DNS 名称作为参数,并返回网络拓扑(Rack)名称作为输出。 DNS 名称到网络拓扑的映射由步骤 2 中创建的 topology.data文件提供。如果在 topology.data文件中未找到条目,脚本将返回 /default/rack作为默认机架名称。

备注

请注意,我们在 topology.data文件中使用的是 IP 地址,而不是主机名。 有一个已知缺陷,即 Hadoop 无法正确处理以字母“a”到“f”开头的主机名。 有关更多详细信息,请查看 HADOOP-6682:issues.apache.org/jira/browse…

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

重新启动 Hadoop 后,如步骤 5 和 6 中的日志所示,HDFS 和 MapReduce 会将正确的机架名称作为前缀添加到从节点的 DNS 名称中。 这表明 HDFS 和 MapReduce 机架感知与上述设置配合良好。

带记号和记号的安装盘

如果您纯粹为 Hadoop 挂载磁盘,并且使用 ext3 或 ext4,或者 XFS 文件系统,我们建议您使用 noatimenodiratime属性挂载磁盘。

如果将磁盘挂载为 noatime,则在文件系统上读取文件时不会更新访问时间戳。 在属性为 nodiratime的情况下,挂载磁盘不会更新文件系统上的目录信息节点访问时间。 由于不再有用于更新访问时间戳的磁盘 I/O,这加快了文件系统的读取速度。

在本指南中,我们将介绍为什么推荐 Hadoop 使用 noatimenodiratime选项,以及如何使用 noatimenodiratime挂载磁盘。

做好准备

您需要在从节点上拥有 root 权限。 我们假设您有两个仅用于 Hadoop 的磁盘-/dev/xvdc 和 /dev/xvdd。 这两个磁盘分别安装在 /mnt/is1/mnt/is2。 此外,我们还假设您使用的是 ext3 文件系统。

怎么做……

要使用 noatimenodiratime挂载磁盘,请在集群中的每个从节点上执行以下指令:

  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 的本地文件系统中的访问时间戳在这里没有任何意义。 这就是为什么我们建议您使用 noatimenodiratime挂载磁盘,如果这些磁盘仅用于 Hadoop。 使用 noatimenodiratime挂载磁盘可在每次访问本地文件时节省写入 I/O。

这些选项在 /etc/fstab文件中设置。 要应用更改,请不要忘记再次卸载并挂载磁盘。

启用这些选项后,HDFS 读取的性能有望提高。 由于 HBase 将数据存储在 HDFS 上,因此 HBase 的读取性能也有望提高。

还有更多...

另一种优化方法是降低 ext3 或 ext4 文件系统保留块的百分比。 默认情况下,某些文件系统块保留供特权进程使用。 这是为了避免用户进程为了继续工作而填满系统守护进程所需的磁盘空间的情况。 这对于托管操作系统的磁盘非常重要,但对于仅由 Hadoop 使用的磁盘用处较小。

通常,这些仅用于 Hadoop 的磁盘具有非常大的存储空间。 降低保留块的百分比可以向 HDFS 群集添加相当多的存储容量。 通常,保留块的默认百分比为 5%。 可以降到 1%。

提示

警告:

不要减少托管操作系统的磁盘上的保留块。

为此,请在集群中每个从节点的每个磁盘上运行以下命令:

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

将 vm.swappness 设置为 0 以避免交换

Linux 将一段时间内未访问的内存页移动到交换空间,即使有足够的空闲内存可用。 这就是所谓的换出。 另一方面,将交换出的数据从交换空间读出到内存称为换入。 交换在许多情况下是必要的,但由于**Java 虚拟机(JVM)**在交换下表现不佳,如果交换,HBase 可能会遇到麻烦。 ZooKeeper 会话到期是交换可能导致的典型问题。

在本食谱中,我们将介绍如何调优 Linux vm.swappiness参数以避免交换。

做好准备

确保您在群集中的节点上具有 root 权限。

怎么做……。 要调优 Linux 参数以避免交换,请在集群中的每个节点上调用以下命令:

  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参数可用于定义内存页面交换到磁盘的积极程度。 它接受 0100—a之间的任何值。较低的值意味着内核不太可能交换应用,而较高的值将使内核更频繁地换出应用。 默认值为 60

我们在步骤 1 中将 vm.swappiness设置为 0,这将导致内核尽可能长时间地避免将进程换出物理内存。 这对 HBase 有好处,因为 HBase 进程消耗大量内存。 较高的 vm.swappiness值将使 HBase 交换很多,并遇到非常慢的垃圾收集。 随着 ZooKeeper 会话超时,这可能会导致 RegionServer 进程被终止。 我们建议您将其设置为 0或任何其他较小的数字(例如, 10)),并观察交换状态。

请注意,由 sysctl命令设置的值仅在服务器下一次重新启动之前有效。 您需要在 /etc/sysctl.conf文件中设置 vm.swappiness,以便在系统重新启动时启用该设置。

另请参阅

  • 更改内核设置配方,第 1 章设置 HBase 群集

Java GC 和 HBase 堆设置

由于 HBase 在 JVM 中运行,JVM**垃圾收集(GC)**设置对于 HBase 平稳、高性能运行非常重要。 除了配置 HBase 堆设置的一般指导原则之外,让 HBase 进程输出其 GC 日志,然后根据 GC 日志的输出调优 JVM 设置也很重要。

在本食谱中,我们将描述最重要的 HBase JVM 堆设置,以及如何启用和理解 GC 日志记录。 我们还将介绍一些针对 HBase 调优 Java GC 设置的一般指导原则。

做好准备

登录您的 HBase 区域服务器。

怎么做……

以下是推荐的 Java GC 和 HBase 堆设置:

  1. 通过编辑 hbase-env.sh文件为 HBase 提供足够的堆大小。 例如,以下代码片段为 HBase 配置 8000 MB 的堆大小:

    $ vi $HBASE_HOME/conf/hbase-env.sh
    export HBASE_HEAPSIZE=8000
    
    
  2. 使用以下命令启用 GC 日志记录:

    $ vi $HBASE_HOME/conf/hbase-env.sh
    export HBASE_OPTS="$HBASE_OPTS -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/local/hbase/logs/gc-hbase.log"
    
    
  3. 添加以下代码以早于默认值启动并发标记扫描 GC(CMS)

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

  5. Check that the GC logs were output to the specified log file (/usr/local/hbase/logs/gc-hbase.log).

    GC 日志如下所示:

How to do it...

它是如何工作的.

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

在步骤 2 中,我们启用 JVM 日志记录。 使用该设置,您将获得区域服务器的 JVM 日志,类似于我们在步骤 5 中显示的内容。理解日志输出需要有关 JVM 内存分配和垃圾收集的基本知识。 以下是 JVM 分代垃圾收集系统的示意图:

How it works...

有三个堆世代:Perm(或永久)世代、老世代(或永久)世代和年轻世代。 年轻一代部分由三个独立的空间组成:伊甸园空间和两个幸存者空间S0S1

通常,对象分配在年轻一代的Eden空间中。 如果分配失败(Eden已满),所有 Java 线程都会暂停,并调用年轻一代 GC(Minor GC)。 年轻一代(EdenS0空间)中的所有幸存对象都被复制到S1空间。 如果s1空间已满,则会将对象复制(升级)到旧版本。 升级失败时收集老一代(主要/完整 GC)。 永久世代和老世代通常聚集在一起。 永久生成用于保存对象的类和方法定义。

回到步骤 5 中的示例,上述选项的次要 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>是次要集合中使用的收集器的内部名称。
  • <starting occupancy1>是收藏前年轻一代的占有率。
  • <ending occupancy1>是收集后年轻一代的占有率。
  • <pause time1>是次要收集的暂停时间(秒)。
  • <starting occupancy3>是集合之前整个堆的占用率。
  • <ending occupancy3>是集合之后整个堆的占用率。
  • <pause time3>是整个垃圾收集的暂停时间。 这将包括一次大型收藏的时间。
  • [Time:]说明 GC 收集所花费的时间、用户时间、系统时间和实时。

步骤 5 中输出的第一行指示一个较小的 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 没有暂停。

在以 1441.435: [GC[YG occupancy:开始的行处有一个停顿...]。 在前面 GC 日志的屏幕截图中。 此处的停顿时间是 0.0413960 秒以重新标记堆。 在那之后,你可以看到清扫开始了。 Cms 扫描花了 3.446 秒,但是堆大小在这里没有太大变化(它一直占用大约 150MB)。

这里的调谐点是将所有这些停顿保持在较低的水平。 为了保持较低的暂停时间,您可能需要通过-XX:NewSize 和 -XX:MaxNewSizeJVM 标志调整年轻一代空间的大小,以便将它们设置为相对较小的值(例如,高达几百 MB)。 如果服务器的 CPU 能力更强,我们建议您通过设置 -XX:+UseParNewGC选项来使用并行新收集器。 您可能还希望通过 -XX:ParallelGCThreadsJVM 标志调优年轻一代的并行 GC 线程数。

我们建议将上述设置添加到 HBASE_REGIONSERVER_OPTS变量,而不是 hbase-env.sh文件中的 HBASE_OPTS变量。 HBASE_REGIONSERVER_OPTS变量只影响区域服务器进程,这很好,因为 HBase 主服务器既不处理繁重的任务,也不参与数据进程。

对于老一辈人来说,并发收集(CMS)一般不会加速,但可以更早开始。 当旧一代中分配的空间百分比超过阈值时,CMS 开始运行。 此阈值由收集器自动计算。 对于某些情况,特别是在加载期间,如果 CMS 启动太晚,HBase 可能会运行完整的垃圾回收。 为了避免这种情况,我们建议设置 -XX:CMSInitiatingOccupancyFractionJVM 标志,以明确指定 CMS 的启动百分比,就像我们在步骤 3 中所做的那样。从 60%或 70%开始是一种良好的做法。 当对老一代使用 CMS 时,默认的年轻一代 GC 将被设置为并行新收集器。

还有更多...

如果您使用的是 0.92 之前的 HBase 版本,请考虑启用 MemStore-Local 分配缓冲区,以防止在繁重的写入负载下出现老式堆碎片:

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

此功能在 HBase 0.92 中默认启用。

另请参阅

  • 设置 Ganglia 以监视 HBase 群集配方,请参见第 5 章监视和诊断

使用压缩

HBase 最重要的特性之一是使用数据压缩。 这很重要,因为:

  • 压缩减少了写入 HDFS/从 HDFS 读取的字节数
  • 节省磁盘使用量
  • 提高从远程服务器获取数据时的网络带宽效率

HBase 支持 GZip 和 LZO 编解码器。 我们建议使用 LZO 压缩算法,因为它数据解压速度快,CPU 使用率低。 由于系统首选较好的压缩比,您应该考虑 GZip。

不幸的是,由于许可证问题,HBase 不能与 LZO 一起发布。 HBase 是 Apache 许可的,而 LZO 是 GPL 许可的。 因此,我们需要自己安装 LZO。 我们将使用 hadoop-lzo 库,它为 Hadoop 带来了可拆分的 LZO 压缩。

在本指南中,我们将介绍如何安装 LZO 以及如何配置 HBase 以使用 LZO 压缩。

做好准备

确保要在其上构建 Hadoop-lzo 的计算机上安装了 Java。

从源代码构建 Hadoop-lzo 需要 Apache Ant。 通过运行以下命令安装 Ant:

$ sudo apt-get -y install ant

群集中的所有节点都需要安装本机 LZO 库。 您可以使用以下命令进行安装:

$ sudo apt-get -y install liblzo2-dev

怎么做……

我们将使用 Hadoop-lzo 库将 LZO 压缩支持添加到 HBase:

  1. github.com/toddlipcon/…获取最新的 hadoop-lzo 源代码。

  2. 从源代码构建本机和 Java Hadoop-lzo 库。 根据您的操作系统,您应该选择构建 32 位或 64 位二进制文件。 例如,要构建 32 位二进制文件,请运行以下命令:

    $ export JAVA_HOME="/usr/local/jdk1.6"
    $ export CFLAGS="-m32"
    $ export CXXFLAGS="-m32"
    $ cd hadoop-lzo
    $ ant compile-native
    $ ant jar
    
    
    • 这些命令将创建 hadoop-lzo/build/ative 目录和 hadoop-lzo/build/hadoop-lzo-x.y.z.jar 文件。 要构建 64 位二进制文件,只需将 CFLAGS 和 CXXFLAGS 的值更改为-m64 即可。
  3. 将构建的库复制到主节点上的 $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 位操作系统,将 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 附带了一个测试压缩设置是否正确的工具。 使用此工具测试群集的每个节点上的 LZO 设置。 如果一切配置正确,您将得到 SUCCESS输出:

    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
    
    

它是如何工作的.

我们构建了 Hadoop-lzo Java 和本地库,并将它们分别安装在 $HBASE_HOME/lib$HBASE_HOME/lib/native目录下。 通过添加 LZO 压缩支持,HBase StoreFiles(HFiles)将在写入数据块时使用 LZO 压缩。 HBase 使用本机 LZO 库执行压缩,而本机库由 HBase 通过我们构建的 Hadoop-lzo Java 库加载。

为了避免在启动节点时丢失或安装错误的编解码器,我们将 LZO 添加到 hbase-site.xml文件的 hbase.regionserver.codecs设置中。 如果 LZO 安装不正确,此设置将导致区域服务器启动失败。 如果您看到诸如“无法加载本机 GPL 库”之类的日志,则说明 LZO 安装有问题。 为了修复它,请确保安装了本机 LZO 库并正确配置了路径。

压缩算法是以每个列族为基础指定的。 如步骤 7 所示,我们创建了一个表 t1,其中包含一个使用 LZO 压缩的列族 cf1

虽然 LZO 增加了读取时间的损失,因为数据块在读取时可能会被解压缩,但作为实时压缩库,LZO 的速度已经足够快了。 我们建议使用 LZO 作为生产 HBase 中的默认压缩算法。

还有更多...

另一个压缩选项是使用最近发布的 Snappy 压缩库和 Hadoop Snappy 集成。 由于设置与我们之前所做的基本相同,因此我们将跳过细节。 查看以下 URL,了解如何将快速压缩添加到 HBase:

http://hbase.apache.org/book.html#snappy.compression

管理压缩

HBase 表具有以下物理存储结构:

Managing compactions

它由多个区域组成。 虽然一个区域可能有多个商店,但每个商店都有一个柱族。 编辑首先写入宿主区域存储区的内存空间,称为 MemStore。 当 MemStore 的大小达到阈值时,它会刷新到 HDFS 上的 StoreFiles。

随着数据量的增加,HDFS 上可能会有很多 StoreFiles,这不利于其性能。 因此,HBase 会自动选取几个较小的 StoreFiles,并将它们重写为较大的 StoreFiles。 这一过程被称为次要压实。 对于某些情况,或者当由配置的时间间隔触发时(默认情况下为每天一次),主要压缩会自动运行。 重大压缩将丢弃已删除或过期的单元格,并将 Store 中的所有 StoreFiles 重写为单个 StoreFile;这通常会提高性能。

但是,由于主要压缩会重写存储区的所有数据,因此在此过程中可能会发生大量磁盘 I/O 和网络流量。 这在重载系统上是不可接受的。 您可能希望在较低的系统加载时间运行它。

在本食谱中,我们将介绍如何关闭此自动主要压缩功能,并手动运行它。

做好准备

以启动群集的用户身份登录到您的 HBase 主服务器。

怎么做……

以下步骤介绍如何禁用自动主要压缩:

  1. 将以下内容添加到 hbase-site.xml文件:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.hregion.majorcompaction</name>
    <value>0</value>
    </property>
    
    
  2. 跨群集中同步更改并重新启动 HBase。

  3. 使用前述设置,将禁用自动主要压缩;您现在需要显式运行它。

  4. 要通过 HBase Shell 在特定区域手动运行主要压缩,请运行以下命令:

    $ echo "major_compact '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
    major_compact 'hly_temp,,1327118470453.5ef67f6d2a792fb0bd737863dc00b6a7.'
    0 row(s) in 1.7070 seconds
    
    

它是如何工作的.

hbase.hregion.majorcompaction属性指定一个区域中所有 StoreFiles 的主要压缩之间的时间间隔(以毫秒为单位)。 默认值为 86400000,表示一天一次。 我们在步骤 1 中将其设置为 0,以禁用自动主要压缩。 这将防止在高负载期间运行主要压缩,例如,当 MapReduce 作业在 HBase 集群上运行时。

另一方面,为了提高性能,需要进行较大的压实。 在步骤 4 中,我们展示了如何通过 HBase Shell 在特定区域手动触发重大压缩的示例。 在本例中,我们向 major_compact命令传递了一个区域名称,以便仅在单个区域上调用主要压缩。 通过将表名传递给命令,还可以对表的所有区域运行主要压缩。 major_compact命令将指定的表或区域排队以进行主要压缩;这将由托管它们的区域服务器在后台执行。

正如我们前面提到的,您可能只想在较低的加载时间内手动执行主要压缩。 这可以通过从 cron 作业调用 major_compact命令轻松完成。

还有更多...

调用主要压缩的另一种方法是使用 org.apache.hadoop.hbase.client.HBaseAdmin类提供的 majorCompactAPI。 在 Java 中很容易调用此 API,因此您可以从 Java 管理复杂的主要压缩调度。

管理区域拆分

通常,HBase 表从单个区域开始。 但是,随着数据的不断增长和区域达到其配置的最大大小,它会自动分为两部分,以便它们可以处理更多数据。 下图显示了 HBase 区域拆分:

Managing a region split

这是 HBase 区域拆分的默认行为。 这种机制在许多情况下都工作得很好,但也有遇到问题的情况,例如分裂/压实风暴问题。

由于数据分布和增长大致一致,最终表中的所有区域都需要同时拆分。 拆分后,将立即对子区域运行压缩,以将其数据重写到单独的文件中。 这会导致大量的磁盘 I/O 和网络流量。

为了避免这种情况,您可以关闭自动拆分并手动调用它。 由于您可以控制何时调用拆分,因此有助于分散 I/O 负载。 另一个优点是,手动拆分可以让您更好地控制区域,从而帮助您跟踪和修复与区域相关的问题。

在本菜谱中,我们将介绍如何关闭自动区域拆分并手动调用它。

做好准备

以启动群集的用户身份登录到您的 HBase 主服务器。

怎么做……

要关闭自动区域拆分并手动调用它,请执行以下步骤:

  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. 使用上述设置,在区域大小达到配置的 100 GB 阈值之前,不会发生区域拆分。 您需要在选定的区域显式触发它。

  4. 要运行通过 HBase Shell 拆分的区域,请使用以下命令:

    $ 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属性以字节指定最大区域大小。 默认情况下,该值为 1 GB(对于 HBase 0.92 之前的版本,为 256MB),这意味着当一个区域超过此大小时,它将被一分为二。 在步骤 1 中,我们将此最大区域大小设置为 100 GB,这是一个非常高的数字。

由于在区域达到 100 GB 上限之前不会发生拆分,因此我们需要显式调用它。 在步骤 4 中,我们使用 split命令通过 HBase Shell 调用指定区域的拆分。

别忘了分割大区域。 区域是 HBase 中数据分发和平衡的基本单位。 区域应该分割成合适的大小,并在较低的加载时间。

另一方面,太多的分裂是不好的。 区域服务器上的区域太多会降低其性能。

您可能还希望在手动拆分区域后触发主要压缩和平衡。

还有更多...

我们之前设置的设置会导致整个群集的默认最大区域大小为 100 GB。 除了更改整个簇之外,还可以在创建表格时以列族为基础指定 MAX_FILESIZE属性:

$ hbase> create 't1', {NAME => 'cf1', MAX_FILESIZE => '107374182400'}

与主要压缩一样,您也可以使用 org.apache.hadoop.hbase.client.HBaseAdminJava 类提供的 splitAPI。

另请参阅

  • 使用自己的算法预制区域配方,第 9 章高级配置和调整

九、高级配置和调整

在本章中,我们将介绍:

  • 使用 YCSB 对 HBase 群集进行基准测试
  • 增加区域服务器处理程序计数
  • 使用您自己的算法预先创建区域
  • 避免写入密集型群集上的更新阻塞
  • 调整 MemStore 的内存大小
  • 针对低延迟系统的客户端调整
  • 为列族配置块缓存
  • 增加读取密集型群集上的数据块缓存大小
  • 客户端扫描仪设置
  • 调整数据块大小以提高寻道性能
  • 启用 Bloom Filter 以提高整体吞吐量

简介

这是关于性能调优的另一章。 在基本性能调优中,我们描述了一些调整 Hadoop、操作系统设置、Java 和 HBase 本身以提高 HBase 集群整体性能的方法。 这些都是对许多用例的一般性改进。 在本章中,我们将描述更多的“具体”配方;其中一些是针对写入繁重的集群,而另一些则旨在提高集群的读取性能。

在调优 HBase 集群之前,您需要知道其性能如何。 因此,我们将首先介绍如何使用**Yahoo! 云服务基准(YCSB)**用于测量(基准)HBase 集群的性能。

在第 2 章中的配方在将数据移动到 HBase之前预先创建区域中,我们介绍了如何使用 HBase 的 RegionSplitter实用程序创建包含预先创建的区域的表,以提高数据加载速度。 虽然 RegionSplitter默认情况下使用 MD5 编号边界预先创建区域,但对于行键不能表示为 MD5 编号的情况,我们需要使用其他拆分算法。 我们将描述一种预创建具有您想要指定的任何边界的区域的方法。

基本上有两种负载类型的 HBase 集群,写密集型集群和读密集型集群。 每种类型都有不同的调优选项。 许多调优选项在写性能和读性能之间进行权衡。 我们将有几个配方来描述如何调优 HBase 集群以获得更好的写入性能;同时,我们还将介绍调优读密集型 HBase 集群的配方。 这些方法包括服务器端配置调优、客户端设置和表模式选择。

不存在适用于所有情况的调优。 您需要仔细考虑系统的性能要求,并调整集群以获得读写性能之间的最佳平衡。

我们假设您对 HBase 架构有基本的了解,并且已经对您的集群进行了常规调优。 您可以参考基本性能调优了解 HBase 体系结构和基本 HBase 调优。

以 YCSB 为基准的 HBase 集群

测量 HBase 集群的性能或对集群进行基准测试与调整集群本身一样重要。 我们应该测量的 HBase 集群的性能特征至少包括以下内容:

  • 群集的总体吞吐量(每秒操作数)
  • 群集的平均延迟(每次操作的平均时间)
  • 最小延迟
  • 最大延迟
  • 操作延迟的分布

YCSB 是对 HBase 集群性能进行基准测试的一个很好的工具。 YCSB 支持并行运行可变负载测试,以评估系统的插入、更新、删除和读取性能。 因此,您可以使用 YCSB 对写密集型和读密集型 HBase 集群进行基准测试。 每次测试都可以配置要加载的记录数、要执行的操作、读写比例以及许多其他属性,因此可以很容易地使用 YCSB 来测试集群的不同负载场景。

YCSB 还可用于评估许多其他不同键值存储的性能。 YCSB 的一个常见用途是对多个系统进行基准测试,并比较它们的性能。

在本指南中,我们将介绍如何安装 YCSB,并使用它测试写密集型和读密集型 HBase 集群。

做好准备

启动您的 HBase 群集并登录到您的 HBase 客户端节点。

怎么做……

要使用 YCSB 对您的 HBase 集群进行基准测试,需要遵循以下步骤:

  1. 在您的 HBase 客户端节点上下载 YCSB 并解压缩:

    $ wget https://github.com/downloads/brianfrankcooper/YCSB/ycsb-0.1.4.tar.gz
    $ tar xfvz ycsb-0.1.4.tar.gz
    $ cd ycsb-0.1.4
    
    
  2. Add the HBase configuration file (hbase-site.xml) to YCSB HBase binding's classpath:

    $ rm hbase-binding/conf/hbase-site.xml
    $ ln -s $HBASE_HOME/conf/hbase-site.xml hbase-binding/conf/hbase-site.xml
    
    

    仅对于 HBase 0.92,将 HBase JAR 文件添加到 YCSB HBase 绑定的类路径中:

    $ cp $HBASE_HOME/hbase-0.92.0.jar hbase-binding/lib
    
    

    仅对于 HBase 0.92,将 ZooKeeper JAR 文件添加到 YCSB HBase 绑定的类路径中:

    $ cp $ZOOKEEPER_HOME/zookeeper-3.4.2.jar hbase-binding/lib
    
    
  3. 在 HBase 中创建测试表:

    $ $HBASE_HOME/bin/hbase shell
    hbase> create 'usertable', {NAME => 'f1', VERSIONS => '1', COMPRESSION => 'LZO'}
    
    
  4. 调用写入繁重基准:

    $ bin/ycsb load hbase -P workloads/workloada -p columnfamily=f1 -p recordcount=1000000 -p threadcount=4 -s | tee -a workloada.dat
    YCSB Client 0.1
    Command line: -db com.yahoo.ycsb.db.HBaseClient -P workloads/workloada -p columnfamily=f1 -p recordcount=1000000 -p threadcount=4 -s -load
    Loading workload...
    Starting test.
    0 sec: 0 operations;
    10 sec: 49028 operations; 4902.8 current ops/sec; [INSERT AverageLatency(us)=759.91]
    20 sec: 98060 operations; 4899.28 current ops/sec; [INSERT AverageLatency(us)=777.67]
    ...
    160 sec: 641498 operations; 0 current ops/sec;
    170 sec: 641498 operations; 0 current ops/sec;
    180 sec: 682358 operations; 4086 current ops/sec; [INSERT AverageLatency(us)=2850.51]
    ...
    240 sec: 1000000 operations; 4721.1 current ops/sec; [INSERT AverageLatency(us)=525.48]
    240 sec: 1000000 operations; 0 current ops/sec;
    [OVERALL], RunTime(ms), 240132.0
    [OVERALL], Throughput(ops/sec), 4164.376259723818
    [INSERT], Operations, 1000000
    [INSERT], AverageLatency(us), 935.844141
    [INSERT], MinLatency(us), 10
    [INSERT], MaxLatency(us), 26530269
    [INSERT], 95thPercentileLatency(ms), 0
    [INSERT], 99thPercentileLatency(ms), 0
    [INSERT], Return=0, 1000000
    [INSERT], 0, 999296
    [INSERT], 1, 42
    ...
    [INSERT], 999, 0
    [INSERT], >1000, 240
    
    
  5. 调用读密集型基准测试:

    bin/ycsb run hbase -P workloads/workloadb -p columnfamily=f1 -p recordcount=1000000 -p operationcount=100000 -p threadcount=4 -s | tee -a workloadb.dat
    YCSB Client 0.1
    Command line: -db com.yahoo.ycsb.db.HBaseClient -P workloads/workloadb -p columnfamily=f1 -p recordcount=1000000 -p threadcount=4 -s -t
    Loading workload...
    Starting test.
    0 sec: 0 operations;
    10 sec: 11651 operations; 1165.1 current ops/sec; [UPDATE AverageLatency(us)=95.15] [READ AverageLatency(us)=3576.62]
    20 sec: 26265 operations; 1461.25 current ops/sec; [UPDATE AverageLatency(us)=43.71] [READ AverageLatency(us)=2877.47]
    ...
    60 sec: 100000 operations; 544.22 current ops/sec; [UPDATE AverageLatency(us)=25.15] [READ AverageLatency(us)=3139.45]
    [OVERALL], RunTime(ms), 60740.0
    [OVERALL], Throughput(ops/sec), 1646.3615409944023
    [UPDATE], Operations, 5082
    [UPDATE], AverageLatency(us), 45.35615899252263
    [UPDATE], MinLatency(us), 12
    [UPDATE], MaxLatency(us), 6155
    [UPDATE], 95thPercentileLatency(ms), 0
    [UPDATE], 99thPercentileLatency(ms), 0
    [UPDATE], Return=0, 5082
    [UPDATE], 0, 5080
    [UPDATE], 1, 1
    [UPDATE], 2, 0
    [UPDATE], 3, 0
    ...
    [UPDATE], >1000, 0
    [READ], Operations, 94918
    [READ], AverageLatency(us), 2529.312764702164
    [READ], MinLatency(us), 369
    [READ], MaxLatency(us), 484754
    [READ], 95thPercentileLatency(ms), 8
    [READ], 99thPercentileLatency(ms), 13
    [READ], Return=0, 94918
    [READ], 0, 31180
    [READ], 1, 21938
    [READ], 2, 18331
    [READ], 3, 10227
    ...
    
    

它是如何工作的.

撰写本文时,YCSB 的最新版本是 0.1.4。 此版本具有预编译的 HBase 绑定。 要使用 YCSB HBase 绑定,我们将 HBase 集群的配置文件(hbase-site.xml)的链接添加到 YCSB 安装下的 hbase-binding/conf文件夹。 这将告诉 YCSB 我们的 HBase 集群的连接信息。

由于 YCSB 0.1.4 tarball 是使用 HBase 0.90.5 构建的,如果您使用的是集群的 HBase 0.92,则需要将 YCSB 的 HBase 和 ZooKeeper JAR 文件更新为您的集群正在运行的版本,否则输出中可能会出现错误 Not a host:port pair。 要实现这一点,只需将 HBase 和 ZooKeeper JAR 文件复制到 YCSB 安装下的 hbase-binding/lib文件夹。

在运行负载测试之前,我们首先需要在 HBase 中创建测试表。 测试表的名称是固定的:它必须是 usertable。 我们还需要为表创建柱族。 在步骤 3 中,我们使用名为 f1的列族创建了 usertable,该列族只支持一个版本和 LZO 压缩。

创建测试表之后,我们可以使用 ycsb命令运行 YCSB 负载测试。 执行不带参数的 ycsb命令将显示命令用法。

$ bin/ycsb
Usage: bin/ycsb command database [options]
Commands:
load Execute the load phase
run Execute the transaction phase
shell Interactive mode
Databases:
basic https://github.com/brianfrankcooper/YCSB/tree/master/basic
cassandra-10 https://github.com/brianfrankcooper/YCSB/tree/master/cassandra
cassandra-7 https://github.com/brianfrankcooper/YCSB/tree/master/cassandra
cassandra-8 https://github.com/brianfrankcooper/YCSB/tree/master/cassandra
gemfire https://github.com/brianfrankcooper/YCSB/tree/master/gemfire
hbase https://github.com/brianfrankcooper/YCSB/tree/master/hbase
infinispan https://github.com/brianfrankcooper/YCSB/tree/master/infinispan
jdbc https://github.com/brianfrankcooper/YCSB/tree/master/jdbc
mapkeeper https://github.com/brianfrankcooper/YCSB/tree/master/mapkeeper
mongodb https://github.com/brianfrankcooper/YCSB/tree/master/mongodb
nosqldb https://github.com/brianfrankcooper/YCSB/tree/master/nosqldb
redis https://github.com/brianfrankcooper/YCSB/tree/master/redis
voldemort https://github.com/brianfrankcooper/YCSB/tree/master/voldemort
Options:
-P file Specify workload file
-p key=value Override workload property
-s Print status to stderr
-target n Target ops/sec (default: unthrottled)
-threads n Number of client threads (default: 1)
Workload Files:
There are various predefined workloads under workloads/ directory.
See https://github.com/brianfrankcooper/YCSB/wiki/Core-Properties
for the list of workload properties.

在步骤 4 中,我们调用了一个写入繁重的测试,将 100 万条记录加载到 HBase 中的测试表中。 测试行为由工作负载文件定义。 工作负载指定将加载到表中的数据,以及将对数据执行的操作。 YCSB 的 workloads目录下有预定义的工作负载。 在这里,我们选择 workloads/workloada作为工作负载文件。 这是一个更新繁重的工作负载。 我们还通过在命令行中指定 -p recordcount=1000000来覆盖工作负载的默认记录计数。 通过将 -p threadcount=4传递给命令,我们调用了四个客户端线程来运行测试。 -s选项使 YCSB 定期向输出报告状态。

正如您从 HBase web UI 中看到的那样,YCSB 开始将测试数据加载到我们之前在步骤 3 中创建的表中:

How it works...

负载测试耗时 240132.0 毫秒完成,平均吞吐量为每秒 4,164 个操作。 输出还报告插入操作数量、总延迟和详细延迟。 所有插入都已成功完成(返回=0)。 几乎所有的插入(999296)在不到 1ms 的时间内完成,而 42 个插入在 1-2ms 之间完成。 还有 240 个插入操作耗时超过 1000ms 才能完成。 由于高负载,这些插件可能被区域服务器阻塞了一段时间。

我们还在步骤 5 中调用了读密集型测试。我们对测试数据执行了 100,000 个操作(-p operationcount=100000),而 95%的操作是读操作(在 workloads/workloadb)中定义)。 测试在 60740.0 毫秒内完成;吞吐量为每秒 1646 次操作。 如您所见,HBase 写入比读取快得多。

YCSB 支持其他有用的属性,例如报告时间序列延迟。 有关工作量属性的列表,请参阅以下内容:

https://github.com/brianfrankcooper/YCSB/wiki/Core-Properties

还有更多...

HBase 附带了自己的性能评估(PE)工具,该工具也可用于对 HBase 进行基准测试。 以下是 HBase PE 工具的用法:

$ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.PerformanceEvaluation
Usage: java org.apache.hadoop.hbase.PerformanceEvaluation \
[--miniCluster] [--nomapred] [--rows=ROWS] <command> <nclients>
Options:
miniCluster Run the test on an HBaseMiniCluster
nomapred Run multiple clients using threads (rather than use mapreduce)
rows Rows each client runs. Default: One million
flushCommits Used to determine if the test should flush the table. Default: false
writeToWAL Set writeToWAL on puts. Default: True
Command:
filterScan Run scan test using a filter to find a specific row based on its value (make sure to use --rows=20)
randomRead Run random read test
randomSeekScan Run random seek and scan 100 test
randomWrite Run random write test
scan Run scan test (read every row)
scanRange10 Run random seek scan with both start and stop row (max 10 rows)
scanRange100 Run random seek scan with both start and stop row (max 100 rows)
scanRange1000 Run random seek scan with both start and stop row (max 1000 rows)
scanRange10000 Run random seek scan with both start and stop row (max 10000 rows)
sequentialRead Run sequential read test
sequentialWrite Run sequential write test
Args:
nclients Integer. Required. Total number of clients (and HRegionServers)
running: 1 <= value <= 500
Examples:
To run a single evaluation client:
$ bin/hbase org.apache.hadoop.hbase.PerformanceEvaluation sequentialWrite 1

以下是使用 HBase PE 测试顺序写入性能的示例:

$ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.PerformanceEvaluation sequentialWrite 1
12/03/20 16:34:42 INFO hbase.PerformanceEvaluation: Start class org.apache.hadoop.hbase.PerformanceEvaluation$SequentialWriteTest at offset 0 for 1048576 rows

12/03/20 16:34:50 INFO hbase.PerformanceEvaluation: 0/104857/1048576
12/03/20 16:34:57 INFO hbase.PerformanceEvaluation: 0/209714/1048576
...
12/03/20 16:36:11 INFO hbase.PerformanceEvaluation: Finished class org.apache.hadoop.hbase.PerformanceEvaluation$SequentialWriteTest in 88730ms at offset 0 for 1048576 rows


我们使用一个客户端将大约 100 万行(每行 100 字节)顺序写入 HBase 集群中的测试表。 这项测试花了 88 秒才完成。 不需要指定表和列族名称,因为 HBase PE 工具将创建一个名为 TestTable的表,其代码中包含一个名为 info的列族。

备注

请注意,在使用 PE 运行读取测试之前,您需要执行写入测试,因为读取测试使用写入测试插入的数据。

增加区域服务器处理程序计数

区域服务器保持多个正在运行的线程,以应答对用户表的传入请求。 为防止区域服务器内存不足,默认情况下将此数字设置为非常低。 对于许多情况,特别是当您有许多并发客户端时,您将需要增加此数量以处理更多请求。

在本菜谱中,我们将介绍如何调优区域服务器处理程序计数。

做好准备

以启动 HBase 的用户身份登录主节点。

怎么做……

要增加区域服务器处理程序计数,需要执行以下步骤:

  1. 在主节点上,将以下内容添加到您的 hbase-site.xml文件:

    hadoop@master1$ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.regionserver.handler.count</name>
    <value>40</value>
    </property>
    
    
  2. 在群集中同步更改:

    hadoop@master1$ for slave in `cat $HBASE_HOME/conf/regionservers`
    do
    rsync -avz $HBASE_HOME/conf/ $slave:$HBASE_HOME/conf/
    done
    
    
  3. 重新启动 HBase 以应用更改。

它是如何工作的.

hbase.regionserver.handler.count属性控制 RPC 侦听器线程的计数。 默认情况下,该属性设置为 10。这是一个相当低的值,可防止区域服务器在某些情况下内存不足。

如果您的区域服务器可用内存不足,则应将其设置为较低的值。 较低的值也适用于处理需要大量内存的请求,例如将较大的值放入 HBase 或使用较大的缓存配置扫描数据。 将 hbase.regionserver.handler.count设置为高意味着更多的并发客户端,这可能会消耗区域服务器中的过多内存,甚至会耗尽所有内存。

如果您的请求只需要少量内存,但需要较高的每秒事务处理量(TPS),请考虑将其设置为更大的值,以便区域服务器可以处理更多并发请求。

调优此值时,我们建议您启用 RPC 级日志记录,并监视每个 RPC 请求的内存使用情况和 GC 状态。

您需要在整个集群中同步更改并重新启动 HBase 才能应用它。

另请参阅

  • 启用 HBase RPC 调试级日志记录配方,参见第 6 章维护和安全

使用您自己的算法预先创建区域

当我们在 HBase 中创建表时,该表从单个区域开始。 插入到该表中的所有数据都将进入单个区域。 随着数据的不断增长,当区域大小达到阈值时,会发生区域分割。 单个区域被分成两半,以便表可以处理更多数据。

在写入繁重的 HBase 集群中,此方法有几个问题需要解决:

  • The split/compaction storm issue.

    随着数据的均匀增长,大部分区域同时被分割,造成了巨大的磁盘 I/O 和网络流量。

  • Load is not well balanced until enough regions have been split.

    尤其是在创建表之后,所有请求都会转到部署第一个区域的同一个区域服务器。

拆分/压缩问题已在基本性能调整管理区域拆分配方中进行了讨论。 通过使用手动分割方法。 对于第二个问题,我们在第 2 章中的在将数据移入 HBase配方之前的预创建区域中介绍了如何通过在创建表时创建区域来避免它。 我们描述了如何使用 HBase RegionSplitter实用程序预先创建区域。

默认情况下, RegionSplitter实用程序使用 MD5 算法生成 MD5 校验和的区域起始键。 键的范围在“00000000”到“7FFFFFFF”之间。 这在许多情况下都很有效;但在某些情况下,您可能希望使用自己的算法生成密钥,以便在集群中很好地分配负载。

由于 HBase 行键完全由将数据放入 HBase 的应用控制,因此在许多情况下,行键的范围和分布在某种程度上是可以预测的。 因此,可以计算区域分割密钥并使用它们来创建预分割区域。

我们将在本食谱中描述如何实现这一目标。 我们将使用文本文件中指定的区域起始键,创建一个具有预定义区域的表。

做好准备

登录到您的 HBase 客户端节点,并在那里创建一个 split-keys文件。 将您的区域分割密钥放入文件中,每行一个密钥。 例如,我们假设该文件包含以下密钥:

$ cat split-keys
a0000
affff
b0000
bffff

怎么做……

按照以下说明使用您自己的算法预先创建面域:

  1. 创建实现 org.apache.hadoop.hbase.util.RegionSplitter.SplitAlgorithm接口的 FileSplitAlgorithmJava 类:

    $ vi FileSplitAlgorithm.java
    import org.apache.hadoop.hbase.util.RegionSplitter.SplitAlgorithm;
    public class FileSplitAlgorithm implements SplitAlgorithm {
    public static final String SPLIT_KEY_FILE = "split-keys";
    }
    
    
  2. 接口的 split()方法实现如下:

    $ vi FileSplitAlgorithm.java
    @Override
    public byte[][] split(int numberOfSplits) {
    BufferedReader br = null;
    try {
    File keyFile = new File(SPLIT_KEY_FILE);
    if (!keyFile.exists()) {
    throw new FileNotFoundException("Splitting key file not found: " + SPLIT_KEY_FILE);
    }
    List<byte[]> regions = new ArrayList<byte[]> ();
    br = new BufferedReader(new FileReader(keyFile));
    String line;
    while ((line = br.readLine()) != null) {
    if (line.trim().length() > 0) {
    regions.add(Bytes.toBytes(line));
    }
    }
    return regions.toArray(new byte[0][]);
    } catch (IOException e) {
    throw new RuntimeException("Error reading splitting keys from " + SPLIT_KEY_FILE, e);
    } finally {
    if (br != null) {
    try {
    br.close();
    } catch (IOException e) {
    // ignore
    }
    }
    }
    }
    
    
  3. 要编译 Java 类,我们还需要实现接口的其他几个方法。 因为我们实际上并不使用它们,所以只需为每个方法创建一个空实现,如下所示(我们在这里跳过了一些方法):

    $ vi FileSplitAlgorithm.java
    @Override
    public byte[] firstRow() {
    return null;
    }
    @Override
    public byte[] lastRow() {
    return null;
    }
    
    
  4. 编译 Java 文件:

    $ javac -classpath $HBASE_HOME/hbase-0.92.0.jar FileSplitAlgorithm.java
    
    
  5. 将包含拆分密钥的 split-keys文件复制到编译 FileSplitAlgorithm的目录。

  6. 运行以下脚本以在创建表时预先创建区域:

    $ export HBASE_CLASSPATH=$HBASE_CLASSPATH:./
    $ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.util.RegionSplitter -D split.algorithm=FileSplitAlgorithm -c 2 -f f1 test_table
    12/03/25 08:09:42 DEBUG util.RegionSplitter: -D configuration override: split.algorithm=FileSplitAlgorithm
    12/03/25 08:09:42 DEBUG util.RegionSplitter: Creating table test_table with 1 column families. Presplitting to 2 regions
    12/03/25 08:09:49 DEBUG util.RegionSplitter: Table created! Waiting for regions to show online in META...
    12/03/25 08:09:49 DEBUG util.RegionSplitter: Finished creating table with 2 regions
    
    
  7. Confirm that the table and predefined regions has been created correctly via the HBase web UI:

    How to do it...

它是如何工作的.

HBase 附带一个 RegionSplitter实用程序类,用于:

  • 创建带有预分割区域的 HBase 表
  • 对现有表中的所有区域执行滚动拆分
  • 使用自定义算法分割区域

我们的说明基于这个实用程序类。

首先,我们创建了一个实现 SplitAlgorithm接口的 FileSplitAlgorithmJava 类。 SplitAlgorithm是在 RegionSplitter类中声明的 Java 接口,用于定义 RegionSplitter的功能。 我们还在 FileSplitAlgorithm类中定义了一个 SPLIT_KEY_FILE常量来引用包含区域起始键的文件。

SplitAlgorithm接口定义了实现类需要实现的几个方法。 要在创建表时拆分区域,我们只需像在步骤 2 中那样实现 split()方法。此方法由 RegionSplitter类调用以拆分整个表。 在我们的实现中,它从我们准备的文件中读取拆分密钥,每行一个密钥。 然后,将它们转换为表示表初始区域的拆分键的 byte[]数组。

其他 SplitAlgorithm接口方法对于我们的使用是不必要的,所以我们在步骤 3 中只将这些方法的实现放在空的位置。

在步骤 4 中,我们将 HBase JAR 添加到类路径中,然后运行 javac命令编译 Java 代码。

要使用该类分割区域,我们将 FileSplitAlgorithm类添加到 HBASE_CLASSPATH,然后使用以下参数调用 RegionSplitter实用程序:

  • -D split.algorithm=FileSplitAlgorithm:拆分算法。
  • -c 2:要将表拆分到的区域数;在我们的实现中不使用。
  • -f f1:创建名为“F1”的单个柱族。
  • test_table:要创建的表的名称。

正如您在步骤 7 中看到的, test_table被分成五个区域,由四个拆分键分隔。 拆分密钥与我们放在 split-keys文件中的密钥完全相同。

其他表属性都有缺省值;您可能希望通过 HBase Shell 使用 alter命令更改其中一些属性。

请注意,即使以前拆分区域,您也需要在应用层设计行键,以避免向单个区域写入过多的连续行键。 您需要仔细选择拆分算法以适合您的数据访问模式。

还有更多...

此方法的另一个有用场景是加快将数据从导出的备份 HBase 表导入到 HBase 中。

正如第 4 章备份和还原 HBase 数据中的配方备份区域起始密钥中提到的,您可以通过一个简单的脚本备份您的区域起始密钥。 通过 FileSplitAlgorithm类,我们可以使用这些键先前恢复 HBase 表的区域边界,然后通过从导出的数据文件导入来恢复数据。

与从单个区域导入数据相比,由于先将区域恢复并均衡到多个区域服务器,因此数据恢复速度将显著提高。

另请参阅

  • 第 8 章中的管理区域拆分
  • 在将数据移动到第 2 章中的 HBase之前预先创建区域
  • 备份第 4 章中的区域起始键

避免写入密集型群集上的更新阻塞

在写入繁重的 HBase 群集上,您可能会发现写入速度不稳定。 大多数写操作都非常快,但也有一些写得很慢。 对于在线系统,即使平均速度非常快,这种不稳定的写入速度也是不可接受的。

这种情况很可能是由以下两个原因造成的:

  • 拆分/压缩会使群集负载非常高
  • 更新被区域服务器阻止

正如我们在第 8 章基本性能调优中所描述的,您可以通过禁用自动拆分/压缩并在低加载时间调用它们来避免拆分/压缩问题。

Grep 您所在地区的服务器日志,如果您发现许多消息说“阻止更新...”,则可能是许多更新被阻止,并且这些更新的响应时间可能较短。

要解决此问题,我们需要调整服务器端和客户端配置以获得稳定的写入速度。 在本食谱中,我们将描述避免更新阻塞的最重要的服务器端调优。

做好准备

由启动 HBase 的用户登录到您的主节点。

怎么做……

要避免更新阻止,请执行以下步骤:

  1. 增加 hbase-site.xml文件中的 hbase.hregion.memstore.block.multiplier属性值。

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.hregion.memstore.block.multiplier</name>
    <value>8</value>
    </property>
    
    
  2. 增加 hbase-site.xml文件中的 hbase.hstore.blockingStoreFiles属性值。

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.hstore.blockingStoreFiles</name>
    <value>20</value>
    </property>
    
    
  3. 跨群集中同步更改并重新启动 HBase 以应用更改。

它是如何工作的.

以下是 HBase 写操作的流程:

How it works...

编辑首先写入 RegionServer 的HLog(预写日志),其中编辑在HDFS上持久化。 之后,编辑转到宿主 HRegion,然后转到它的列族 HStore 的内存空间,称为MemStore。 当MemStore的大小达到阈值时,它被刷新到HDFS上的StoreFile。 StoreFiles 在内部使用HFile文件格式保存数据。

HBase 是一种多版本并发控制(MVCC)架构。 要更新/删除任何旧数据,而不是覆盖它,HBase 会向数据添加一个较新的版本。 这使得 HBase 写入非常快,因为所有写入都是顺序操作。

当 HDFS 上有许多小的 StoreFiles 时,HBase 开始压缩,将它们重写为更少但更大的文件。 如果一个区域的大小达到阈值,它将被一分为二。

为防止长时间压缩/拆分和内存不足错误,如果区域的 MemStore 大小达到阈值,HBase 会阻止更新,阈值由以下条件定义:

hbase.hregion.memstore.flush.size乘以 hbase.hregion.memstore.block.multiplier

hbase.hregion.memstore.flush.size属性指定 MemStore 将刷新到磁盘的大小。 其默认值为 128MB(在 0.90 版本中为 64MB)。

hbase.hregion.memstore.block.multiplier属性的默认值为 2,这意味着如果 MemStore 的大小为 256MB(128x2),则该区域上的更新将被阻止。 在更新流量峰值期间,该值在写入繁重的群集中太小,因此我们需要提高阻塞阈值。

我们通常将 hbase.hregion.memstore.flush.size属性保留为其默认值,并将 hbase.hregion.memstore.block.multiplier属性调优为一个大得多的值(如 8),以增加 MemStore 阻塞大小,从而减少更新阻塞计数。

请注意,增加 hbase.hregion.memstore.block.multiplier更有可能在刷新时触发压缩/拆分,因此请仔细调整。

步骤 2 适用于另一个阻塞场景。 如果任何一个Store的 StoreFiles 数超过 hbase.hstore.blockingStoreFiles(默认情况下为 7)(每个 MemStore 刷新一个 StoreFile),则此区域的更新将被阻止,直到压缩完成或超过 hbase.hstore.blockingWaitTime(默认情况下为 90 秒)。 我们将其增加到 20,对于写入繁重的群集来说,这是一个相当大的值。 副作用是将有更多文件需要压缩。

此调优通常会降低发生更新阻塞的可能性。 同时,正如我们刚才提到的,它也有副作用。 我们建议您仔细调整这些设置,并在调整过程中观察写入吞吐量和延迟,以找到最佳配置值。

另请参阅

  • 调整 MemStores的内存大小

调整 MemStore 的内存大小

正如我们在配方避免写繁重集群上的更新阻塞中所描述的那样,HBase 写操作首先在托管区域的 MemStore 中应用,然后在 MemStore 大小达到阈值时刷新到 HDFS 以节省内存空间。 MemStore 刷新使用 MemStore 的快照在后台线程上运行。 因此,即使在刷新 MemStore 时,HBase 也会继续处理写入。 这使得 HBase 写得非常快。 如果写入峰值过高,以致 MemStore 刷新无法跟上,则填充 MemStore 的写入速度和 MemStore 使用的内存将持续增长。 如果区域服务器中所有 MemStore 的大小达到可配置阈值,则会阻止更新并强制刷新。

在本文中,我们将介绍如何调优此总 MemStore 内存大小以避免更新阻塞。

做好准备

以启动 HBase 的用户身份登录到您的主节点。

怎么做……

需要执行以下步骤来调整 MemStore 的内存大小:

  1. 增加 hbase-site.xml文件中的 hbase.regionserver.global.memstore.upperLimit属性值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.regionserver.global.memstore.upperLimit</name>
    <value>0.45</value>
    </property>
    
    
  2. 增加 hbase-site.xml文件中的 hbase.regionserver.global.memstore.lowerLimit属性值:

    vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.regionserver.global.memstore.lowerLimit</name>
    <value>0.4</value>
    </property>
    
    
  3. 跨群集中同步更改并重新启动 HBase 以应用更改。

它是如何工作的.

在阻止新更新并强制刷新之前, hbase.regionserver.global.memstore.upperLimit属性控制区域服务器中所有 MemStore 的最大大小。 这是一种防止 HBase 因写入峰值而耗尽内存的配置。 默认设置为 0.4,表示区域服务器堆大小的 40%。

默认值适用于许多情况。 但是,如果您在您的区域服务器日志中检测到许多日志条目显示 Flush of region xxxx due to global heap pressure,那么您可能需要调优此属性来处理高写入速率。

我们在步骤 2 中调优的 hbase.regionserver.global.memstore.lowerLimit属性指定当 MemStore 被强制刷新时,它们会一直刷新,直到 MemStore 占用的内存大小减少到这个标记。 默认值为区域服务器堆大小的 35%。

在写入繁重的群集上,增加这两个值有助于降低由于 MemStore 大小限制而阻止更新的可能性。 另一方面,您需要仔细地调优它,以避免出现垃圾回收满或内存不足错误的情况。

还有更多...

通常,读取性能不如写入密集型群集上的写入重要。 因此,我们可以调整群集以优化写入。 优化之一是减少分配给 HBase 块缓存的内存空间,并为 MemStore 腾出空间。 有关如何调优块高速缓存大小的信息,请参见配方在读密集型集群上增加块高速缓存大小

另请参阅

  • 避免写入密集型群集上的更新阻塞
  • 为列族配置块缓存
  • 增加读密集型群集上的数据块缓存大小

针对低延迟系统的客户端调整

我们介绍了几种避免服务器端阻塞的方法。 这些配方应该可以帮助集群稳定运行并具有高性能。 通过服务器端调优,集群吞吐量和平均延迟将显著提高。

然而,在低延迟和实时系统中,仅在服务器端调优是不够的。 即使只出现轻微的暂停,长时间暂停在低延迟系统中也是不可接受的。

有一些客户端配置我们可以调优,以避免长时间暂停。 在本食谱中,我们将介绍如何调优这些配置以及它们的工作方式。

做好准备

以访问 HBase 的用户身份登录到您的 HBase 客户端节点。

怎么做……

按照以下说明为写入繁重的群集执行客户端调整:

  1. 减少 hbase-site.xml文件中的 hbase.client.pause属性值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.client.pause</name>
    <value>20</value>
    </property>
    
    
  2. 调整 hbase-site.xml文件中的 hbase.client.retries.number属性值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.client.retries.number</name>
    <value>11</value>
    </property>
    
    
  3. hbase-site.xml文件中将 hbase.ipc.client.tcpnodelay设置为 true

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.ipc.client.tcpnodelay</name>
    <value>true</value>
    </property>
    
    
  4. 减少 hbase-site.xml文件中的 ipc.ping.interval值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>ipc.ping.interval</name>
    <value>4000</value>
    </property>
    
    

它是如何工作的.

在步骤 1 和 2 中调整 hbase.client.pausehbase.client.retries.number属性的目的是让客户端在连接到群集失败时在短时间内快速重试。

hbase.client.pause属性控制客户端应该在两次重试之间休眠多长时间。 其默认值为 1000 毫秒(1 秒)。 hbase.client.retries.number属性用于指定最大重试次数。 默认情况下,它的值为 10。

使用以下命令计算每次重试之间的休眠时间:

pause_time = hbase.client.pause * RETRY_BACKOFF[retries]

其中 RETRY_BACKOFF是重试退避乘数表,其定义如下:

public static int RETRY_BACKOFF[] = { 1, 1, 1, 2, 2, 4, 4, 8, 16, 32 };

重试 10 次以上后,HBase 将始终使用最后一个乘数(32)来计算暂停时间。

由于我们将暂停时间配置为 20ms,最大重试次数为 11,因此两次重试群集之间的暂停时间如下:

{ 20, 20, 20, 40, 40, 80, 80, 160, 320, 640, 640 }

这意味着客户端将在 2060ms 内重试 11 次,然后才会放弃连接到群集。

在步骤 3 中,我们将 hbase.ipc.client.tcpnodelay设置为 true。 此设置为客户端和服务器之间的套接字传输禁用Nagle 算法

Nagle 的算法是一种提高网络效率的方法,它通过缓冲大量小的传出消息,并一次性发送它们。 默认情况下启用 Nagle 算法。 低延迟系统应通过将 hbase.ipc.client.tcpnodelay设置为 true来禁用Nagle 算法

在步骤 4 中,我们将 ipc.ping.interval设置为 4000 毫秒(4 秒),以便在客户端和服务器之间的套接字传输期间不会超时。 默认的 ipc.ping.interval是 1 分钟,这对于低延迟系统来说有点太长了。

还有更多...

您还可以在客户端代码中使用 org.apache.hadoop.hbase.HBaseConfiguration类来覆盖在 hbase-site.xml中设置的前面属性的值。 下面的示例代码与前面步骤 1 和 2 中的设置具有相同的效果:

Configuration conf = HBaseConfiguration.create();
conf.setInt("hbase.client.pause", 20);
conf.setInt("hbase.client.retries.number", 11);
HTable table = new HTable(conf, "tableName");

为列族配置块缓存

HBase 支持块缓存,提高读取性能。 执行扫描时,如果启用了块缓存并且还有剩余空间,则从 HDFS 上的 StoreFiles 读取的数据块将被缓存在区域服务器的 Java 堆空间中,以便下次访问同一块中的数据时,缓存的块可以提供服务。 数据块缓存有助于减少用于检索数据的磁盘 I/O。

块缓存可在表的列族级别进行配置。 不同的列族可以具有不同的高速缓存优先级,甚至可以禁用块高速缓存。 应用利用此缓存机制来适应不同的数据大小和访问模式。

在本食谱中,我们将介绍如何为列族配置块缓存,并介绍如何利用 HBase 块缓存。

做好准备

登录到您的 HBase 客户端节点。

怎么做……

要在列族级别配置数据块缓存,需要执行以下步骤:

  1. 启动 HBase 外壳:

    $ $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
    hbase(main):001:0>
    
    
  2. 执行以下命令以创建具有三个列族的表:

    hbase> create 'table1', {NAME => 'f1'}, {NAME => 'f2', IN_MEMORY => 'true'}, {NAME => 'f3', BLOCKCACHE => 'false'}
    0 row(s) in 1.0690 seconds
    
    
  3. 显示先前创建的表的属性:

    hbase> describe 'table1'
    DESCRIPTION ENABLED
    {NAME => 'table1', FAMILIES => [{NAME => 'f1', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', true
    COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOC
    KCACHE => 'true'}, {NAME => 'f2', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', COMPRESSION =
    > 'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'true', BLOCKCACHE => 'true'}, {NAME => 'f3', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', COMPRESSION => 'NONE', MIN_
    VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'false'}]}
    1 row(s) in 0.0290 seconds
    
    

它是如何工作的.

我们创建了一个具有三个列族 f1, f2f3的表(table1)。 对于 Family f1,我们没有指定任何属性,因此它的所有属性都设置为默认值。 如步骤 3 所示,其块高速缓存被启用(BLOCKCACHE => 'true'),并且存储器中的块高速缓存被关闭(IN_MEMORY => 'false')

HBase 块缓存包含三个块优先级级别:单次访问、多次访问和内存访问。 如有必要,将使用内存中标志添加一个块(这意味着 HBase 将尝试更积极地将该块保留在内存中,但不能保证),否则它将成为单一访问优先级。 一旦数据块再次被访问,它将被标记为多路访问。 如下图所示,这三个优先级的缓存空间不同,单次访问和内存访问分别为 25%,多次访问分别为总缓存空间的 50%:

How it works...

我们上面创建的列族 f2被标记为已启用内存。 因此,属于该系列的块以内存中的优先级进行缓存。 对于系列 f3,块高速缓存被禁用,这意味着列系列的数据块将不会被高速缓存。 不建议禁用数据块缓存。

由于数据是以块为单位进行缓存的,因此访问同一块中的数据非常高效。 对于小尺寸的行来说尤其如此。 因此,将同时访问的数据放在同一列族中是表模式设计的良好实践。 例如,当使用 HBase 存储从互联网上抓取的网页时,最好有一个 meta列族和一个 raw列族,其中 meta列族启用内存来保存网页的元数据,而 raw列族则存储页面的原始内容。

还有更多...

您还可以通过 HBase Shell 使用 alter命令更改现有列族的块缓存属性:

  1. 禁用要更改的表:

    hbase> disable 'table1'
    0 row(s) in 7.0580 seconds
    
    
  2. 使用 alter命令更改表的块大小:

    hbase> alter 'table1', {NAME => 'f1', IN_MEMORY => 'true'}
    Updating all regions with the new schema...
    1/1 regions updated.
    Done.
    0 row(s) in 6.0990 seconds
    
    
  3. 使用 describe命令确认您的更改:

    hbase> describe 'table1'
    DESCRIPTION ENABLED
    {NAME => 'table1', FAMILIES => [{NAME => 'f1', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', false
    COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'true', BLOCK
    CACHE => 'true'}, {NAME => 'f2'...
    1 row(s) in 0.0300 seconds
    
    
  4. Enable the table again:

    hbase> enable 'table1'
    0 row(s) in 2.0530 seconds
    
    

    在这里,我们刚刚为表 table1中的现有列族 f1启用了内存中。

另请参阅

  • 增加读密集型群集上的数据块缓存大小
  • 客户端扫描仪设置

增加读密集型群集上的数据块缓存大小

如配方调优 MemStores 的内存大小为列族配置块缓存中所述,区域服务器为 MemStores 分配大量的 Java 堆空间以提高写入性能。 它还使用大量堆空间缓存 StoreFile 块,以提高读取性能。

写入和读取性能之间存在平衡。 在读取密集型群集上,由于读取性能更重要,您可能希望为块缓存分配更多内存。

在本配方中,我们将介绍如何增加块缓存大小。 我们还将提供有关确定块缓存是否足够的提示。

做好准备

以启动 HBase 的用户身份登录到您的主节点。

怎么做……

要增加数据块缓存大小,需要执行以下步骤:

  1. 增加 hbase-site.xml文件中的 hfile.block.cache.size属性值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hfile.block.cache.size</name>
    <value>0.3</value>
    </property>
    
    
  2. 跨群集中同步更改并重新启动 HBase 以应用更改。

它是如何工作的.

区域服务器的总块缓存空间由 hfile.block.cache.size属性配置。 此属性指定要分配给块缓存的最大区域服务器堆的百分比。 默认情况下,它分配最大堆大小的 25%。

在步骤 1 中,我们将块缓存的总空间增加到最大区域服务器堆大小的 30%。 在读取密集型群集上,建议减少 MemStore 的空间,并为块缓存分配更多内存。 MemStore 和块缓存通常消耗大约 60%~70%的最大区域服务器堆大小。 这是一个合理的值。 MemStore 上限和块缓存的合计值不应高于此级别,除非您绝对确定它会很好。

确定应该为块缓存分配多少内存的另一个指标是检查 Ganglia 或 HBase web UI 上的区域服务器指标。 例如,单击 HBase web UI 上的区域服务器链接;您将找到区域服务器的最重要指标:

How it works...

检查页面上的 MemStore 大小、块缓存大小、命中率等;您会发现这些信息有助于调整集群。 如果块缓存命中率非常低,您可能需要检查您的表模式和数据访问模式;如果列总是同时被访问,则将它们放在一起。 使用 Bloom Filter 是提高块缓存命中率的另一种解决方案。 如果您遇到许多数据块逐出,请考虑增加数据块缓存大小以容纳更多数据块。

另请参阅

  • 调整 MemStores的内存大小
  • 为列族配置块缓存
  • 启用 Bloom Filter 提高整体吞吐量

客户端扫描仪设置

要获得更好的读取性能,除了服务器端调优之外,重要的是客户端应用端的扫描仪设置。 更好的客户端扫描仪设置可使扫描过程更加高效。 相比之下,配置不佳的扫描仪不仅会降低扫描速度,还会对区域服务器造成负面影响。 因此,我们需要仔细配置客户端扫描仪设置。

最重要的扫描仪设置包括扫描缓存、扫描属性选择和扫描块缓存。 在本指南中,我们将介绍如何正确配置这些设置。

做好准备

由访问 HBase 的用户登录到您的 HBase 客户端节点。

怎么做……

要更改客户端扫描仪设置,需要执行以下步骤:

  1. 要在扫描仪上调用 next()方法时提取更多行,请增加 hbase-site.xml文件中的 hbase.client.scanner.caching属性值:

    $ vi $HBASE_HOME/conf/hbase-site.xml
    <property>
    <name>hbase.client.scanner.caching</name>
    <value>500</value>
    </property>
    
    
  2. 仅通过指定列族和限定符来获取所需的列。 示例代码如下所示:

    Scan scan = new Scan();
    // your scan filtering code
    scan.addColumn(family1, qualifier1);
    scan.addColumn(family1, qualifier2);
    
    
  3. 要禁用特定扫描的块缓存,请在代码中添加以下内容。 请注意,这不会禁用服务器端的块缓存,但会阻止缓存扫描仪扫描的块。

    Scan scan = new Scan();
    // your scan filtering code
    scan.setCacheBlocks(false);
    
    

它是如何工作的.

在步骤 1 中,我们将扫描缓存更改为 500,这远远大于默认值 1。这意味着区域服务器将一次向客户端传输 500 行进行处理。 对于某些情况,比如运行 MapReduce 从 HBase 读取数据,将此值设置为更大的值会使扫描过程比缺省值高效得多。 但是,较高的缓存值需要更多内存来缓存客户端和区域服务器的行。

它还存在这样的风险,即客户端可能会在完成处理日期集并调用扫描仪上的 next()之前超时。 如果客户端进程较快,请将缓存设置得更高。 否则,您需要将其设置得更低。 我们将介绍如何在中设置基于每次扫描的缓存。 。 一节。

步骤 2 显示的想法是,如果只处理列族的一小部分,则只应将所需的数据传输到客户端。 当要处理大量行时(例如,在 MapReduce 期间),这一点尤其重要。 不需要的数据传输开销会影响大型数据集的性能。 因此,我们不应该使用 scan.addFamily()将列族中的所有列返回给客户端,而应该调用 scan.addColumn()来只指定我们需要的列。

如果在扫描数据块的扫描仪上禁用了数据块缓存,则不会将数据块添加到数据块缓存中。 正如我们在步骤 3 中所做的那样,禁用特定扫描的块缓存有时非常重要。 例如,使用 MapReduce 完全扫描表时,应禁用扫描的块缓存,因为表中的所有块都将被扫描表中的所有块,这会用一次性访问块填满块缓存空间,并会一次又一次地触发缓存逐出过程。

在 HBase web 用户界面中,您将发现两个数据块缓存命中率指标: blockCacheHitRatioblockCacheHitCachingRatio。 它们的不同之处在于, blockCacheHitRatio是块缓存命中计数占总请求计数的百分比,而 blockCacheHitCachingRatio仅包括打开缓存的请求。 我们可以通过禁用某些扫描的块缓存来增加 blockCacheHitCachingRatio

还有更多...

在上一节中,我们将 hbase-site.xml中的 hbase.client.scanner.caching属性更改为 500。 更改后,该节点上的所有客户端会话都将继承此默认缓存行号。 但是,您还可以使用 HBase 客户端扫描 API 指定每个扫描的缓存行。 下面的代码将扫描器设置为在调用 next()时提取 1000 行:

Scan scan = new Scan();
// your scan filtering code
scan.setCaching(1000);

另请参阅

  • 针对低延迟系统的客户端调整

调整块大小以提高寻道性能

HBase 数据以 HFile 格式存储为 StoreFile。 StoreFile 由 HFile 块组成。 HFile 块是 HBase 从其 StoreFiles 读取的最小数据单位。 它也是区域服务器在块缓存中缓存的基本元素。

HFile 块的大小是一个重要的调优参数。 为了获得更好的性能,我们应该根据平均键/值大小和磁盘 I/O 速度选择不同的块大小。 与块缓存和 Bloom filter 一样,HFile 块大小也可以在列族级别进行配置。

在本文中,我们将介绍如何显示平均键/值大小和调优块大小以提高查找性能。

做好准备

登录到您的 HBase 客户端节点。

怎么做……

需要执行以下步骤来调整块大小以提高寻道性能:

  1. 使用以下命令显示 HFile 中的平均键/值大小。 更改文件路径以适合您的环境。 H 特定列族的文件存储在 HDFS 的 ${hbase.rootdir}/table_name/region_name/column_family_name下。

    $ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.io.hfile.HFile -m -f /hbase/hly_temp/0d1604971684462a2860d43e2715558d/n/1742023046455748097
    Block index size as per heapsize: 12240
    reader=/hbase/hly_temp/0d1604971684462a2860d43e2715558d/n/1742023046455748097, compression=lzo, inMemory=false, firstKey=USW000128350706/n:v01/1323026325955/Put, lastKey=USW000138830522/n:v24/1323026325955/Put, avgKeyLen=31, avgValueLen=4, entries=288024, length=2379789
    fileinfoOffset=2371102, dataIndexOffset=2371361, dataIndexCount=190, metaIndexOffset=0, metaIndexCount=0, totalBytes=12387203, entryCount=288024, version=1
    Fileinfo:
    MAJOR_COMPACTION_KEY = \x00
    MAX_SEQ_ID_KEY = 96573
    TIMERANGE = 1323026325955....1323026325955
    hfile.AVG_KEY_LEN = 31
    hfile.AVG_VALUE_LEN = 4
    hfile.COMPARATOR = org.apache.hadoop.hbase.KeyValue$KeyComparator
    hfile.LASTKEY = \x00\x0FUSW000138830522\x01nv24\x00\x00\x014\x0A\x83\xA1\xC3\x04
    Could not get bloom data from meta block
    
    
  2. 启动 HBase 外壳:

    $ $HBASE_HOME/bin/hbase shell
    
    
  3. 通过 HBase Shell 设置特定列族的块大小:

    hbase> create 'hly_temp', {NAME => 'n', BLOCKSIZE => '16384'}
    0 row(s) in 1.0530 seconds
    
    
  4. 显示先前创建的表的属性:

    hbase> describe 'hly_temp'
    DESCRIPTION ENABLED
    {NAME => 'hly_temp', FAMILIES => [{NAME => 'n', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', true
    COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '16384', IN_MEMORY => 'false', BLOC
    KCACHE => 'true'}]}
    1 row(s) in 0.0330 seconds
    
    

它是如何工作的.

首先,我们想知道特定列族的平均键/值大小。 在步骤 1 中,我们调用了 HFile 工具(org.apache.hadoop.hbase.io.hfile.HFile 类)来显示 HFile 的元数据。 如配方HFile Tool,View Textualized HFile Contentin第 3 章中所述,通过 -m选项,我们可以使用此工具获取 HFile 文件的元数据,包括平均键/值大小。 在步骤 1 中,我们传递了 -f选项和文件路径来显示单个 HFile 的元数据。 如果您的表只有一个列族,您还可以使用 -r选项和一个区域名称来显示属于该区域的每个 HFile 的元数据。 有了这些信息,我们就能够获得列族的大致平均键/值大小。

从步骤 1 的输出中可以看到,HFile 的平均键/值大小非常小(35 字节)。 在这种情况下,键/值的平均大小非常小(例如,100 字节);我们应该使用较小的块(例如,16KB),以避免在每个块中存储过多的键/值对,这会增加块内查找的延迟,因为查找操作总是在块内按顺序从第一个键/值对中查找键。

从步骤 2 到步骤 4,我们演示了如何通过 HBase Shell 配置列族的块大小。在步骤 3 中,我们创建了具有单个列族‘n’的 hly_temp表,并将列族的块大小指定为 16384 字节(16KB)。这比 64KB 的默认块大小小得多。

这里,我们选择较小的块大小以牺牲较大的块索引(更多内存消耗)来实现更快的随机访问。 另一方面,如果平均键/值较大,或者磁盘速度较慢导致瓶颈,则应选择较大的块大小,以便单个磁盘 I/O 可以获取更多数据。

还有更多...

您还可以使用 HBase Shell 中的 alter命令更改现有列族的块大小。 这将在创建新的 StoreFiles 时应用。

另请参阅

  • HFile 工具,查看第 3 章中的文本化 HFile 内容

启用 Bloom Filter 以提高整体吞吐量

HBase 支持 Bloom Filter,提升集群整体吞吐量。 HBase Bloom Filter 是一种节省空间的机制,用于测试 StoreFile 是否包含特定的行或行-列单元格。 下面是 Bloom filter 的详细信息:en.wikipedia.org/wiki/Bloom_…

在没有 Bloom Filter 的情况下,确定 StoreFile 中是否包含行键的唯一方法是检查 StoreFile 的块索引,它存储 StoreFile 中每个块的起始行键。 我们找到的行键很可能会落在两个块起始键之间;如果是这样,那么 HBase 必须加载块并从块的起始键开始扫描,以确定行键是否确实存在。

这里的问题是,在主要压缩将它们聚合成单个 StoreFiles 之前,将存在许多 StoreFiles。 因此,几个 StoreFiles 可能具有请求的行键的一些单元格。

考虑一下下面的示例;这是一张显示 HBase 如何在 StoreFile 中存储数据、块索引和 Bloom 过滤器的图像:

Enabling Bloom Filter to improve the overall throughput

单个列族有四个 StoreFiles,它们是由 MemStore 刷新创建的。 StoreFiles 在行键中有类似的跨页。 一些行键(例如, row-D))被本地化到几个 StoreFiles,而另一些(例如, row-F))则分布在多个 StoreFiles 中。 虽然只有 StoreFile1 实际包含 row-D,但 HBase 还需要从 StoreFiles 2 和 3 加载第一个数据块,以确定该块是否包含 row-D的单元格,因为 row-D位于第一个数据块之间。 如果有一种机制告诉 HBase 跳过加载 StoreFiles 2 和 3,那就更好了。

在这种情况下,Bloom Filter 会有所帮助。 Bloom Filter 用于减少这些不必要的磁盘 I/O,从而提高集群的整体吞吐量。

在本食谱中,我们将介绍如何为列系列启用 Bloom Filter,以及如何利用 HBase Bloom Filter 提高总体吞吐量的提示。

做好准备

登录到您的 HBase 客户端节点。

怎么做……

要启用列族的 Bloom Filter,需要执行以下步骤:

  1. 启动 HBase 外壳:

    $ $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
    hbase(main):001:0>
    
    
  2. 执行以下命令以创建具有三个列族的表:

    hbase> create 'table2', {NAME => 'f1'}, {NAME => 'f2', BLOOMFILTER => 'ROW'}, {NAME => 'f3', BLOOMFILTER => 'ROWCOL'}
    0 row(s) in 1.0690 seconds
    
    
  3. 显示先前创建的表的属性:

    hbase> describe 'table2'
    DESCRIPTION ENABLED
    {NAME => 'table2', FAMILIES => [{NAME => 'f1', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0', VERSIONS => '3', true
    COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOC
    KCACHE => 'true'}, {NAME => 'f2', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', VERSIONS => '3', COMPRESSION =>
    'NONE', MIN_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}, {NAME => 'f3', BLOOMFILTER => 'ROWCOL', REPLICATION_SCOPE => '0', VERSIONS => '3', COMPRESSION => 'NONE', MI
    N_VERSIONS => '0', TTL => '2147483647', BLOCKSIZE => '65536', IN_MEMORY => 'false', BLOCKCACHE => 'true'}]}
    1 row(s) in 0.0360 seconds
    
    

它是如何工作的.

我们创建了一个具有三个列族 f1, f2f3的表(table2)。 对于 Family f1,我们没有指定任何属性,因此它的所有属性都设置为默认值。 如步骤 3 所示,其布隆过滤器被禁用(BLOOMFILTER => 'NONE')。 我们为 f2``(BLOOMFILTER => 'ROW')指定了行级布隆过滤器,为 f3``(BLOOMFILTER => 'ROWCOL')指定了行+列级布隆过滤器。

如果启用 Bloom Filter,HBase 会在创建 StoreFile 时添加 Bloom Filter 和数据块。 Bloom filter 用于让 HBase 高效地判断 StoreFile 是否包含特定的行或单元格,而无需实际加载文件和扫描块。 Bloom 过滤器可能是假阳性,这意味着查询返回 a row is contained in a file,但实际上不是。 但是 Bloom Filter 不允许假阴性,因此当查询返回 a row is not in a file时,该行肯定不在文件中。

正常情况下,错误率为 0.01(由 io.storefile.bloom.error.rate设置配置),因此 Bloom Filter 报告 StoreFile 包含行的可能性为 1%,但事实并非如此。 降低错误率需要更多的空间来存储 Bloom filter。

默认情况下,Bloom Filter 处于关闭状态,即 HBase 在创建 StoreFiles 时不会存储 Bloom Filter。 它可以基于列族进行配置,以启用行级或行+列级布隆过滤器。 行级 Bloom 过滤器用于测试 StoreFile 中是否包含行键,而行+列 Bloom 过滤器可以告诉 HBase StoreFile 是否包含特定单元。 行+列级布隆过滤器占用更多磁盘空间,因为单元格条目比行条目大得多。

即使启用了 Bloom Filter,单个 GET 操作也可能无法立即获得性能,因为 HBase 是并行读取数据的,并且延迟受磁盘 I/O 速度的限制。 但是,由于加载的块数量大大减少,因此显著提高了总体吞吐量,尤其是在负载较重的集群中。

另一个优点是使用 Bloom Filter 可以提高块缓存率。 启用 Bloom Filter 后,HBase 可以加载更少的块来获取客户端请求的数据。 由于不加载不必要的块,因此包含客户端实际请求的数据的块有更多机会保留在块缓存中。 这提高了整个群集的读取性能。

缺点是,Bloom Filter 中的每个条目都使用大约一个字节的存储空间。 如果您有较小的单元(例如,包含键/值信息开销的 20 个字节),则 Bloom 过滤器将是文件的 1/20。 另一方面,如果您的平均单元格大小为 1KB,则 Bloom Filter 的大小约为文件大小的 1/1000。 假设 StoreFile 是 1 GB,那么筛选器只需要 1 MB 的存储空间。 因此,我们建议您对小型单元格列族禁用布隆过滤器,并始终为中型或大型单元格系列启用布隆过滤器。

如上所述,HBase 支持行级和行级+列级 Bloom 过滤器。 使用哪一种取决于您的数据访问模式。 返回到我们的示例 HStore,只有几个 StoreFiles 保存 row-D。 当 row-D的单元格被批量更新时,就会发生这种情况。 如果一行中的大多数单元格一起更新,则行级筛选器更好。 相反,就像我们示例中的 row-F一样,如果更新分布在多个 StoreFiles 中,并且大多数 StoreFiles 包含行的一部分,则建议使用行+列筛选器,因为它能够识别哪个 StoreFile 包含您所请求的行的确切部分。但是,您还需要考虑数据读取模式。 很明显,如果您总是请求整个行,行+列筛选器就没有意义了。 例如,要加载整个 row-F,区域服务器无论如何都需要加载所有四个 StoreFiles。 当您的读取模式是只加载一行的几列时,例如,只加载 StoreFile4 中的 row-F列,那么行+列筛选器很有用,因为区域服务器将跳过加载其他 StoreFiles。

备注

请注意,HFile 版本 1 是 HBase 0.92 之前版本的默认 HFile 格式,Bloom 过滤器可以容纳的元素数是最大的。 此数字由 io.storefile.bloom.max.keys属性控制,默认值为 128M 键。 如果 StoreFile 中的单元格太多,可能会超过此数字。 您需要使用行级筛选器来减少 Bloom 筛选器中的键数。

Bloom Filter 是提高集群整体性能的有效方法。 行级 Bloom 筛选器在许多情况下都可以很好地工作;您应该将其作为首选,并且只有在行级筛选器不适合您的使用时才考虑行+列 Bloom 筛选器。

还有更多...

您还可以通过 HBase Shell 使用 alter命令更改现有表的列族的 Bloom filter 属性。 它将应用于更改后创建的 StoreFiles。