HBase管理指南

978-7-115-31981-4
作者: 蒋燚峰
译者: 苏南
编辑: 汪振
分类: HBase

图书目录:

详情

本书是一部实用性的操作指南,它首先会讲解如何建立一个完全分布式的HBase集群以及如何将数据移到该集群中。你还会学到如何使用各种工具来完成日常管理工作,以及有效地管理和监控集群,以使集群达到最佳的性能。

图书摘要

版权信息

书名:HBase管理指南

ISBN:978-7-115-31981-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    蒋燚峰

  译    苏 南

  责任编辑 汪 振

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright ©2012 Packt Publishing. First published in the English language under the title

HBase Administration Cookbook

All Rights Reserved.

本书由美国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。

版权所有,侵权必究。


本书通过详尽的操作步骤以及贴近实际使用的案例说明,帮助读者轻松掌握管理HBase所需的各项技能。本书是一部实用性很强的操作指南,主要介绍如何建立一个完全分布式的HBase集群并将数据转移到该集群中,以及如何使用各种工具来完成日常的管理工作,有效管理和监控集群以使其达到最佳的性能,最终还将讲解如何安装Hadoop集群、如何配置Hadoop,使其与HBase进行协作和如何调整Hadoop的性能。

本书适合使用HBase进行数据库开发的高级数据库研发人员阅读使用。


蒋燚峰(Yifeng Jiang)是日本最大的电子商务公司乐天(Rakuten)公司的Hadoop和HBase管理员和开发人员。他毕业于中国科学技术大学,获得了信息管理系统专业的学士学位。在其专业软件工程师职业生涯的开始阶段,他专注于Java开发。

从2008年开始,他一直在寻找Hadoop方面的项目。2009年,他在上一家公司领导了一个使用Hadoop和Hive来开发的显示广告方面数据基础设施项目。

2010年,他加入了乐天公司,设计并实现了一个基于Hadoop和HBase的大型商品排名系统。他还是该公司Hadoop团队的一员,该团队负责多个Hadoop/HBase集群的运维工作。


2011年9月,当Packt出版公司第一次问我是否有兴趣写一本关于HBase管理的书时,我一点也不知道这将会是一种多大的工作压力(当然,这其中也有很多的乐趣)。

现在,这本书终于完成了。我要感谢那些帮助过我的人,没有他们,本书也不可能完成。

首先,我要感谢HBase的开发者们,他们给我们带来了这样一个伟大的软件。感谢他们曾在邮件列表上为我的很多问题提供了很好的解答,感谢他们努力地完成了各项功能和文档。

我还要感谢Packt出版公司的团队,感谢他们最开始联系我写作本书,并一直对我进行支持、指导和反馈。

非常感谢乐天公司(Rakuten),我所就职的这家公司给我提供了一个使用HBase的工作环境,因此我才有机会写作本书。

感谢Michael Stack,谢谢你对本书进行的快速审校。

感谢本书的审校者——Michael Morello、河野达人(Tatsuya Kawano)、滨野贤一朗(Kenichiro Hamano)、山下真一(Shinichi Yamashita)和岩崎正刚(Masatake Iwasaki)。

感谢香川洋太郎(Yotaro Kagawa):感谢你一直以来对于我和我的家庭的支持。

感谢信萍和铃音:谢谢你们的支持和耐心,我爱你们!


岩崎正刚(Masatake Iwasaki) 是一名NTT数据公司的软件工程师,专门提供Hadoop、HBase以及PostgreSQL等开源软件方面的技术咨询。

河野达人(Tatsuya Kawano)是HBase在日本的贡献者和传播者。2010年以来,他一直在帮助日本的Hadoop和HBase社区不断成长。

目前,他在为Gemini Mobile Technologies工作,是其研发部的一名软件工程师。他还开发了一个与S3 API完全兼容的云存储平台Cloudian和一个开源的分布式键值对存储系统Hibari DB。

他是日本2012年出版的《NoSQL基本知识》一书的合作作者,该书向初学者们介绍了包括HBase、Cassandra、Riak、MongoDB和Neo4j在内的16种NoSQL产品。

20世纪90年代中后期,他曾在纽约学习平面设计。他喜欢玩3D电脑绘图,就如同他喜爱开发高可用、可伸缩的存储系统一样。

Michael Morello持有分布式计算和人工智能的硕士学位。他是一名资深的高级Java/JEE开发人员,具有深厚的UNIX和Linux背景。他的研究领域大多与大型系统和专门解决可伸缩性、性能和高可用性问题的新兴技术有关。

我想感谢我的妻子和我的小天使,感谢她们的爱和支持。

山下真一(Shinichi Yamashita)是NTT数据公司OSS专业服务部门在日本的首席工程师。他拥有超过7年在软件和中间件(Apache、Tomcat、PostgreSQL、Hadoop的生态系统)工程开发方面的经验。

他在日本也已经出版过了几本关于Hadoop的著作。

我要感谢我的同事们。


作为一个开源、分布式的大数据存储系统,HBase可以处理记录数达几十亿条、字段数达数百万个的大数据,并且只需依靠一些由商品化硬件而构成的集群就可以运行。如果你正在寻找一种能够实时存储和访问海量数据的方法,那么你就必须研究一下HBase。

《HBase管理指南》一书提供了一些实用的例子和一些简单详尽的操作步骤,使你可以轻松地管理HBase。本书的章节涵盖了在云计算环境中管理一个完全分布式的高可用HBase集群所需的各种过程。处理如此海量的数据意味着建立起一套有组织、可管理的过程至关重要,本书将帮助你做到这一点。

本书是一部实用性的操作指南,它首先会讲解如何建立一个完全分布式的HBase集群以及如何将数据转移到该集群中。你还将学到如何使用各种工具来完成日常管理工作,以及有效地管理和监控集群,以使集群达到最佳的性能。理解Hadoop与HBase之间的关系能帮你最好地利用HBase,因此本书也将讲解如何安装Hadoop集群,如何配置Hadoop使之可与HBase进行协作、以及如何调整它们的性能。

第1章HBase集群安装:本章将介绍HBase集群的安装方法,首先将介绍基本的单机运行模式的HBase实例的安装方法,然后介绍如何在Amazon EC2上安装完全分布式运行的高可靠性HBase集群。

第2章数据迁移:本章将从简单的任务开始,首先介绍如何使用Put AP将数据从MySQL导入到HBase中;然后,介绍如何使用importtsv和批量加载工具将TSV数据文件加载到HBase中。我们还将使一个MapReduce的例子来导入其他文件格式的数据。其中包括将数据直接放入到一张HBase表中,然后在将其写到HDFS(Hadoop分布式文件系统)文件系统内的HFile格式的文件中。本章的最后一节将介绍如何在将数据加载到HBase之前预创建一些区域。

本章附带有几个用Java编写的示例代码。我们假设你已具备了一些基本的Java知识,所以本章没有解释如何对各节中的Java示例代码进行编译和打包。

第3章使用管理工具:本章将介绍一些管理工具的用法,比如HBase的Web用户界面、HBase Shell、HBase hbck等。我们会解释这些工具的用途以及如何使用它们来完成特定的任务。

第4章HBase数据备份和恢复:在本章中,我们将介绍如何使用不同方法来对HBase的数据进行备份以及各种方法的优点和缺点,还会介绍如何根据你的数据集的规模、资源和需求来选择应该使用何种方法。

第5章监控及诊断:本章将介绍如何使用Ganglia、OpenTSDB、Nagios以及其他一些工具来对HBase集群进行监控和故障诊断。我们还是从一个简单的任务开始,首先介绍如何显示HBase表的磁盘利用率;然后,我们将安装和配置Ganglia来监控HBase的各项指标,并展示一个使用Ganglia图表的例子。我们还会安装OpenTSDB,它与Ganglia类似但更具可伸缩性,因为它构建在HBase之上。

我们将安装Nagios来检查一切我们想要检查的指标,包括HBase的相关守护进程的健康状况、Hadoop/HBase的日志、HBase的不一致问题、HDFS的健康状况和磁盘空间的利用率。

热点区域问题是一个频繁被人问及的问题,本章的最后一节将介绍一种诊断和修复它的方法。

第6章维护和安全:本章前6节的内容主要针对HBase的各种维护工作,比如发现并纠正故障点、改变集群的规模、进行配置修改等。

本章还将讨论安全性的问题。本章的后三节将介绍安装Kerberos并使用Kerberos来设置HDFS的安全机制,并且最终建立一种安全的HBase客户端访问方式。

第7章故障排查:本章将研究几个最常遇到的问题。我们将介绍这些问题的错误消息、发生原因以及如何使用故障排除工具来解决这些问题。

第8章基本性能调整:本章将介绍如何对HBase进行调优,以获得更好的性能。本章还将包括一些对其他调优点进行调优的内容,比如Hadoop的配置、JVM垃圾回收的设置以及操作系统的内核参数等。

第9章高级配置和调优:这是本书中又一个关于性能优化的章节。第8章介绍了一些通过调整Hadoop、操作系统设置、Java和HBase本身来改进HBase集群的整体性能的方法。这是可适用于很多场合的一般性改进方法。本章将介绍一些更具针对性的调优方法,其中有些适用于写密集的集群,而另一些则以提高集群的读性能为目的。

本书的每一节都会列出你需要的所有东西。

本书所需软件的基本清单如下:

本书的读者是HBase的管理员和开发人员,同时本书对Hadoop的管理员也有所帮助。读者并非一定要有HBase的使用经验,但应该对Hadoop和MapReduce有基本的了解。

本书使用了一些不同样式的文本来区分不同种类的信息。下面是这些不同样式的一些例子以及对于它们各自含义的解释。

正文中的代码将以这种方式显示:“可以使用stop-hbase.sh脚本来关闭Hbase”。

一段代码将以如下方式显示:

nameserver 10.160.49.250 #private IP of ns
search hbase-admin-cookbook.com #domain name

当需要你注意一段代码的某一部分时,我们会把相关的行或项以粗体的方式显示出来:

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

所有命令行的输入或输出都以如下形式显示:

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

新术语重要的文字会以粗体显示。你在电脑屏幕上看到文字(比如在菜单或对话框中出现的文字)也将会以粗体显示,比如:“在AWS管理控制台中验证其已经启动”。


警告或重要的说明将会显示在这样一个方框中。


提示和技巧将会显示在这样一个方框中。

我们随时欢迎来自本书读者的反馈。请让我们知道您的想法——您喜欢或可能不喜欢本书的哪些内容。读者的反馈非常重要,它能指引我们撰写出真正对读者有价值的书籍。

对于一般性的反馈,您只需将电子邮件发送至feedback@packtpub.com就可以了,请在您的邮件标题中注明本书的名字。

如果您具有某一领域的专长,并且有兴趣写一本书或者对某一本书作出些贡献,那么请您阅读一下我们的作者指南(www.packtpub.com/authors)。

我们还为您提供了如下一些东西来帮您从本次购买中获得最大的收益。

您可以从您在http://www.packtpub.com的账户中下载到您所购买的所有Packt图书的示例代码文件。如果您是从其他地方购买的本书,那么您可以访问http://www. packtpub.com/support进行注册,然后我们会以电子邮件的方式直接将这些文件发送给您。

虽然我们已尽力确保本书内容的准确性,但错误依旧在所难免。如果你发现了书中的错误——无论是文字还是代码中的错误,都请报告给我们,我们将不胜感激。您这样做可以避免其他读者遭受挫折,并且能帮助我们改进本书的后续版本。如果您发现了错误,请以这种方式报告给我们:访问http://www.packtpub.com/support,选择您所购买的书,点击链接“errata submission form”,然后输入您勘误的详细内容。在验证完你所提交的勘误之后,我们将接受您的提交并将此勘误上传到我们的网站中或添加到该书勘误区的已有勘误列表中。

互联网上的盗版问题是所有媒体都在面临的一个将长期存在的问题。Packt出版公司非常重视保护自己的版权和许可权。如果您在互联网上遇到了本公司作品的任何形式的非法拷贝,都请您立即将其地址或网站名称提供给我们,以便于我们提出赔偿。

请将涉嫌盗版的链接发送至copyright@packtpub.com与我们联系。

我们非常感谢您在保护作者和出版公司方面所提供的帮助,这样我们才能继续为您提供一些更有价值的内容。

无论您对本书有任何方面的问题,都可以通过questions@packtpub.com与我们联系,我们将尽最大努力来解决您的问题。


本章内容:

本章将介绍HBase集群的安装方法,首先将介绍基本的单机运行模式的HBase实例的安装方法,然后介绍如何在Amazon EC2上安装完全分布式运行的高可靠性HBase集群。

根据Apache HBase主页上的定义:

HBase是Hadoop上的数据库。它适合在需要对大数据进行随机且实时读写的情况下使用。其目标是在基于商品化硬件构建的集群之上存储那些非常大的表——比如那些有数百万个字段和数十亿条记录的大表。

HBase可以在任何文件系统上运行。比如说,无论是在EXT4本地文件系统、Amazon S3(Amazon Simple Storage Service),还是Hadoop分布式文件系统(HDFS)上,都可以运行HBase。 HDFS是Hadoop首选的分布式文件系统,因此大多数完全分布式的HBase集群都运行在HDFS文件系统上,所以我们将首先介绍一下如何安装Hadoop。

Apache ZooKeeper是一个开源软件,它能够提供一种具有高可靠性的分布式的协调服务。分布式的HBase需要运行一个ZooKeeper集群。

作为一种运行在Hadoop上的数据库,HBase需要同时打开很多个文件。 为了使HBase能够顺畅运行,我们需要修改一些Linux内核参数的设置。

一个完全分布式的HBase集群都有一个或多个主节点(HMaster)和许多从节点(RegionServer),其中主节点用于协调整个集群,从节点用于处理实际的数据存储和要求。图1-1显示的是一个典型的HBase集群结构。

图1-1 典型的HBase集群结构

HBase可以同时运行多个主节点,它会使用Zookeeper来监控这些主节点和实现这些主节点之间的故障转移。不过,由于HBase在底层使用了HDFS来作为它的文件系统,所以如果HDFS宕机了,HBase也自然要宕机。HDFS的主节点(我们称之为NameNode)是HDFS的单点故障(SPOF,Single Point Of Failure),因此也是HBase集群的单一故障点。幸运的是,NameNode在软件方面非常健壮和稳定。此外,HDFS的研发团队也正在努力开发具有真正高可靠性的NameNode,Hadoop的下一个主要发布版本可能就会包含这一特性。

在1.2~1.8节中,我们将介绍如何让HBase与它所需要使用的那些软件一起工作,构建起一个完全分布式的HBase集群。最后一节将介绍一个较为高级的话题——如何避免集群的单点故障问题。

下面,我们就开始介绍如何安装一个单机运行的HBase实例,之后再来演示如何在Amazon EC2上安装一个分布式的HBase集群。

HBase有两种运行模式:单机运行(Standalone)模式和分布式运行(Distributed)模式。单机运行模式是HBase的默认模式。在单机模式中,HBase会使用一个本地文件系统来代替HDFS,并且在同一个JVM上运行所有的HBase守护进程和HBase管理的ZooKeeper实例。

本节将介绍单机HBase的安装。内容包括:安装HBase、以单机模式启动HBase、在HBase Shell中创建一张表、插入记录、清除记录以及关闭单机模式HBase实例。

你需要有一台Linux机器来运行这组软件栈。不建议在Windows系统之上运行HBase。本书将以Debian 6.0.1(Debian Squeeze)为例进行讲解,因为我所任职的Rakuten公司就有多个运行在Debian上的Hadoop/HBase集群,而6.0.1是目前最新一版的Amazon Machine Image(AMI)。你可以从http://wiki.debian.org/Cloud/AmazonEC2Image下载它。

HBase是用Java编写的,因此需要先安装Java。HBase只能运行在Oracle的JDK上,所以在安装时不要使用OpenJDK。尽管Java 7已经发布了,但我们并不建议使用,因为它还需要更多的时间来测试。可以从下列地址中下载到最新版的Java SE 6:http://www.oracle.com/technetwork/java/javase/downloads/index.html

执行下载到的二进制文件,安装Java SE 6。本书将使用/usr/local/jdk1.6来作为JAVA_HOME

root# ln -s /your/java/install/directory /usr/local/jdk1.6

我们将新建一个名为Hadoop的用户,用来作为所有HBase/Hadoop守护进程和文件的所有者。我们还要把所有HBase文件和数据都存储在/usr/local/hbase目录下。

root# useradd hadoop
root# mkdir /usr/local/hbase
root# chown hadoop:hadoop /usr/local/hbase

从HBase官方网站(http://www.apache.org/dyn/closer.cgi/hbase/)上可以下载到HBase的最新稳定版本。

单机运行模式HBase实例的安装步骤如下。

1.下载HBase源码包,然后将其解压缩到我们为HBase准备的根目录中。为了使安装过程更为简单,我们还要设置一个HBASE_HOME环境变量。完成这两组操作的命令如下:

root# su - hadoop
hadoop$ cd /usr/local/hbase
hadoop$ tar xfvz hbase-0.92.1.tar.gz
hadoop$ ln -s hbase-0.92.1 current
hadoop$ export HBASE_HOME=/usr/local/hbase/current

2.在HBase的环境设置文件中设定环境变量JAVA_HOME,命令如下:

hadoop$ vi $HBASE_HOME/conf/hbase-env.sh
    # The java implementation to use. Java 1.6 required.
    export JAVA_HOME=/usr/local/jdk1.6

3.为HBase创建一个用于存储数据的目录,然后将该路径设置在HBase配置文件(hbase-site.xml)的 <configuration>标签内。此步操作所使用的命令如下:

hadoop$ mkdir -p /usr/local/hbase/var/hbase
hadoop$ vi /usr/local/hbase/current/conf/hbase-site.xml

  <property>
    <name>hbase.rootdir</name>
    <value>file:///usr/local/hbase/var/hbase</value>
  </property>

4.使用如下命令以单机运行模式启动HBase:

hadoop$ $HBASE_HOME/bin/start-hbase.sh
starting master, logging to /usr/local/hbase/current/logs/hbase- hadoop- master-master1.out

5.使用如下命令通过HBase Shell连接到正在运行的HBase上:

hadoop$ $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.1, r1298924, Fri Mar 9 16:58:34 UTC 2012

6.创建一张表并再插入一些值,以此来验证HBase是否安装正确。使用如下命令创建一张名为test的表,并为该表创建一个名为cf1的列族。

hbase(main):001:0> create 'test', 'cf1'
0 row(s) in 0.7600 seconds

(1)使用如下命令列出新创建的这张表:

hbase(main):002:0> list
TABLE
test
1 row(s) in 0.0440 seconds

(2)使用如下命令在新创建的表中插入一些值:

hbase(main):003:0> put 'test', 'row1', 'cf1:a', 'value1'
0 row(s) in 0.0840 seconds
hbase(main):004:0> put 'test', 'row1', 'cf1:b', 'value2'
0 row(s) in 0.0320 seconds

7.使用scan命令验证我们已将数据插入到了HBase中:

hbase(main):003:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf1:a, timestamp=1320947312117, value=value1
row1 column=cf1:b, timestamp=1320947363375, value=value2
1 row(s) in 0.2530 seconds

8.现在,使用disabledrop命令清除掉前面的操作。

(1)使用如下命令禁用表 test

hbase(main):006:0> disable 'test'
0 row(s) in 7.0770 seconds

(2)使用如下命令删除表test

hbase(main):007:0> drop 'test'
0 row(s) in 11.1290 seconds

9.使用如下命令退出HBase Shell:

hbase(main):010:0> exit

10.执行stop脚本关闭HBase实例:

hadoop$ /usr/local/hbase/current/bin/stop-hbase.sh
stopping hbase.......

通过上面的操作,我们在单台服务器上完成了HBase 0.92.1的安装工作。我们使用了一个名为current的符号链接来指向其安装包的解压缩目录,这样可以使将来的升级更为方便。

为了让HBase能够知道Java的安装位置,我们在HBase的环境设置脚本HBase-env.sh中设置了JAVA_HOME。该脚本中还有一些关于Java堆和HBase守护进程的设置。本书的8~9章将对这些设置进行深入的讨论。

在第1步中,我们在本地文件系统中创建了一个目录,用于存储HBase的数据。在安装完全分布式的HBase时,需要将HBase配置为使用HDFS(而非使用本地文件系统)的方式。当我们在一台服务器上执行了start-hbase.sh脚本时,在该服务器上将会启动一个HBase的主守护进程(HMaster)。因为这一次我们没有配置区域服务器,所以HBase也只是在同一个JVM中启动了一个从守护进程(HRegionServer)。

正如“1.1 简介”一节提到过的那样,HBase需要使用ZooKeeper来作为自己的协调服务。而你可能也已经注意到了,前面这些步骤中并没有启动ZooKeeper。这是因为HBase在默认情况下会启动和管理一个它自己的ZooKeeper仲裁团。

接下来,我们通过HBase Shell连接上了HBase。我们可以使用HBase Shell来管理HBase集群、访问HBase中的数据并完成很多其他的工作。这一次,我们创建了一张名为test的表,向HBase中插入了一些数据,对test表进行了扫描,然后禁用并删除了这张表,最后退出了shell。

我们可以使用HBase的stop-hbase.sh脚本来关闭HBase。该脚本会将HMaster和HRegionServer守护进程都关闭。

Amazon EC2Amazon Elastic Compute Cloud)是一种可在云计算环境中提供具有可伸缩行的计算能力的Web服务。通过使用Amazon EC2,我们只需用很低的成本就可以轻松地体验到完全分布式运行的HBase的运行模式。本书中用来展示HBase各种管理操作的所有服务器都运行在Amazon EC2上。

本节将介绍如何安装Amazon EC2环境,这是在EC2上安装HBase的准备工作。我们将建立在Amazon EC2上一个名称服务器和客户端。你也可以使用其他一些托管服务(比如Rackspace)或使用一些真正的服务器来建立HBase集群。

你需要在http://aws.amazon.com/上注册或创建一个Amazon Web ServiceAWS)账户。

管理实例需要使用一些EC2命令行工具。你可以按照如下网页中的操作说明来下载并安装这些命令行工具。

http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?SettingUp_CommandLine.html

为了能够登录到EC2实例上,你需要一个公钥/私钥对。你可以使用该网页中的操作指令来生成自己的密钥对,然后将你的公共密钥上传到EC2上。

http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/generating-a-keypair.html

在你登录到一个实例上之前,你必须进行访问授权。下面这个链接中包含有如何向默认安全组添加规则的操作说明。

http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/addingsecurity-group-rules.html

在完成上述这些步骤之后,请按照下面的清单进行一下检查,以确保一切都已准备就绪。

$ cat ~/.bashrc
export EC2_HOME=~/opt/ec2-api-tools-1.4.4.2
export PATH=$PATH:$EC2_HOME/bin
export EC2_PRIVATE_KEY=~/.ec2/pk-OWRHNWUG7UXIOPJXLOBC5UZTQBOBCVQY.pem
export EC2_CERT=~/.ec2/cert-OWRHNWUG7UXIOPJXLOBC5UZTQBOBCVQY.pem
export JAVA_HOME=/Library/Java/Home
export EC2_URL=https://ec2.us-west-1.amazonaws.com

我们需要先通过EC2命令行工具来导入自己的EC2密钥对,然后才能管理这些EC2实例。

$ ec2-import-keypair your-key-pair-name --public-key-file ~/.ssh/id_rsa.pub

键入如下这条命令来验证设置是否正确。

$ ec2-describe-instances

如果上述这些设置都已正确完成,该命令将显示出各实例的设置信息,所显示的内容应与你在之前使用命令进行配置时的信息类似。


下载示例代码

您可以从您在http://www.packtpub.com的账户中下载到您所购买的所有Packt图书的示例代码文件。如果您是从其他地方购买的本书,您可以访问http://www.packtpub.com/support进行注册,然后我们会以电子邮件的方式直接将这些文件发送给您。

最后一项准备工作是找到一个合适的AMI。我们可以在http://wiki.debian.org/Cloud/AmazonEC2Image上找到一个已注册的Debian AMI。

如果只是为了体验一下HBase,那么使用以EBS为后端的32位AMI是最具成本效益的选择。要确保你所选择的是为你所属地区而提供的AMI。由于本书所使用的地区是US-West(us-west-1),所以对我们来说,该AMI的ID是ami-77287b32。这是一个32位的小EC2实例。小实例很便宜,因此适合用来在EC2中对HBase进行体验。如果是生产环境,建议你至少使用带EBS的高内存超大实例(High-Memory Extra Large Instance)或一个真正的服务器。

请按照以下步骤操作你的EC2实例,以便为使用HBase做好准备。我们将启动两个EC2实例,一个作为DNS/NTP服务器,另一个作为DNS/NTP客户端。

1.启动一个微实例来作为DNS/NTP服务器。本书后面的章节将一直使用ns1.hbase-admin-cookbook.comns1)来作为该服务器的完全限定域名(FQDN)。

$ ec2-run-instances ami-77287b32 -t t1.micro -k your-key-pair

2.启动一个小实例作为DNS/NTP客户端。本书后面的章节将一直使用client1. hbase-admin-cookbook.comclient1)作为该客户端的FQDN。

$ ec2-run-instances ami-77287b32 -t m1.small -k your-key-pair

3.在AWS Management Console中验证这些实例是否都已启动,也可以通过键入以下命令进行验证。

$ ec2-describe-instances

在该命令的输出中,应该能够看到两个实例。从ec2-describe-instances命令的输出结果或AWS Management Console中,可以看到这些已启动实例的公网DNS。它们的DNS应该是一些格式大致为ec2-xx-xx-xxx-xx.us-west-1.compute.amazonaws. com的值(如图1-2所示)。

图1-2 EC2实例的公网DNS

4.使用如下命令通过SSH登录实例。

$ ssh root@ec2-xx-xx-xxx-xx.us-west-1.compute.amazonaws.com

5.在为服务器安装软件包之前,先使用如下命令更新软件包索引文件。

root# apt-get update

6.使用如下命令将实例所属时区修改为你所在的时区。

root# dpkg-reconfigure tzdata

7.使用如下命令在DNS服务器上安装NTP服务器守护程序。

root@ns# apt-get install ntp ntp-server ntpdate

8.使用如下命令在客户机/服务器上安装NTP客户端。

root@client1# apt-get install ntp ntpdate

9.分别修改/etc/ntp.conf配置文件,让ns1作为NTP服务器运行,让client1作为一个以ns1为服务器的NTP客户端来运行。

因为在NTP设置中没有什么HBase特别要求的配置,所以这里就不对此进行详述了。在本书的示例源码中,可以找到服务器端和客户端不同ntp.conf文件的示范样本。

10.在ns1上安装BIND9作为DNS服务器。

root@ns# apt-get install bind9

BIND9还需要进行一下配置,以使之成为内部域名查找时的首选主服务器和外部域名查找时的缓存服务器。DNS服务器也需要进行配置,以使其他EC2实例可以更新自己在DNS服务器上的记录。

这部分内容并不属于本书所要讲述的范畴,所以我们将不对此进行详述。BIND9配置文件的样本,请参阅本书所附带的示例源码。

11.对于client1,只需配置其把ns1作为自己的DNS服务器就可以了。

root@client1# vi /etc/resolv.conf
nameserver 10.160.49.250 #private IP of ns
search hbase-admin-cookbook.com #domain name

12.自动更新DNS主机名。将客户端EC2实例的用户数据设为其主机名。打开AWS Management ConsoleMy Instances页,在实例列表中选中client1并将其关闭,然后单击Instance Actions | View | Change User Data。在图1-3所示的弹出页面输入你想要给该实例使用的主机名(在本例中为client1)。

图1-3 将用户数据设为客户端EC2实例的主机名

13.创建一个使用该用户数据来更新DNS服务器上该客户端的记录的脚本。

root@client1# vi ec2-hostname.sh
#!/bin/bash
#you will need to set up your DNS server to allow update from this key
DNS_KEY=/root/etc/Kuser.hbase-admin-cookbook.com.+157+44141.private
DOMAIN=hbase-admin-cookbook.com

USER_DATA='/usr/bin/curl -s http://169.254.169.254/latest/userdata'
HOSTNAME='echo $USER_DATA'
#set also the hostname to the running instance
hostname $HOSTNAME

#we only need to update for local IP
LOCIP='/usr/bin/curl -s http://169.254.169.254/latest/meta-data/local-ipv4'
cat<<EOF | /usr/bin/nsupdate -k $DNS_KEY -v
server ns.$DOMAIN
zone $DOMAIN
update delete $HOSTNAME.$DOMAIN A
update add $HOSTNAME.$DOMAIN 60 A $LOCIP
send
EOF

14.最后,在rc.local文件中添加下列脚本,以使ec2-hostname.sh可以在启动时由rc.local调用执行。

root@client1# vi /etc/rc.local
sh /root/bin/ec2-hostname.sh

首先,我们启动了两个实例,一个微实例作为DNS/NTP服务器,一个小实例作为其客户端。为了能给其他实例提供名称服务,DNS名称服务器必须一直保持运行。使用微实例可以降低你的EC2成本。

在第3步中,我们设置好了NTP服务器和客户端。我们让NTP服务器与DNS服务器运行在同一个实例上,然后在其他所有实例上运行NTP客户端。


注意:

请确保HBase集群中各成员的时钟上基本一致。

EC2实例可以按需启用和关闭,并且还无需为已关闭的实例付费。但重新启动EC2实例会使实例的IP地址发生改变,这会给HBase的运行带来麻烦。我们可以运行一个DNS服务器来为HBase集群中所有EC2实例提供名称服务,这样就可以解决IP地址变化的问题。我们可以让其他EC2实例在每次重新启动时都更新一下自己在DNS服务器上的名称记录。

这就是我们在第4步和第5步中做的事情。第4步是常规的DNS设置。在第5步中,我们把实例的名称保存在了该实例的用户数据属性中,这样做的目的是:在该实例重新启动后,我们仍可以使用EC2 API来找回它的名字。此外,我们还可以通过EC2 API来获得该实例的私有IP地址。有了这些数据,我们就可以在实例每次重启时向DNS服务器发送一个DNS update命令更新该实例名称对应的IP地址。这样,我们就可以始终使用一个固定主机名来访问该实例了。

只有DNS实例需要一直保持运行。在不需要运行HBase集群时,我们可以将其他所有实例都关掉。

完全分布式的HBase要运行在HDFS之上。对于完全分布式的HBase集群来说,其主守护进程(HMaster)通常要运行在HDFS主节点所在的服务器(NameNode)上,而其从守护进程(HRegionServer)要运行在HDFS从节点所在的服务器(我们称之为DataNode)上。

HBase并不必须使用Hadoop MapReduce,所以我们也不需要启动MapReduce的守护进程。本节也会对MapReduce的安装进行介绍,因为你毕竟有可能要在HBase上运行MapReduce。对于一个小型的Hadoop集群来说,我们通常会在NameNode服务器上运行一个MapReduce主守护进程(JobTracker),在各Datanode服务器上运行MapReduce从守护进程(TaskTracker)。

本节将介绍Hadoop的安装。我们将用一个主节点(master1)来运行NameNode和JobTracker。另外,我们还将安装3个从节点(slave1slave3),这三个节点将分别用来运行DataNode和TaskTracker。

你需要4个小EC2实例,你可以通过下面这条命令来获得4个小EC2实例。

$ec2-run-instances ami-77287b32 -t m1.small -n 4 -k your-key-pair

对于所有这4个实例,必须如“1.3 Amazon EC2的安装及准备”一节所述的那样进行正确的设置。除了NTP和DNS的设置之外,所有服务器上还都必须安装Java。

我们将使用hadoop用户来作为所有Hadoop守护进程和文件的所有者。所有Hadoop文件和数据都将存储在/usr/local/hadoop目录下。因此,要预先在所有服务器上添加hadoop用户并创建/usr/local/hadoop目录。

我们还要安装一个Hadoop客户端节点。我们将使用前一节中已配置好的client1节点来作为客户端节点。因此,在client1上也要安装Java并准备好hadoop用户和目录。

安装一个完全分布式运行的Hadoop集群的步骤如下。

1.为了能SSH登录到集群的所有节点上,需要在主节点上生成hadoop用户的公共密钥。

hadoop@master1$ ssh-keygen -t rsa -N ""

这个命令将在主节点上为hadoop用户创建一个公共密钥:~/.ssh/id_rsa.pub

2.在所有从节点和客户端节点中添加hadoop用户的公共密钥,以使我们可以从主节点对该节点进行SSH登录。

hadoop@slave1$ mkdir ~/.ssh
hadoop@slave1$ chmod 700 ~/.ssh
hadoop@slave1$ cat >> ~/.ssh/authorized_keys

3.复制上一步生成的hadoop用户公共密钥的内容,将其粘贴到~/.ssh/ authorized_keys文件中。然后,使用如下命令修改该文件的权限。

hadoop@slave1$ chmod 600 ~/.ssh/authorized_keys

4.从Hadoop的官方网站(http://www.apache.org/dyn/closer.cgi/hadoop/common/)获取最新、稳定且支持HBase的Hadoop版本。在写作本章时,支持HBase的Hadoop的最新稳定版本是1.0.2版。下载压缩包并将其解压到我们为Hadoop而设的root目录中,然后再添加一个符号链接和环境变量。

hadoop@master1$ ln -s hadoop-1.0.2 current
hadoop@master1$ export HADOOP_HOME=/usr/local/hadoop/current

5.在主节点上创建如下几个目录。

hadoop@master1$ mkdir -p /usr/local/hadoop/var/dfs/name
hadoop@master1$ mkdir -p /usr/local/hadoop/var/dfs/data
hadoop@master1$ mkdir -p /usr/local/hadoop/var/dfs/namesecondary

6.如果不使用MapReduce,可以跳过下面几个步骤。如果使用MapReduce,则需要为其创建一个目录。

hadoop@master1$ mkdir -p /usr/local/hadoop/var/mapred

7.在Hadoop环境设置文件(hadoop-env.sh)中设置 JAVA_HOME

hadoop@master1$ vi $HADOOP_HOME/conf/hadoop-env.sh
export JAVA_HOME=/usr/local/jdk1.6

8.在core-site.xml中添加hadoop.tmp.dir属性。

hadoop@master1$ vi $HADOOP_HOME/conf/core-site.xml
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/usr/local/hadoop/var</value>
  </property>

9.在core-site.xml中添加fs.default.name属性。

hadoop@master1$ vi $HADOOP_HOME/conf/core-site.xml
  <property>
    <name>fs.default.name</name>
    <value>hdfs://master1:8020</value>
  </property>

10.如果你需要MapReduce,则需要在mapred-site.xml中添加mapred.job. tracker属性。

hadoop@master1$ vi $HADOOP_HOME/conf/mapred-site.xml
  <property>
    <name>mapred.job.tracker</name>
    <value>master1:8021</value>
  </property>

11.添加一个从服务器列表文件slaves

hadoop@master1$ vi $HADOOP_HOME/conf/slaves
slave1
slave2
slave3

12.将所有Hadoop文件从主节点同步到各客户端节点和从节点上。如果不是初次安装,则不需要同步${hadoop.tmp.dir}目录。

hadoop@master1$ rsync -avz /usr/local/hadoop/ client1:/usr/local/hadoop/
hadoop@master1$ for i in 1 2 3
 do rsync -avz /usr/local/hadoop/ slave$i:/usr/local/hadoop/
 sleep 1
done

13.在启动Hadoop之前,你需要格式化NameNode节点。该操作仅在初始安装时执行。

hadoop@master1$ $HADOOP_HOME/bin/hadoop namenode -format

14.在主节点上启动HDFS。

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

15.键入如下命令可访问你的HDFS。

hadoop@master1$ $HADOOP_HOME/bin/hadoop fs -ls /

你也可以通过浏览器来查看此HDFS的管理页面。请确保50070端口处于打开状态。访问http://master1:50070/dfshealth.jsp,便可看到图1-4所示的HDFS管理页面。

图1-4 HDFS管理页面

16.如果需要的话,在主节点上启动MapReduce。

hadoop@master1$ $HADOOP_HOME/bin/start-mapred.sh

现在,你就可以从浏览器中访问MapReduce的管理页面了。请确保50030端口处于打开状态。访问http://master1:50030/jobtracker.jsp,便可看到图1-5所示的MapReduce管理页面。

图1-5 MapReduce管理页面

17.若要停止HDFS,请在主节点上执行如下命令。

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

18.要停止MapReduce,请在主节点上执行如下命令。

hadoop@master1$ $HADOOP_HOME/bin/stop-mapred.sh

若要从主节点上启动/关闭远程从节点上的守护进程,必须用hadoop用户以无密码SSH登录的方式进行操作。这便是我们在第1步中做的事情。

HBase必须运行在一个支持持久化的sync实现的特殊HDFS上。如果在不支持持久化的sync实现的HDFS上运行HBase,那么在从服务器宕机时,HBase就有可能会丢失数据。在0.20.205以后的各版本(包括我们所选择的Hadoop1.0.2)中,Hadoop都支持此功能。

HDFS和MapReduce要使用本地文件系统来存储它们自己的数据。我们在第3步中创建了Hadoop所需要的目录,在第8步中将该路径设置在了Hadoop配置文件中。

在第9至11步中,我们对Hadoop进行了设置,以使它可以找到HDFS、JobTracker和从服务器。在启动Hadoop之前,需要将所有Hadoop目录和设置都同步到各个从服务器上。在第一次启动Hadoop(HDFS)时,需要对NameNode进行格式化。请注意,只需要在初始安装时做一次格式化就可以了。

执行到这一步,你就可以使用Hadoop的启动/停止脚本来启动/停止Hadoop了。在本例中,我们要将HDFS和MapReduce分开进行启动/停止,因为你有可能并不需要MapReduce。你也可以使用$HADOOP_HOME/bin/start-all.shstop-all.sh来同时启动/停止HDFSMapReduce

分布式的HBase需要运行一个ZooKeeper集群。HBase集群的所有节点和客户端都必须能够访问该ZooKeeper仲裁团。

此节将描述如何建立一个ZooKeeper集群。我们将仅为我们的HBase集群建立一个单机运行的ZooKeeper节点,但是如果是在生产环境中,就应该建立一个至少含有三个节点ZooKeeper仲裁团。此外,还要确保运行节点的个数是奇数。

我们将在本节的“1.5.4 补充说明”一节中讨论集群式ZooKeeper的安装。

首先,要确保在你的ZooKeeper服务器上已经安装好了Java。

我们将使用hadoop用户来作为所有ZooKeeper守护进程和文件的所有者。所有ZooKeeper文件和数据将被存储在/usr/local/ZooKeeper目录下,你需要预先创建好该目录。我们的ZooKeeper也将安装在master1上。

我们将在client1上安装一个ZooKeeper客户端。因此,在client1上也要安装Java并准备好hadoop用户和目录。

安装一个单机运行的ZooKeeper的步骤如下。

1.从ZooKeeper的官方网站(http://ZooKeeper.apache.org/releases.html#download)下载ZooKeeper的最新稳定版本。

2.下载其安装压缩包并将其解压缩到我们为ZooKeeper准备的根目录中。为了更便于安装,我们要设置一个ZK_HOME环境变量。在写作本书时,ZooKeeper的最新稳定版是3.4.3版。

hadoop@master1$ ln -s ZooKeeper-3.4.3 current
hadoop@master1$ export ZK_HOME=/usr/local/ZooKeeper/current

3.为ZooKeeper创建几个目录,用来存储的快照和事务日志。

hadoop@master1$ mkdir -p /usr/local/ZooKeeper/data
hadoop@master1$ mkdir -p /usr/local/ZooKeeper/datalog

4.创建$ZK_HOME/CONF/java.env文件,并在其中输入下列的Java设置。

hadoop@master1$ vi $ZK_HOME/conf/java.env
JAVA_HOME=/usr/local/jdk1.6
export PATH=$JAVA_HOME/bin:$PATH

5.复制ZooKeeper样本配置文件并进行如下一些修改,以设置ZooKeeper存储数据的位置。

hadoop@master1$ cp $ZK_HOME/conf/zoo_sample.cfg $ZK_HOME/conf/zoo.cfg

hadoop@master1$ vi $ZK_HOME/conf/zoo.cfg
dataDir=/usr/local/ZooKeeper/var/data
dataLogDir=/usr/local/ZooKeeper/var/datalog

6.将/usr/local/ZooKeeper下的所有文件从主节点同步到客户端上。在此初始安装时,不要同步${dataDir}${dataLogDir}目录。

7.在主节点上执行如下命令启动ZooKeeper。

hadoop@master1$ $ZK_HOME/bin/zkServer.sh start

8.连接到正在运行的ZooKeeper上,执行如下一些命令来验证安装是否正确。

hadoop@client1$ $ZK_HOME/bin/zkCli.sh -server master1:2181

[zk: master1:2181(CONNECTED) 0] ls /
[ZooKeeper]
[zk: master1:2181(CONNECTED) 1] quit

9.在主节点上执行如下命令关闭ZooKeeper。

hadoop@master1$ $ZK_HOME/bin/zkServer.sh stop

在本节中,我们安装了一个基本的单机运行的ZooKeeper实例。正如你看到的那样,安装非常简单,你所需要做的只是告诉ZooKeeper在哪里可以找到Java和在哪里保存自己的数据。

在第4步中,我们创建了一个名为java.env的文件,然后在该文件中记录了一些Java设置。你必须使用这个文件名,因为ZooKeeper(默认情况下)要从这个文件中读取Java设置。

ZooKeeper的配置文件名为zoo.cfg。你可以从ZooKeeper附带的示例文件复制出一个配置文件来。对于基本安装来说,那些默认设置就很适用。因为ZooKeeper在集群系统中一直起着核心作用,所以必须对其正确设置才能获得最佳的性能。

若要连接一个正在运行的ZooKeeper仲裁团,必须使用它的命令行工具,并且指定要连接的ZooKeeper服务器和端口。默认的客户端口是2181。如果使用默认端口,那么就不需要指定端口号。

我们把所有的ZooKeeper数据称为Znode。Znode在构造上就像一个文件系统的层次结构。ZooKeeper提供了一些命令,可以通过它的命令行工具访问或更新Znode。在命令行工具中键入help可以获得进一步的帮助信息。

HBase将ZooKeeper作为自己的协调服务来使用,因此ZooKeeper服务必须非常可靠。在生产环境中,你必须运行一个至少含三个节点的ZooKeeper集群。此外,还要确保运行节点的个数是奇数。

安装一个集群式ZooKeeper的过程与本节前面所示的过程基本相同。你可以先按照前面的步骤来安装好集群中的每一个节点。然后再在每个节点的zoo.cfg文件中添加如下一些设置,这样每一个节点就可以知道仲裁团中其他的每个节点了。

hadoop@node{1,2,3}$ vi $ZK_HOME/conf/zoo.cfg
   server.1=node1:2888:3888
   server.2=node2:2888:3888
   server.3=node3:2888:3888

另外,你还需要在${DATADIR}目录下放置一个myid文件。该myid文件由一行文本组成,内容是该节点的ID。因此,在node1节点的myid文件中,除了包含一行内容为1的文本以外,没有其他内容。


请注意,所有的ZooKeeper节点的时钟必须是同步的。你可以使用NTPNetwork Time Protocol网络时间协议)来同步时钟。

分别在该集群的每个节点上启动ZooKeeper。然后,你就可以使用下面这条命令从客户端连接到ZooKeeper集群上了。

$ zkCli.sh -server node1,node2,node3

在ZooKeeper集群中,只要有一半以上的节点能够正常运行,ZooKeeper就能正常工作。这意味着:在3个节点的集群中,只有一台服务器可以宕机。

HBase是运行在Hadoop上的数据库,和其他数据库一样,它也要同时打开很多个文件。Linux对于一个进程可打开文件描述符的个数有所限制。默认的限制是每个进程可打开1024个文件。为了使HBase能够顺畅运行,你需要调高启动HBase的那个用户允许打开的文件描述符的最大个数。在本书中,就是hadoop用户。

你还要调高Hadoop的nproc设置。nproc设置指定了用户可以同时启动的最大进程数量。如果nproc过低,就会遇到OutOfMemoryError这种错误。

本节将描述如何显示和更改这些内核参数。

请确保你在所有服务器上都有root权限。

你需要对集群中的所有服务器进行如下的内核参数设置修改。

1.以hadoop用户的身份登录并执行以下命令,以确认可打开文件数的当前限制。

hadoop$ ulimit -n
1024

2.使用-u选项的ulimit命令来查看最大进程数的当前设置。

hadoop$ ulimit -u
unlimited

3.以root用户身份进行登录并调高可打开文件数和nproc的限制。在limits.conf文件中添加下列设置。

root# vi /etc/security/limits.conf
hadoop soft nofile 65535
hadoop hard nofile 65535
hadoop soft nproc 32000
hadoop hard nproc 32000

4.为使上述修改生效,使用如下命令在/etc/pam.d/common-session文件中增加一行内容。

root# echo "session required pam_limits.so" >> /etc/pam.d/common-session

5.注销登录,再以hadoop用户的身份重新进行登录,然后再次确认这些设置的当前值,你应该能够看到前面的修改已经生效了。

hadoop$ ulimit -n
65535
hadoop$ ulimit -u
32000

上述的内核参数修改将hadoop用户的可打开文件数限制调高到了65535,同时将hadoop用户的最大进程数限制调高到了32000。经过这种内核参数的修改,HBase就可以同时打开足够多的文件,因此也就可以顺畅运行了。

完全分布式运行的HBase实例要在HDFS上运行一个或多个主节点(HMaster)和多个从节点(RegionServer)。它使用一个可靠的ZooKeeper仲裁团来协调HBase集群中的所有组件,包括主节点、从节点和客户端。

HMaster并非必须与HDFS NameNode运行在同一服务器上,但对小集群来说,为了便于管理通常会让它们运行在同一台服务器上。RegionServers通常被配置在HDFS的DataNode服务器上运行。在Datanode服务器上运行RegionServer还有数据局部化data locality)的优势。在同一台服务器上运行DataNode可使服务器拥有一份RegionServer所需数据的完整拷贝。

本节将描述一个完全分布式HBase的安装。我们将在master1上安装HMaster,然后再安装三个区域服务器(slave1slave3)。我们还要在client1上安装一个HBase客户端。

首先,请确保在集群的所有服务器上都安装好了Java。

我们还是使用hadoop用户来作为所有HBase守护程序和文件的所有者。所有HBase文件和数据将被存储在/usr/local/hbase目录中。请提前在HBase集群的所有服务器上创建好这个目录。

我们将在client1上安装一个HBase客户端。因此,在client1上也要安装Java并准备好hadoop用户和目录。

要确保HDFS处于运行状态。你可以使用如下命令来访问HDFS,以确认其是否已正常启动。

hadoop@client1$ $HADOOP_HOME/bin/hadoop fs -ls /

我们不需要启动MapReduce,因为HBase通常不会使用MapReduce。

我们假设你正管理着自己的一套ZooKeeper,在这种情况下,你可以启动它,并确认是否正常运行。你可以通过向其客户端端口发送ruok命令的方式来确认它是否在正常运行。

hadoop@client1$ echo ruok | nc master1 2181

要安装一个完全分布式的HBase集群,我们需要先在主节点上下载并配置好HBase,然后将其同步到所有从节点和客户端上。

从HBase官方网站(http://www.apache.org/dyn/closer.cgi/hbase/)上,我们可以下载到HBase的最新稳定版本。

在编写本书时,其最新的稳定版本为0.92.1版。

1.下载HBase源码包,然后将其解压缩到我们为HBase准备的根目录中。另外,为了更便于安装,我们要设置一个HBASE_HOME环境变量。

hadoop@master1$ ln -s hbase-0.92.1 current
hadoop@master1$ export HBASE_HOME=/usr/local/hbase/current

2.我们将使用/usr/local/hbase/var作为HBase在本地文件系统中的临时目录。如果你已经在安装独立运行的HBase时创建了该目录,那么请先删除它。

hadoop@master1$ mkdir -p /usr/local/hbase/var

3.在HBase环境设置文件(hbase-env.sh)中设置JAVA_HOME,使HBase可以知道Java的安装位置。

hadoop@master1$ vi $HBASE_HOME/conf/hbase-env.sh
# The java implementation to use. Java 1.6 required.
export JAVA_HOME=/usr/local/jdk1.6

4.设置HBase使用独立的ZooKeeper仲裁团。

hadoop@master1$ vi $HBASE_HOME/conf/hbase-env.sh
# Tell HBase whether it should manage it's own instance of ZooKeeper or not.
export HBASE_MANAGES_ZK=false

5.在HBase配置文件(hbase-site.xml)中添加下列这些参数设置。

hadoop@master1$ vi $HBASE_HOME/conf/hbase-site.xml
<configuration>
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://master1:8020/hbase</value>
  </property>
  <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>
  <property>
    <name>hbase.tmp.dir</name>
    <value>/usr/local/hbase/var</value>
  </property>
  <property>
    <name>hbase.ZooKeeper.quorum</name>
    <value>master1</value>
  </property>
</configuration>

6.配置集群的从节点。

hadoop@master1$ vi $HBASE_HOME/conf/regionservers
slave1
slave2
slave3

7.将HDFS配置文件(hdfs-site.xml)链接至HBase的配置文件夹(conf)中,使HBase可以查看Hadoop集群上HDFS客户端的配置。

hadoop@master1$ ln -s $HADOOP_HOME/conf/hdfs-site.xml $HBASE_HOME/conf/ hdfs-site.xml

8.从Hadoop和ZooKeeper的安装目录中将hadoop-coreZookeeper及其相关的JAR包复制到$HBASE_HOME/lib/目录中。

hadoop@master1$ rm -i $HBASE_HOME/lib/hadoop-core-*.jar
hadoop@master1$ rm -i $HBASE_HOME/lib/ZooKeeper-*.jar

hadoop@master1$ cp -i $HADOOP_HOME/hadoop-core-*.jar $HBASE_HOME/lib/
hadoop@master1$ cp -i $HADOOP_HOME/lib/commons-configuration-1.6.jar $HBASE _HOME/lib/
hadoop@master1$ cp -i $ZK_HOME/ZooKeeper-*.jar $HBASE_HOME/lib/

9.将/usr/local/hbase目录下的所有HBase文件从主节点同步到各客户端和从节点的同名目录中。

10.在主节点上启动HBase集群。

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

11.在客户端节点上连接该HBase集群。

hadoop@client1$ $HBASE_HOME/bin/hbase shell

你也可以通过浏览器来访问HBase的Web用户界面。请确保你的主节点服务器上已经打开了60010端口。如图1-6所示,HBase Web界面的URL是http://master1:60010/master.jsp

图1-6 HBase的Web用户界面

12.在主节点上关闭HBase集群。

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

在配置HBase集群时,我们通过指定hbase.rootdir属性将/hbase目录设定为了它在HDFS上的根目录。在HBase第一次启动时,它会自动创建该目录。你可以客户端机器上看到HBase在HDFS上创建的那些文件。

hadoop@client1$ $HADOOP_HOME/bin/hadoop fs -ls /hbase

我们希望我们的HBase能够运行在分布式模式下,所以我们在hbase-site.xml中将 hbase.cluster.distributed属性设为true

通过在HBase-env.sh指定HBASE_MANAGES_ZK=false,我们还设定了该集群要使用一个独立的ZooKeeper仲裁团。所使用的ZooKeeper仲裁团由hbase.ZooKeeper.quorum属性来指定。你也可以使用集群模式的ZooKeeper,这时需要在该属性中列出Zookeeper仲裁团的所有服务器(比如zoo1zoo2zoo3)。

所有区域服务器都要在$HBASE_HOME/conf/regionservers中登记。在该配置文件中,每台区域服务器各占一行。在启动集群时,HBase会以SSH方式登录到该配置文件所记录的每台区域服务器上,然后启动该服务器上的HRegionServer守护进程。

由于hdfs-site.xml已被链接到了$HBASE_HOME/conf目录下,所以HBase可以访问hdfs-site.xml中的所有HDFS客户端的配置信息,比如dfs.replication设置。

HBase带有一些预构建好的hadoop-core和ZooKeeper的JAR文件。但与我们安装的Hadoop和ZooKeeper中的对应JAR包相比,这些预构建JAR包可能已经过时了。为了避免出现一些意外问题,要确保HBase使用的.jar文件与Hadoop和ZooKeeper所使用的对应文件版本相同。

在进行下一步工作之前,我们需要进行一些基本设置的调优。这都是一些非常基本和非常重要的Hadoop(HDFS)、ZooKeeper和HBase设置,你应该在安装好集群后立刻修改这些设置。

有些设置会对数据持久性或集群可用性产生影响,因此必须进行配置,而另外一些设置则是为保证HBase顺畅运行而推荐你进行的设置。

这些配置的设置值取决于你的硬件、数据量和集群的规模。本节将对此进行一种指南式的描述。你可能需要根据自己的具体环境对这些设置值进行一些修改。

每次修改都需要先同步到所有客户端和从节点上,然后再重新启动相应的守护进程,这样才能使修改生效。

集群的配置应该进行如下一些修改。

1.打开HDFS的dfs.support.append属性。dfs.support.append属性决定HDFS是否支持追加(sync)功能。其默认值为false。必须将其设置为true,否则在区域服务器崩溃时,就有可能丢失数据。

hadoop$ vi $HADOOP_HOME/conf/hdfs-site.xml
  <property>
    <name>dfs.support.append</name>
    <value>true</value>
  </property>

2.调高dfs.datanode.max.xcievers属性的值,使DataNode可以让更多数量的线程保持打开,以便可以处理更多的并发请求。

hadoop$ vi $HADOOP_HOME/conf/hdfs-site.xml
  <property>
    <name>dfs.datanode.max.xcievers</name>
    <value>4096</value>
  </property>

3.调高ZooKeeper堆内存的大小,以使该内存不必进行交换。

hadoop$ vi $ZK_HOME/conf/java.env
export JAVA_OPTS="-Xms1000m -Xmx1000m"

4.调高ZooKeeper的最大客户端连接数,以便处理更多的并发请求。

hadoop$ echo "maxClientCnxns=60" >> $ZK_HOME/conf/zoo.cfg

5.调高HBase堆内存的大小,以使HBase可以顺畅运行。

hadoop$ vi $HBASE_HOME/conf/hbase-env.sh
export HBASE_HEAPSIZE=8000

6.调低zookeeper.session.timeout属性的值,以使HBase可以很快发现某台区域服务器已宕机,并且能够在很短时间内对其进行恢复。

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

7.若要修改Hadoop/ZooKeeper/HBase的日志设置,需要修改Hadoop/ZooKeeper/HBase各自安装目录下的conf目录中的log4j.properties文件和hadoop-env.shhbase-env.sh文件。最好能将日志目录改到安装文件夹之外。例如,下面这个例子就将HBase的日志目录指定到了/usr/local/hbase/logs目录上。

hadoop$ vi $HBASE_HOME/conf/hbase-env.sh
export HBASE_LOG_DIR=/usr/local/hbase/logs

在第1步中,我们通过打开dfs.support.append属性,因而启用了HDFS的写盘功能。在启用了该功能之后,我们可以通过调用flush函数来让HDFS写进程确保对数据进行了持久化。这样HBase就可以保证:在一台区域服务器发生宕机时,我们可以通过在其他区域服务器上重演故障服务器的预写日志WALWrite-Ahead Log)的方式来恢复故障服务器上的数据。

若想确认是否支持HDFS追加功能,可查看HBase启动时的HMaster日志。如果没有启用追加功能,你就会看到类似下面这样的日志。

$ grep -i "HDFS-200" hbase-hadoop-master-master1.log
...syncFs -- HDFS-200 -- not available, dfs.support.append=false

在第2步中,我们对dfs.datanode.max.xcievers属性进行了配置,该属性指定了HDFS的DataNode可同时打开的文件数量的上限。


请注意,该属性的名字是xcievers,这个名字有拼写错误。其默认值是256,这个值太低,无法在HDFS上运行HBase。

第3步和第4步是设置ZooKeeper的属性。ZooKeeper对于内存交换非常敏感,内存交换会使其性能严重降低。ZooKeeper的堆内存大小需要在java.env文件中设置。ZooKeeper可同时打开的连接的数量也有一个上限。其默认值是10,这对于HBase来说太低了,尤其是还要在HBase上运行MapReduce的时候。 我们建议你把它设置为60

在第5步中,我们配置了HBase堆内存的大小。HBase默认的堆大小为1GB, 对于当前服务器的硬件水平来说,这也太低了。对于大型的机器来说,8GB或更大都是一个比较合理的值,但不要超过16GB。

在第6步中,我们将ZooKeeper的会话超时时间修改为一个较低的值。超时时间更短意味着HBase可以更快地发现有区域服务器发生了宕机,因此,HBase可以在很短的时间内在其他服务器上恢复那些被毁坏的区域。另一方面,如果会话超时时间过短,也会有在集群负载很重时HRegionServer守护进程将自己的进程杀掉的风险,因为它可能还没来得及把心跳信号发给ZooKeeper,时间就已经超时了。

Hadoop和HBase的设计目标是能够在各从节点之间自动进行故障转移。 因为在大型集群中可能有很多节点,服务器硬件故障或从节点宕机被视为是集群中的正常现象。

对于主节点来说,HBase本身并没有SPOF。HBase使用ZooKeeper作为它的中央协调服务。ZooKeeper仲裁团通常是一个由3台以上服务器所组成的集群。只要集群中有一半以上的服务器还处于在线状态,ZooKeeper就可以正常提供服务。

HBase将其活跃主节点、根域服务器的位置以及其他一些重要的运行数据都保存在ZooKeeper中。因此,我们只需在两台不同服务器上启动两个(或两个以上的)HMaster守护进程,其中先启动的那个就会成为HBase集群的活跃主服务器。

但是,HDFS的NameNode却是集群的单点故障。NameNode将整个HDFS文件系统的映像文件保存在它的本地存储中。如果NameNode宕机,HDFS就无法正常工作,因此HBase也就宕机了。你可能已经注意到了,HDFS还有一个二级NameNode(Secondary NameNode)。需要注意的是,二级NameNode并不是一个后备NameNode,它只是为NameNode提供了一种检查点的功能。因此,高可用集群的难点就在于实现NameNode的高可用性。

本节将介绍含两个高可用主节点的集群的安装方式,这两个节点将通过Heartbeat软件来监控对方的状态。Heartbeat是一种应用广泛的高可用性解决方案,可以为Linux集群提供通信和成员关系的设定。在启动/停止集群的服务时,需要将Heartbeat与集群资源管理器CRMCluster Resource Manager)结合在一起使用。Pacemaker是最适合Heartbeat的一种集群资源管理器。我们将使用Heartbeat和Pacemaker来建立一个虚拟IPVIPVirtual IP)地址,然后将该地址与活跃主节点相关联。因为EC2不支持静态IP地址,所以我们不能在EC2上演示这一过程,但我们可以讨论另一种替代方案:使用弹性IPEIPElastic IP)来达到同样的目的。

本节的重点是如何安装NameNode和HBase,使用类似的方法也可以设置好两个JobTracker节点。

你应该已经安装好了HDFS和HBase。我们将安装一个后备主节点(master2),因此你需要准备好另外一台服务器。请确保所有需要的类库都已正确配置。将你的Hadoop及HBase的根目录从活跃主节点(master1)同步到后备主节点中。

本节也需要使用NFS。请安装好NFS服务器,并在master1master2上装载好相同的NFS目录。确保hadoop用户具有该NFS目录的写权限。在NFS上创建一个目录来存储Hadoop的元数据。我们假设该目录就是/mnt/nfs/hadoop/dfs/name

我们将为两个主节点设置VIP,假设其IP地址与DNS的映射关系如下。

按照如下步骤设置两个高可用主节点。

一、安装配置Heartbeat和Pacemaker

首先,我们要安装Heartbeat和Pacemaker,并进行一些基本的配置。

1.在master1master2上安装Heartbeat和Pacemaker。

root# apt-get install heartbeat cluster-glue cluster-agents pacemaker

2.在配置Heartbeat时,在master1master2上都要进行如下修改。

root# vi /etc/ha.d/ha.cf
# enable pacemaker, without stonith
crm yes
# log where ?
logfacility local0
# warning of soon be dead
warntime 10
# declare a host (the other node) dead after:
deadtime 20
# dead time on boot (could take some time until net is up)
initdead 120
# time between heartbeats
keepalive 2
# the nodes
node master1
node master2
# heartbeats, over dedicated replication interface!
ucast eth0 master1 # ignored by master1 (owner of ip)
ucast eth0 master2 # ignored by master2 (owner of ip)
# ping the name server to assure we are online
ping ns

3.创建authkeys文件。在master1master2上以root用户的身份执行如下脚本。

root# ( echo -ne "auth 1\n1 sha1 "; \
 dd if=/dev/urandom bs=512 count=1 | openssl md5 ) \
 > /etc/ha.d/authkeys

root# chmod 0600 /etc/ha.d/authkeys

二、创建并安装一个NameNode资源代理

Pacemaker要依靠一个资源代理来管理集群。资源代理就是一个负责管理集群资源的可执行程序。在本例中,VIP地址和HDFS NameNode的服务都是我们要(使用Pacemaker来)管理的集群资源。Pacemaker带有一个可对VIP进行管理的名为IPaddr的资源代理,因此我们需要创建的只是一个名为NameNode的自定义资源代理。

1.在master1master2上,给root用户的.bashrc 文件添加几个环境变量。不要忘了使该修改生效。

root# vi /root/.bashrc
export JAVA_HOME=/usr/local/jdk1.6
export HADOOP_HOME=/usr/local/hadoop/current
export OCF_ROOT=/usr/lib/ocf

调用下面这条命令来使前面的修改生效。

root# source /root/.bashrc

2.创建一个标准开放集群框架OCFOpen Clustering Framework)的资源代理文件,将其命名为namenode并在文件中加入如下的内容。

namenode资源代理启动后,会包含显示元数据以及对NameNode节点进行启动、关闭、状态查询和环境变量验证等标准的OCF功能。下面我们就来逐一添加这些功能。

root# vi namenode
#!/bin/sh
: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat}
. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs
usage() {
  echo "Usage: $0 {start|stop|status|monitor|meta-data|validateall}"
}

3.添加meta_data()函数,代码如下所示。该meta_data()函数会把资源代理的元数据转储到标准输出中。每一个资源代理必须用一组XML元数据来描述自己的目的和可支持的参数。

root# vi namenode
meta_data() {cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="namenode">
<version>0.1</version>
<longdesc lang="en">
This is a resource agent for NameNode. It manages HDFS namenode daemon.
</longdesc>
<shortdesc lang="en">Manage namenode daemon.</shortdesc>
<parameters></parameters>
<actions>
<action name="start" timeout="120" />
<action name="stop" timeout="120" />
<action name="status" depth="0" timeout="120" interval="120" />
<action name="monitor" depth="0" timeout="120" interval="120" />
<action name="meta-data" timeout="10" />
<action name="validate-all" timeout="5" />
</actions>
</resource-agent>
END
}

4.添加namenode_start()函数。Pacemaker要使用该函数来启动NameNode守护进程。在namenode_start()函数中,首先要检查服务器上是否已经启动了NameNode。如果没有启动,就要以hadoop用户的身份来调用hadoop-daemon.sh将其启动。

root# vi namenode
namenode_start() {
  # if namenode is already started on this server, bail out early
    namenode_status 
  if [ $? -eq 0 ]; then
    ocf_log info "namenode is already running on this server, skip"
    return $OCF_SUCCESS
  fi

    # start namenode on this server
    ocf_log info "Starting namenode daemon..."
    su - hadoop -c "${HADOOP_HOME}/bin/hadoop-daemon.sh start name node"
  if [ $? -ne 0 ]; then
    ocf_log err "Can not start namenode daemon."
    return $OCF_ERR_GENERIC;
  fi
  sleep 1
  return $OCF_SUCCESS
}

5.添加namenode_stop()函数。Pacemaker要使用该函数来关闭NameNode守护进程。在namenode_stop()函数中,首先要检查服务器上的NameNode是否已经关闭。如果它还在运行,就要以hadoop用户的身份来调用hadoop-daemon.sh将其关闭。

root# vi namenode
namenode_stop () {
  # if namenode is not started on this server, bail out early
    namenode_status 
  if [ $? -ne 0 ]; then
    ocf_log info "namenode is not running on this server, skip"
      return $OCF_SUCCESS
  fi

  # stop namenode on this server
  ocf_log info "Stopping namenode daemon..."
  su - hadoop -c "${HADOOP_HOME}/bin/hadoop-daemon.sh stop name node"
    if [ $? -ne 0 ]; then
      ocf_log err "Can not stop namenode daemon."
      return $OCF_ERR_GENERIC;
    fi
    sleep 1
    return $OCF_SUCCESS
}

6.添加namenode_status()函数。Pacemaker要使用该函数来监控服务器上NameNode守护进程的状态。在namenode_status()函数中,我们使用jps命令来显示hadoop用户下所有正在运行的Java进程,然后使用grep命令来从中查找NameNode守护进程的名字,以此来确定它是否已经启动。

root# vi namenode
namenode_status () {
  ocf_log info "monitor namenode"
  su - hadoop -c "${JAVA_HOME}/bin/jps" | egrep -q "NameNode" rc=$?
  # grep will return true if namenode is running on this machine
  if [ $rc -eq 0 ]; then
    ocf_log info "Namenode is running"
    return $OCF_SUCCESS else
    ocf_log info "Namenode is not running"return $OCF_NOT_RUNNING
  fi
}

7.添加一个namenode_validateAll()函数。在运行其他函数之前,我们会用它来确认各环境变量是否均已正确设置好了。

root# vi namenode
namenode_validateAll () {
  if [ -z "$JAVA_HOME" ]; then
    ocf_log err "JAVA_HOME not set."
    exit $OCF_ERR_INSTALLED
  fi
  if [ -z "$HADOOP_HOME" ]; then
    ocf_log err "HADOOP_HOME not set."
    exit $OCF_ERR_INSTALLED
  fi
  # Any subject is OK
    return $OCF_SUCCESS
}

8.添加主例程。该主例程所做的就是调用前面定义的几个函数来实现一些必备的标准OCF资源代理操作。

root# vi namenode
# See how we were called.
if [ $# -ne 1 ]; then
  usage
  exit $OCF_ERR_GENERIC
fi

namenode_validateAll

case $1 in
    meta-data) meta_data
    exit $OCF_SUCCESS;;
  usage) usage
    exit $OCF_SUCCESS;;
  *);;
  esac

case $1 in
    status|monitor) namenode_status;;
    start) namenode_start;;
    stop) namenode_stop;;
    validate-all);;
    *)usage
    exit $OCF_ERR_UNIMPLEMENTED;;
  esac
exit $?

9.在master1master2上修改namenode文件的权限并进行测试。

root# chmod 0755 namenode
root# ocf-tester -v -n namenode-test /full/path/of/namenode

10.请确保所有测试都已通过,然后再进入下一步;否则HA集群就有可能会工作不正常。

11.在master1master2上,将namenode资源代理程序安装到hac适配器上。

root# mkdir ${OCF_ROOT}/resource.d/hac
root# cp namenode ${OCF_ROOT}/resource.d/hac
root# chmod 0755 ${OCF_ROOT}/resource.d/hac/namenode

三、配置高可用NameNode

现在我们可以使用Heartbeat和Pacemaker配置高可用NameNode了。我们要设置一个VIP地址,然后对Hadoop和HBase进行配置,让它们使用这个VIP地址来作为自己的主节点。NameNode将在那个分配到VIP的活跃主节点上启动。在活跃主节点发生宕机时,Heartbeat和Pacemaker能够检测到宕机并将VIP地址分配给后备主节点,然后在该节点上启动NameNode。

1.在master1master2上启动Heartbeat。

root# /etc/init.d/heartbeat start

2.修改默认的crm配置。在master1master2上,所有资源相关的命令只需执行一次。

root# crm configure property stonith-enabled=false
root# crm configure property default-resource-stickiness=1

3.添加一个使用了VIP地址的VIP资源。

root# crm configure primitive VIP ocf:heartbeat:IPaddr params ip="10.174. 14.10" op monitor interval="10s"

4.按如下方法修改Hadoop的配置,让Hadoop使用VIP地址。在完成修改之后,将该配置文件同步到所有的主节点、客户端和从节点上。

hadoop$ vi $HADOOP_HOME/conf/core-site.xml
  <property>
    <name>fs.default.name</name>
    <value>hdfs://master:8020</value>
  </property>

5.按如下方法修改HBase的配置,让HBase使用VIP地址。在完成修改之后,将该配置文件同步到所有的主节点、客户端和从节点上。

hadoop$ vi $HBASE_HOME/conf/hbase-site.xml
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://master:8020/hbase</value>
  </property>

6.修改Hadoop的配置,让Hadoop将其元数据写到本地磁盘和NFS上,然后将配置文件同步到所有主节点、客户端和从节点上。

hadoop$ vi $HADOOP_HOME/conf/hdfs-site.xml
  <property>
    <name>dfs.name.dir</name>
    <value>/usr/local/hadoop/var/dfs/name,/mnt/nfs/hadoop/dfs/name </value>
  </property>

7.将之前创建的那个namenode资源代理程序加入到Pacemaker中。我们将使用NAMENODE来作为它的资源名称。

root# crm configure primitive NAMENODE ocf:hac:namenode op monitor interval="120s" timeout="120s" op start timeout="120s" op stop timeout="120s" meta resource-stickiness="1"

8.将VIP资源和NAMENODE资源配置为一个资源组。

root# crm configure group VIP-AND-NAMENODE VIP NAMENODE

9.配置VIP资源和NAMENODE资源的colocation属性。

root# crm configure colocation VIP-WITH-NAMENODE inf: VIP NAMENODE

10.配置VIP资源和NAMENODE资源的资源顺序。

root# crm configure order IP-BEFORE-NAMENODE inf: VIP NAMENODE

11.使用crm_mon命令来验证前面的Heartbeat配置和资源配置是否正确。如果一切配置正确,你会看到类似下面这样的输出。

root@master1 hac$ crm_mon -1r
============
Last updated: Tue Nov 22 22:39:11 2011
Stack: Heartbeat
Current DC: master2 (7fd92a93-e071-4fcb-993f-9a84e6c7846f) -
partition with quorum
Version: 1.0.9-74392a28b7f31d7ddc86689598bd23114f58978b
2 Nodes configured, 1 expected votes
1 Resources configured.
============
Online: [ master1 master2 ]

Full list of resources:

Resource Group: VIP-AND-NAMENODE
 VIP (ocf::heartbeat:IPaddr): Started master1
 NAMENODE (ocf::hac:namenode): Started master1

12.确认同一台服务器上的VIP资源和NAMENODE资源是否已启动。

13.现在,关闭maste1上的Heartbeat;几秒钟之后,master2上的VIP-AND-NAMENODE就会启动。

14.在master1上重新启动Heartbeat;而master2上的VIP-AND-NAMENODE仍然在运行。资源不会切还给master1

四、启动DataNode、HBase集群和后备HBase主节点

我们已经确认HA配置能正常工作了,所以现在可以启动HDFS和HBase了。请注意, Pacemaker已经把NameNode启动起来了,所以我们现在只需要启动DataNode。

1.如果一切顺利,我们现在就可以启动DataNode了。

hadoop@master$ for i in 1 2 3
do
 ssh slave$i "$HADOOP_HOME/bin/hadoop-daemon.sh start datanode"
 sleep 1
done

2.在master上启动HBase集群,master就是VIP地址现在所关联的那台活跃主节点服务器。

hadoop@master$ $HBASE_HOME/bin/start-hbase.sh

3.在后备主节点服务器(本例中即为master2)上启动后备HMaster。

hadoop@master2$ $HBASE_HOME/bin/hbase-daemon.sh start master

经过前面这些步骤的操作之后,我们会得到一个如图1-7所示的集群结构。

图1-7 含两个HA主节点的集群的结构图

首先,我们在两个主节点上安装了Heartbeat和Pacemaker,并且对Heartbeat进行了配置,使之能够与Pacemaker联合使用。

在“创建并安装NameNode资源代理”的第2步中,我们创建了namenode脚本,该脚本实现了一个标准的OCF资源代理程序。在namenode脚本中,最重要的是namenode_status函数,它负责对NameNode守护进程的状态进行监控。该函数会使用jps来命令显示hadoop用户下所有正在运行的Java进程,然后使用grep命令来从中查找NameNode守护进程的名字,以此来确定它是否已启动。 Pacemaker要使用namenode资源代理程序来启动、关闭和监控NameNode守护进程。从namenode脚本可以看出,namenode_startnamenode_stop方法实际上是调用了hadoop-daemon.sh脚本来启动/关闭NameNode,之前我们在单服务器上启动/关闭Hadoop守护进程时就使用过这一脚本。在本书附带的源代码中,可以找到该脚本的完整代码。

在测试和安装完namenode资源代理程序之后,我们启动了Heartbeat;然后,我们对一些默认的crm配置进行了修改。“default-resourcestickiness=1”这一设置非常重要,因为它会关闭了资源的自动故障恢复功能。

在“配置高可用NameNode”一节的第3至5步中,我们给Pacemaker增加了一个VIP资源,然后使用该资源对Hadoop和HBase进行了配置。在配置中使用了VIP之后,如果活跃主节点发生宕机,Hadoop和HBase就会切换到与后备主节点进行通信的工作方式。

该节第6步是对Hadoop(HDFS NameNode)进行配置,让它将元数据同时写入到本地磁盘和NFS上。如果活跃主节点宕机了,后备主节点上的NameNode就会启动。因为它们装载的是同一个NFS目录,所以在后备主节点上启动的NameNode也可以从NFS上访问到最新的元数据,然后将HDFS恢复到原活跃主节点宕机之前的状态。

在第7~10步中,我们首先增加了一个NAMENODE资源,它使用了我们在“创建并安装一个NameNode资源代理”一节第2步中创建的namenode资源代理程序(第7步);然后,我们将VIP资源和NameNode的资源设置为了一个资源组(第8步),并确保它们一直运行在同一台服务器上(第9步)按照正确的顺序先后启动(第10步)。我们这样做是因为:我们不希望出现“VIP运行在master1上,而NameNode运行在master2上”这样的一种情况。

因为Pacemaker会通过namenode资源代理程序来启动NameNode,所以我们只需要单独启动DataNode。这就是“启动DataNode、HBase集群和后备HBase主节点”一节的第1步的内容。

在HBase正常启动之后,我们在后备主服务器上启动了后备HBase主节点(HMaster)。如果查看HBase主节点的日志,你会看到类似下面这样的输出,这表明该节点现在是后备HMaster。

2011-11-21 23:38:55,168 INFO org.apache.hadoop.hbase.master.ActiveMaster- Manager: Another master is the active master, ip-10-174-14-15.us-west-1. compute.internal:60000; waiting to become the next active master

最终,我们让NameNode和HMaster运行在了按“主机-后备”方式进行配置的两台服务器上。这样就可以避免集群的单点故障了。

但是,如果是在生产环境中,我们则还有很多工作要做。你需要测试HA集群在各种罕见情况下的反应,比如服务器掉电、网线被拔掉、网络交换机宕机或其他任何你能想到的情况。

另一方面,集群的单点故障可能也不像你想象的那么重要。根据我们的经验,几乎所有集群停机时间都是由于运维人员的失误或需要进行软件升级而引起的。最好还是让你的集群保持简单一些吧。

在Amazon EC2上建立一个高可用HBase集群更为复杂,因为EC2不支持静态IP地址,所以我们不能在EC2上使用VIP。替代的方法是使用Elastic IP(弹性IP)地址。弹性IP地址相当于是EC2上的静态IP地址,不过它关联的是你的账户,而不是某个特定的实例。如果活跃主节点宕机了,我们可以使用Heartbeat自动将EIP关联到后备主节点上;然后,我们再对Hadoop和HBase进行配置,让它们使用一个与EIP关联的实例公共DNS,以此来寻找活跃主节点。另外,在namenode资源代理程序中,我们不仅要启动/关闭NameNode,还要启动/关闭所有的DataNode。这是因为活跃主节点的IP地址已经发生了变化,如果不重新启动DataNode,它也无法找到新的活跃主节点。

本节没有对此进行详述,因为它不属于本书所要讲述的范畴。我们创建了一个elastic-ip资源代理程序来实现这一目的。你可在本书附带的源码中找到该资源代理程序。


本章内容:

将数据移到HBase的方法有以下几种。

使用HBase的Put API是最直接的方法。这种方法的使用并不难学。但在大多数情况下,它并非总是最有效的方法。特别是在有一大批数据需要移入HBase并且对移入时间又有限定的情况下,这种方法的效率并不高。我们需要处理的数据通常都有很大的数据量,这可能也是我们使用HBase而不使用其他数据库的原因。你必须在HBase的项目的开始阶段就仔细考虑该如何将所有数据转入HBase,否则你就会遇到一些严重的性能问题。

HBase提供了批量加载的功能来支持高效地将大量数据加载到HBase中。批量加载功能使用了一个MapReduce任务将数据加载到一个特定的HBase表中,它会生成一些HBase内部的HFile数据格式的文件,然后再将这些数据文件直接加载到正在运行的集群中。使用批量加载功能最简单的方法是使用importtsv工具。importtsv是一个可将数据从TSV文件加载到HBase中的内置工具。它会运行一些MapReduce任务来读取TSV文件中的数据,然后将其输出直接写入HBase表或HBase内部数据格式的文件中。

虽然importtsv工具在将文本数据导入HBase的时候非常有用,但是有些情况下,比如导入一些其他格式的数据时,你可能必须以编程的方式来生成数据。MapReduce是处理海量数据的最有效方法。它可能也是将海量数据加载到HBase中的唯一可行的方法。当然,我们也可以使用MapReduce将数据导入到HBase中。然而,当数据量非常大时,MapReduce任务的负载也可能非常重。如果不正确对待,重负载MapReduce任务运行时的吞吐量也可能很差。

数据迁移是HBase上的一项写密集的任务,除非我们能先生成好一些内部数据文件然后再把它们直接加载到HBase中去。尽管HBase的写操作总是非常快,但是如果不正确配置,在迁移过程中也会经常出现写操作阻塞的情况。写密集型任务的另一个问题是:所有写操作可能针对的都是同一台区域服务器,尤其是在将大量数据加载到一个新HBase安装的时候,这种情况更容易发生。因为所有负载都集中在同一台服务器上,不能均衡分配给集群中的各个服务器,所以写入速度也会明显减慢。

本章所要解决的就是这些问题。我们将从简单的任务开始,使用Put AP将数据从MySQL导入到HBase中;然后,我们将描述如何使用importtsv和批量加载工具将TSV数据文件加载到HBase中。我们还将使一个MapReduce的例子来导入其他文件格式的数据。其中包括将数据直接放入到一张HBase表中,然后在将其写到HDFS(Hadoop分布式文件系统)文件系统中的HFile格式的文件中。本章最后一节将介绍如何在向HBase加载数据之前预先创建好一些区域。

本章附带有一些Java编写的示例代码。我们假设你已经具备了一些Java的基础知识,所以本章的各节将不再讲解如何编译和打包这些示例的Java源代码了,但对示例源代码本身会进行一些介绍。

数据迁移最常见的情况可能就是从现有的RDBMS将数据导入到HBase中了。对于这类任务,可能最简单也最直接的方法就是:用一个客户端程序来读取数据,然后通过HBase的Put API把数据送到HBase中去。如果需要传输的数据不太多,这种方法非常适合。

本节将介绍如何使用HBase的Put API将数据从MySQL导入到HBase。所有操作都将在一个客户端程序中完成。MapReduce不在本节的介绍范围内。本节会引导你通过HBase Shell创建一张HBase表,从Java连接到集群,然后将数据写入到HBase中。

公共数据集是我们练习HBase数据迁移的理想数据源。在互联网上有许多公共数据集。本书将使用美国国家海洋和大气管理局(NOAA)编制的1981-2010年气候标准值(CLIMATE NORMALS)公共数据集。该数据集可以从http://www1.ncdc.noaa.gov/pub/data/normals/1981-2010/下载。

该气候统计数据是由美国国家海洋和大气管理局(NOAA,National Oceanic and Atmospheric Administration)编制的。


本节将使用上述链接中的products|hourly目录下的每小时的气温数据。请下载该目录中的hly-temp-normal.ztxt文件。

本节需要你事先安装好一个可运行的MySQL。使用下面的SQL语句,在MySQL数据库中创建一张名为hly_temp_normal的表。

create table hly_temp_normal (
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    stnid CHAR(11),
    month TINYINT,
    day TINYINT,
    value1 VARCHAR(5),
    value2 VARCHAR(5),
    value3 VARCHAR(5),
    value4 VARCHAR(5),
    value5 VARCHAR(5),
    value6 VARCHAR(5),
    value7 VARCHAR(5),
    value8 VARCHAR(5),
    value9 VARCHAR(5),
    value10 VARCHAR(5),
    value11 VARCHAR(5),
    value12 VARCHAR(5),
    value13 VARCHAR(5),
    value14 VARCHAR(5),
    value15 VARCHAR(5),
    value16 VARCHAR(5),
    value17 VARCHAR(5),
    value18 VARCHAR(5),
    value19 VARCHAR(5),
    value20 VARCHAR(5),
    value21 VARCHAR(5),
    value22 VARCHAR(5),
    value23 VARCHAR(5),
    value24 VARCHAR(5)
);

本书附带的一些脚本可帮你将该数据加载到MySQL表中。你可以使用insert_hly.py脚本来加载NOAA的气温数据。你需要修改脚本中的主机名、用户名、密码和数据库名称。在修改完这些之后,可使用如下命令将所下载的hly-temp- normal.txt文件中的数据插入到hly_temp_normal表中。

$ python insert_hly.py -f hly-temp-normal.txt -t hly_temp_normal

为了编译2.2.2小节中提到的那段Java源代码,你需要有如下几个Java库。

你可以手动将它们添加到你的classpath中,然后就可以使用本书所提供的示例源码了。

在开始导入数据之前,请确保你的HDFS、ZooKeeper和HBase集群都已经能够正常运行了,然后请登录到你的HBase客户端节点上。

通过客户端程序将数据从MySQL导入到HBase中的步骤如下。

1.在HBase的客户端服务器上通过HBase Shell连接到HBase集群上。

hadoop$ $HBASE_HOME/bin/hbase shell

2.在HBase中创建一张名为hly_temp表。

hbase> create 'hly_temp', {NAME => 'n', VERSIONS => 1}

3.编写将数据从MySQL导入到HBase的Java源代码。将其打包为一个JAR文件。按照如下步骤使用Java代码来完成数据导入。

(1)创建一个connectHBase()方法,用来在Java程序中连接指定的HBase表。

$ vi Recipe1.java
  private static HTable connectHBase(String tablename) \
  throws IOException {
    HTable table = null;
    Configuration conf = HBaseConfiguration.create();
    table = new HTable(conf, tablename);
    return table;
  }

(2)创建一个connectDB()方法,用来在Java程序中连接MySQL。

$ vi Recipe1.java
  private static Connection connectDB() \
  throws Exception {
    String userName = "db_user";
    String password = "db_password";
    String url = "jdbc:mysql://db_host/database";
    Class.forName("com.mysql.jdbc.Driver").newInstance();
    Connection conn = DriverManager.getConnection(url,
    userName, password);
    return conn;
  }

下面是该Java类的main()方法。在该方法中,我们要从MySQL中读取数据,然后将数据写入到HBase中。

$ vi Recipe1.java
public class Recipe1 {
  public static void main(String[] args) {
    Connection dbConn = null;
    HTable htable = null;
    Statement stmt = null;
    String query = "select * from hly_temp_normal";
    try {
      dbConn = connectDB();
      htable = connectHBase("hly_temp");
      byte[] family = Bytes.toBytes("n");
      stmt = dbConn.createStatement();
      ResultSet rs = stmt.executeQuery(query);
      // time stamp for all inserted rows
      long ts = System.currentTimeMillis();
      while (rs.next()) {
        String stationid = rs.getString("stnid");
        int month = rs.getInt("month");
        int day = rs.getInt("day");
        String rowkey = stationid + Common.lpad(String.
        valueOf(month), 2,
        '0') + Common.lpad(String.valueOf(day), 2, '0');
        Put p = new Put(Bytes.toBytes(rowkey));
        // get hourly data from MySQL and put into hbase
        for (int i = 5; i < 29; i++) {
          String columnI = "v" + Common.lpad
          (String.valueOf(i - 4), 2, '0');
          String valueI = rs.getString(i);
          p.add(family, Bytes.toBytes(columnI), ts,
          Bytes.toBytes(valueI));
        }
        htable.put(p);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      try {
        if (stmt != null) {
          stmt.close();
        }
        if (dbConn != null) {
          dbConn.close();
        }
        if (htable != null) {
          htable.close();
        }
      } catch (Exception e) {
        // ignore
      }
    }
  }
}

4.执行导入任务。运行该JAR包的脚本大致如下。

#/bin/bash
bin='dirname $0'
bin='cd $bin;pwd'
cp=$HBASE_HOME/conf:$HBASE_HOME/hbase-0.92.1.jar:$bin/build/hacchapter2.
jar
for jar in $bin/lib/*.jar
do
    cp=$cp:$jar
done
for jar in $HBASE_HOME/lib/*.jar
do
    cp=$cp:$jar
done

$JAVA_HOME/bin/java -classpath $cp "hac.chapter2.Recipe1"

5.为了验证数据已被导入到HBase中,需要通过HBase Shell连接到运行中的HBase实例上。

hadoop$ $HBASE_HOME/bin/hbase shell

6.验证数据是否已经被导入到HBase中的目标表内。

hbase> count 'hly_temp'
95630 row(s) in 8.9850 seconds
hbase> scan 'hly_temp', {LIMIT => 10}
...
 AQW000617050110 column=n:v23,
timestamp=1322958813521, value=814S
 AQW000617050110 column=n:v24,
timestamp=1322958813521, value=811C
10 row(s) in 0.6730 seconds

在第1步和第2步中,我们在HBase中创建了一张用于插入数据的目标表。该表名为hly_temp,它有一个名为n的列族。我们用一个字母来命名的原因是:列族的名字将被存储在HBase中的每一个键/值对中,使用短列族名能使数据的存储和缓存都更有效率。我们只需要一个版本的数据,这可以通过列族的VERSION属性来指定。

为了连接HBase,我们首先在Java代码中创建了一个Configuration对象,然后以它和表名为参数创建了一个HTable实例。该HTable对象要处理所有的客户端API调用。正如你看到的那样,我们没有在代码中设置任何ZooKeeper或HBase的连接配置信息。那么,程序是如何找到那个正确的运行中的HBase集群呢?这是因为:我们在第4步中将$HBase/conf目录添加到了classpath中。在添加了该目录之后,HBase客户端API就能从classpath中找到hbase-site.xml并载入配置信息。连接设置都已在hbase-site.xml文件中指定好了。

在使用JDBC从MySQL获取到数据之后,我们通过循环将MySQL结果集中的每一行都映射成HBase表中的对应行。在本例中,我们使用stationidmonthday这3个字段来构成HBase数据的行键。我们还对不足2位的月份和日期数据进行前面补0的操作。这一操作很重要,因为HBase的行键是按字典顺序排序的,这意味着12会排在2的前面,而这当然不是我们期望得到的结果。

我们使用行键来为每条记录创建一个Put对象。小时级的数据通过调用Put.add()方法来赋值,传入该方法的参数有:列族、字段名、时间和气温值。各小时的气温值字段同样使用了很短的字段名,目的也是提高数据的存储效率。在全天各小时的数据都赋值完毕之后,我们调用了HTable.put()来将数据插入到了表中。

最后,我们需要将所有已打开的资源都手动关闭。我们把MySQL和HBase的连接关闭操作放在了代码的final段中,以确保即便是在导入过程中发生了异常,这些操作也会最终得到执行。

我们可以通过比较MySQL和HBase中的两张表的记录数来验证导入是否成功。正如扫描结果所显示的那样,数据已被准确地导入到了HBase中。

HBase带有一个名为importtsv的工具,它支持将TSV文件中的数据导入到HBase中。使用此工具将文本数据加载到HBase的效率非常高,因为它会运行一个MapReduce任务来进行导入。即便你需要加载的数据来自于某个RDBMS,你也可以先将数据转储到某种方式的一个文本文件中,然后再使用importtsv来将转储出的数据导入到HBase中。在导入大量数据时这种方法尤为适用,因为在RDBMS上转储数据要比执行SQL快很多。

importtsv 工具不仅能将数据直接加载到HBase表中,还能生成HBase内部格式(HFile)的文件。因此,你可以使用HBase的批量加载工具将所生成的这些文件直接加载到一个正在运行的HBase集群中。这种方式可以减少迁移过程中因数据传输而产生的网络流量和给HBase带来的负载。

本节将介绍importtsv和批量加载工具的使用。我们首先会演示如何使用importtsv工具将TSV文件中的数据加载到HBase表中,然后我们还会介绍如何生成HBase内部格式的文件,以及如何将所生成的文件直接加载到HBase中。

本节仍将使用NOAA的气候标准值数据。该数据可以在下列地址中获得。

http://www1.ncdc.noaa.gov/pub/data/normals/1981-2010/


请下载上述地址中products | hourly目录下的hly- temp-10pctl.txt 文件。

所下载的数据不能直接用来用importtsv工具进行加载,因为它使用的不是该工具支持的文件格式。我们提供了一个脚本,可以帮你将数据转换为TSV文件的格式。除了实际的数据之外,待加载的TSV文件还必须包含一个可代表HBase表的行键的字段。这本书附带的to_tsv_hly.py 脚本可从NOAA小时级数据文件中读取数据,生成行键,然后将数据输出到本地文件系统中的TSV文件中。

$ python to_tsv_hly.py -f hly-temp-10pctl.txt -t hly-temp-10pctl.tsv

由于importtsv工具会运行一个MapReduce任务来进行导入,所以我们需要在集群上启动MapReduce。在主节点上执行以下命令可启动MapReduce守护进程。

hadoop$ $HADOOP_HOME/bin/start-mapred.sh

我们将在客户端服务器上添加一个hac用户,用它来执行MapReduce任务。这是生产环境中建议采用的作法。为了能从客户端运行MapReduce任务,你需要给客户端的hac用户赋予${hadoop.tmp.dir}目录的写权限。我们假设${hadoop.tmp.dir}目录所指向的是/usr/local/hadoop/var目录。

root@client1# usermod -a -G hadoop hac
root@client1# chmod -R 775 /usr/local/hadoop/var

在HDFS上为hac用户创建主目录。

hadoop@client1$ $HADOOP_HOME/bin/hadoop fs -mkdir /user/hac
hadoop@client1$ $HADOOP_HOME/bin/hadoop fs -chown hac /user/hac

另外,还要确保hac用户对于HDFS上的MapReduce临时目录拥有写权限。

hadoop@client1$ $HADOOP_HOME/bin/hadoop fs -chmod -R 775 /usr/local/ hadoop/ var/mapred

利用MapReduce将TSV文件中的数据加载到HBase表中的操作步骤如下。

1.在HDFS上创建一个目录,然后将TSV文件从本地文件系统复制到HDFS中。

hac@client1$ $HADOOP_HOME/bin/hadoop fs -mkdir /user/hac/input/2-1
hac@client1$ $HADOOP_HOME/bin/hadoop fs -copyFromLocal hly-temp-10pctl.tsv /user/hac/input/2-1

2.在HBase中添加一张目标表。连接到HBase上,然后创建一张名为hly_temp的表。

hac@client1$ $HBASE_HOME/bin/hbase shell
hbase> create 'hly_temp', {NAME => 't', VERSIONS => 1}

3.如果该表已存在(我们曾在2.2节中创建过这张表),则为其添加一个新列族。

hbase> disable 'hly_temp'
hbase> alter 'hly_temp', {NAME => 't', VERSIONS => 1}
hbase> enable 'hly_temp'

4.将hbase-site.xml文件链接到Hadoop配置目录下,从而将该文件添加到Hadoop的classpath中。

hac@client1$ ln -s $HBASE_HOME/conf/hbase-site.xml $HADOOP_HOME/conf/ hbase-site.xml

5.在客户端服务器上编辑$HADOOP_HOME/conf目录下的hadoop-env.sh文件,将HBase的相关JAR文件添加到Hadoop的classpath中。

hadoop@client1$ vi $HADOOP_HOME/conf/hadoop-env.sh
export HADOOP_CLASSPATH=/usr/local/zookeeper/current/zookeeper-3.4.3.jar: /usr/local/hbase/current/lib/guava-r09.jar

6.以hac用户的身份运行下列脚本来执行importtsv工具。

hac@client1$ $HADOOP_HOME/bin/hadoop jar $HBASE_HOME/hbase-0.92.1.jar importtsv \
 -Dimporttsv.columns=HBASE_ROW_KEY,t:v01,t:v02,t:v03,t:v04,t:v05,t:v06,t:v07,t:v08,t:v09,t:v10,t:v11,t:v12,t:v13,t:v14,t:v15,t:v16,t:v17,t:v18,t:v19,t:v20,t:v21,t:v22,t:v23,t:v24 \
 hly_temp \
 /user/hac/input/2-1

7.通过图2-1所示的MapReduce管理页面(http://master1:50030/jobtracker.jsp),可以检查该任务的运行状态。

图2-1 通过MapReduce管理页面检查任务的运行状态

8.验证数据是否已经被导入到了HBase的目标表中。在本例中,我们会首先计算一下hly_temp表的记录条数,然后再扫描一下该表中的一些示例数据。该表的记录条数应当与TSV文件的行数相同。表中的各行键应与文件中对应行的第一个字段的值相等。表中的每行记录应当都有t:v01t:v02、……、t:v24这样的一些单元,而且每个单元的值都应当与TSV文件中的对应字段的值相等。

hbase> count 'hly_temp'
95630 row(s) in 12.2020 seconds
hbase> scan 'hly_temp', {COLUMNS => 't:', LIMIT => 10}
AQW000617050110 column=t:v23,
timestamp=1322959962261, value=781S
AQW000617050110 column=t:v24,
timestamp=1322959962261, value=774C
10 row(s) in 0.1850 seconds

因为importtsv工具只能从HDFS中读取数据,所以我们首先使用了hadoop fs -copyFromLocal命令来将TSV文件从本地文件系统复制到HDFS中。

在第2步中,我们在HBase中创建了目标表hly_temp及其列族t。如果该表已经存在,我们则需对表进行一下修改,在该表上增加一个列族t。所有数据都将被加载到新增的列族t中;原有列族中的数据不会受到影响。

为了运行MapReduce任务,我们需要使用hadoop jar命令来执行含有这些类文件的JAR包。为了将HBase配置信息传给该命令,我们将hbase-site.xml文件链接到了$HADOOP_HOME/conf目录下。该目录下的所有文件都将被添加到hadoop命令所启动的Java进程的classpath中。

在第5步中,我们设置了hadoop-env.sh中的HADOOP_CLASSPATH,以便能将所需的相关运行时JAR包添加到classpath中。除了ZooKeeper类库之外,importtsv工具还需要guava-r09.jar文件。这是一个用来解析TSV文件的类库。

importtsv工具本身就是一个包含在HBase JAR文件中的Java类。在第6步中,我们通过执行一个 hadoop jar命令来运行该工具。此命令将为我们启动一个Java程序,并将所有相关类库添加到该进程中。待运行的JAR文件由hadoop jar命令的第一个参数来指定。在本例中,该参数就是hbase-0.92.1.jar

传给hbase-0.92.1.jar的主类的有下面这些参数。

importtsv工具有其他几个可以指定的选项。在不带参数运行importtsv时,可以看到该工具的简要使用方法。

hac@client1$ $HADOOP_HOME/bin/hadoop jar $HBASE_HOME/hbase-0.92.1.jar
importtsv
Usage: importtsv -Dimporttsv.columns=a,b,c <tablename> <inputdir>

Imports the given input directory of TSV data into the specified table.

在以-D为前缀时,还可以指定如下一些选项。

-Dimporttsv.skip.bad.lines=false - 在遇到无效行时是否失败
'-Dimporttsv.separator=|' - 这是一个避免将分隔符误认为管道命令的例子
-Dimporttsv.timestamp=currentTimeAsLong - 本次导入使用指定的时间戳
-Dimporttsv.mapper.class=my.Mapper - 使用用户自定义的映射程序,不使用org. 

该工具会为我们启动一个MapReduce任务。在map阶段,该任务会读取并解析指定输入目录下的TSV文件的行,然后根据列映射信息把这些行插入到HBase表中。它会在多台服务器上并行执行ReadPut操作,所以速度要比从一个客户端加载数据要快很多。该任务在默认情况下没有reduce阶段。我们可以在MapReduce的管理页面中查看该任务的进度、计数器和其他一些MapReduce信息。

在查看插入到该表中的数据时,我们需要使用HBase Shell的scan命令。我们通过指定参数COLUMNS => 't:'来限制只对表中的t列族进行扫描。

在默认情况下,importtsv工具在其map阶段会使用TableOutputFormat类来通过HBase的Put API将数据插入到HBase表中。但如果指定了-Dimporttsv.bulk. output选项,它就会转而使用HFileOutputFormat类来在HDFS上生成一些HBase内部格式(HFile)的文件。这时,我们就可以使用completebulkload工具来将所生成的文件加载到一个正在运行的集群中。使用批量输出和加载工具的步骤如下。

1.在HDFS上创建一个目录,用于存放所生成的文件。

hac@client1$ $HADOOP_HOME/bin/hadoop fs -mkdir /user/hac/output

2.以带批量输出选项的方式运行importtsv

hac@client1$ $HADOOP_HOME/bin/hadoop jar $HBASE_HOME/hbase-0.92.1.jar importtsv \
 -Dimporttsv.bulk.output=/user/hac/output/2-1 \
 -Dimporttsv.columns=HBASE_ROW_KEY,t:v01,t:v02,t:v03,t:v04,
 t:v05,t:v06,t:v07,t:v08,t:v09,t:v10,t:v11,t:v12,t:v13,t:v14,
 t:v15,t:v16,t:v17,t:v18,t:v19,t:v20,t:v21,t:v22,t:v23,t:v24 \
 hly_temp \
 /user/hac/input/2-1

3.完成批量加载。

hac@client1$ $HADOOP_HOME/bin/hadoop jar $HBASE_HOME/hbase-0.92.1.jar completebulkload \
 /user/hac/output/2-1 \
 hly_temp

completebulkload工具会查看所生成的这些文件,确定它们所属的区域,然后与相应的区域服务器进行联络。区域服务器会将所接收到的HFile文件移到其存储目录中,然后以联机的方式为客户端创建这一数据。

在将文本文件加载到HBase中时,importtsv工具非常有用。但是在许多情况下,我们需要对加载的过程进行全面控制,这时可能就需要编写一个自己的MapReduce任务来将数据导入到HBase中了。举例来说,如果要加载一些其他格式的文件,importtsv工具就不能胜任了。

HBase提供了一个TableOutputFormat类,我们可以在MapReduce任务中使用它来将数据写入一张HBase表。我们还可以在MapReduce任务中使用HFileOutputFormat类来生成一些HBase内部格式的HFile文件,然后使用completebulkload工具将所生成的这些文件加载到一个正在运行的HBase集群中(这一点已在上一节中介绍过了)。

本节将介绍如何使用自己编写的MapReduce任务来加载数据。首先,我们将介绍如何使用TableOutputFormat类。在“2.4.4 补充说明”一节中,我们将介绍如何在MapReduce任务中生成HFile格式的文件。

本节将使用NOAA的原始hly-temp-normal.txt文件。我们不需要对所下载到的文件进行任何格式方面的修改。我们将在MapReduce中直接加载这份原始的数据。

我们假设你的环境已经准备就绪,可以在HBase上运行MapReduce了。如果环境尚未准备好,请参考“2.3使用批量加载工具导入TSV文件的数据”一节。

使用自己的MapReduce任务将数据加载到HBase中的操作步骤如下。

1.将原始数据文件从本地文件系统复制到HDFS中。

hac@client1$ $HADOOP_HOME/bin/hadoop fs -mkdir /user/hac/input/2-3
hac@client1$ $HADOOP_HOME/bin/hadoop fs -copyFromLocal hly-temp-normal.tsv /user/hac/input/2-3

2.在客户端服务器上编辑hadoop-env.sh文件,将HBase的JAR文件添加Hadoop的classpath中。

hadoop@client1$ vi $HADOOP_HOME/conf/hadoop-env.sh
export HADOOP_CLASSPATH=/usr/local/hbase/current/hbase-0.92.1.jar

3.编写自己MapReduce任务的Java源代码,并将其打包为一个JAR文件。该Java源代码应大致如下。

$ vi Recipe3.java
public class Recipe3 {
  public static Job createSubmittableJob
    (Configuration conf, String[] args)
    throws IOException {
    String tableName = args[0];
    Path inputDir = new Path(args[1]);
    Job job = new Job (conf, "hac_chapter2_recipe3");
    job.setJarByClass(HourlyImporter.class);
    FileInputFormat.setInputPaths(job, inputDir);
    job.setInputFormatClass(TextInputFormat.class);
    job.setMapperClass(HourlyImporter.class);

// ++++ insert into table directly using TableOutputFormat ++++
     TableMapReduceUtil.initTableReducerJob(tableName, null, job);
    job.setNumReduceTasks(0);
    TableMapReduceUtil.addDependencyJars(job);
    return job;
  }

  public static void main(String[] args)
    throws Exception {
    Configuration conf = HBaseConfiguration.create();
    Job job = createSubmittableJob(conf, args);
    System.exit (job.waitForCompletion(true) ? 0 : 1);
  }
}

4.在Recipe3.java中添加一个内部类HourlyImporter。该类是MapReduce任务的映射类(mapper)。

$ vi Recipe3.java
  static class HourlyImporter extends
  Mapper<LongWritable, Text, ImmutableBytesWritable, Put> {

    private long ts;
    static byte[] family = Bytes.toBytes("n");

    @Override
    protected void setup(Context context) {
        ts = System.currentTimeMillis();
    }

    @Override
    public void map(LongWritable offset, Text value, Context
context)throws IOException {
      try {
        String line = value.toString();
        String stationID = line.substring(0, 11);
        String month = line.substring(12, 14);
        String day = line.substring(15, 17);
        String rowkey = stationID + month + day;
        byte[] bRowKey = Bytes.toBytes(rowkey);
        ImmutableBytesWritable rowKey = new ImmutableBytesWritable(bRowKey);
        Put p = new Put(bRowKey);
        for (int i = 1; i < 25 ; i++) {
            String columnI =
              "v" + Common.lpad(String.valueOf(i), 2, '0');
            int beginIndex = i * 7 + 11;
            String valueI = line.substring(beginIndex, beginIndex + 6).trim();
            p.add(family, Bytes.toBytes(columnI), ts, Bytes.toBytes(valueI));
        }
        context.write(rowKey, p);
      }
      catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

5.为了能运行MapReduce任务,需要先将该Java源代码打包成一个JAR文件,然后再在客户端上使用hadoop jar命令来运行该JAR包。

hac@client1$ $HADOOP_HOME/bin/hadoop jar hac-chapter2.jar hac.chapter2. Recipe3 \
 hly_temp \
 /user/hac/input/2-3

查看一下该命令的运行结果。该MapReduce任务的输出应如图2-2所示。

图2-2 MapReduce任务的输出


Map输入记录数(Map input records)的值应当与输入路径中各文件的总行数相等,Map输出记录数(Map output records)的值与输入记录数相等。你也可以使用HBase的countscan命令来对结果进行检查。

若要运行MapReduce任务,首先要在createSubmittableJob()中创建一个任务实例Job。在创建完该实例之后,要设置该任务实例的输入路径、输入格式和映射类。然后,就是调用TableMapReduceUtil.initTableReducerJob()来为我们正确地建立一个任务。其中的设置包括添加HBase配置、设置TableOutputFormat和给该任务添加相关类库。TableMapReduceUtil是在HBase上编写MapReduce程序时的一个非常有用的工具类。

在主方法中,调用job.waitForCompletion()的作用是将任务提交给MapReduce框架,然后等待任务完成。运行中的任务会读取输入路径下的所有文件,并且会将数据一行一行地传递给指定的映射类(HourlyImporter)。

该类的map方法会解析每一行文本,组装行键,创建一个Put对象,然后通过调用Put.add()方法将解析好的数据添加到对应的字段中。最后,我们通过调用context.write()方法将数据写入到HBase表中。在这种情况中,不需要reduce阶段。

正如你所看到的那样,编写一个自定义MapReduce任务将数据插入到HBase中是一件很简单的事情。该程序与我们在“2.2 通过客户端程序导入MySQL数据”一节中所介绍的在一个客户端使用HBase API加载数据的程序很类似。如果要加载大数据量的数据,建议使用MapReduce来将数据加载到HBase中。

在很多情况下,使用自定义MapReduce任务来将数据加载到HBase中都会有不错的效果。但是如果待加载的数据量非常大,那么这种方法的效率可能也还不够高。在进行数据迁移时,还有一些更高效的方法。

一、在MapReduce中生成HFile文件

我们可以不直接将数据写入到HBase表中,而是在自己的MapReduce任务中生成一些内部HFile格式的文件,然后使用在2.3.4节中介绍过的completebulkload工具将这些文件加载到集群中。与简单地使用TableOutputFormat API的方法相比,这种方法所使用的CPU资源和网络资源更少。

1.修改任务的配置信息。为了生成HFile文件,需要在createSubmittableJob()方法中找到下面这两行代码。

TableMapReduceUtil.initTableReducerJob(tableName, null, job);
job.setNumReduceTasks(0);

2.将其替换为下面这段代码。

HTable table = new HTable(conf, tableName);
job.setReducerClass(PutSortReducer.class);
Path outputDir = new Path(args[2]);
FileOutputFormat.setOutputPath(job, outputDir);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(Put.class);
HFileOutputFormat.configureIncrementalLoad (job, table);

3.在命令行参数树添加输出路径参数。在编译并将源程序打好包之后,使用如下增加了输出路径的命令来运行该任务。

hac@client1$ $HADOOP_HOME/bin/hadoop jar hac-chapter2.jar hac.chapter2. Recipe3 \
 hly_temp \
 / user/hac/input/2-3 \
 / user/hac/output/2-3

4.完成批量加载。

hac@client1$ $HADOOP_HOME/bin/hadoop jar $HBASE_HOME/hbase-0.92.1.jar completebulkload \
 /user/hac/output/2-3 \
 hly_temp

在第1步中,我们在源程序中修改了任务的配置信息。我们让任务使用一个名为PutSortReducer的reducer类(该类是HBase自带的)。该类会在将记录输出之前对其进行排序。HFileOutputFormat.configureIncrementalLoad()方法会负责为生成HFile文件的任务设置适当的配置信息。

在执行完第3步之后,任务运行完成,并且已将那些内部HFile格式的文件生成到了我们所指定的输出路径中。在图2-3所示的输出目录文件列表中,目录2-3/n是列族目录,completebulkload工具会把该目录下的文件加载到HBase集群中。

图2-3 生成内部HFile格式文件时所产生的目录结构

在MapReduce任务执行期间,如果从浏览器中打开HBase的管理页面,你会发现:并没有来自HBase的请求(如图2-4所示)。这表明,该任务并没有直接向HBase表中写入数据。

图2-4 HBase的管理页面显示:没有来自HBase的请求

二、影响数据迁移的一些重要配置

如果在MapReduce任务中使用TableOutputFormat直接将数据写入HBase表,那么HBase上的写操作可能就会非常繁重。虽然HBase在设计上能够很迅速地处理写操作,但你可能仍需要对下列这些重要配置参数进行一些调整。

你需要对HBase的架构有一些基本的了解,才能理解这些配置对HBase写性能的影响。在本书的第8章“基本性能调整”和第9章“高级配置和调整”中,我们将更为详细地介绍这方面的内容。

Hadoop和HBase集群会生成有几种类型的日志。你可以通过查看日志来发现一些线索,找出MapReduce数据加载任务执行期间集群的瓶颈在哪里。这些重要的日志包括:

我们将在第8章“基本性能调整”中对此进行更为详尽的介绍。

HBase中的每一行都隶属于某一个特定的区域。在一个区域中,保存有某一范围内的已排好序的HBase记录。区域由区域服务器来部署和管理。

当我们在HBase中创建一张表,该表会自动创建一个区域。插入该表的所有数据会首先进入到这个区域中。随着表中不断有数据插入,当数据量到达一定阈值时,该区域就会分解成两半。这就是所谓的区域分割(region splitting)。分割出来的区域会分布到其他区域服务器上,因此负载可在集群之中得到均衡分配。

正如你想到的那样,如果在初始化表时可以使用某种合适的算法来预先创建好一些区域,那么数据迁移的负载就可以均衡地分布到整个集群中,这样就能显著地提高数据加载的速度。

在本节中,我们将介绍如何创建一张带有预创建区域的表。

登录到你的HBase客户端节点上。

在客户端节点上执行如下命令。

$ $HBASE_HOME/bin/hbase org.apache.hadoop.hbase.util.RegionSplitter -c 10 -f n hly_temp2
12/04/06 23:16:32 DEBUG util.RegionSplitter: Creating table hly_temp2 with 1 column families. Presplitting to 10 regions


12/04/06 23:16:44 DEBUG util.RegionSplitter: Table created! Waiting for regions to show online in META...
12/04/06 23:16:44 DEBUG util.RegionSplitter: Finished creating table with 10 regions

该命令在调用RegionSplitter类时使用了如下几个参数。

在浏览器中打开HBase管理页面,然后单击User Tables 中的hly_temp2。你会看到页面中列出了10个预先创建好的区域(如图2-5所示)。

图2-5 hly_temp2表的10个预先创建好的区域

RegionSplitter是HBase提供的一个工具类。你可以使用RegionSplitter来:

如果现在再运行“2.4 编写自定义MapReduce任务来导入数据”一节所讨论过的那个数据加载任务,你可能会认为“这一次数据的写操作会分布在集群的所有区域服务器上执行”,但实际情况并非如此。通过查看管理页面(如图2-6所示),你将发现该MapReduce任务执行期间的所有请求都发送给了同一台服务器。

图2-6 默认分割算法将所有请求都发给了一台区域服务器

出现这种情况的原因是:默认分割算法(MD5StringSplit)不适合我们的情况。按照这种分割算法,我们的所有行都落在了同一区域中,因此所有API请求都发给了存放该区域的区域服务器。为了能正确地进行区域分割,我们需要提供一个自定义的分割算法。

对于那些要生成内部HFile格式文件的MapReduce任务来说,预分割区域也会改变任务的行为方式。现在,我们以打开对hly_temp2表生成HFile文件的选项的方式再次运行一下“2.4 编写自定义MapReduce任务来导入数据”一节中的那个任务。在图2-7所示的HBase管理页面中可以看到:MapReduce任务的reduce计数会从原来的1一下跳到了10,而10正好是预先创建的区域的个数。

图2-7 MapReduce任务的reduce进程数会随着目标表的区域个数而变化

这是因为:任务的reduce进程数是由目标表的区域个数所决定的。如果reduce进程数增加了,通常都意味着负载被分布到了多台服务器上,因此任务的执行速度就会快很多。


相关图书

HBase入门与实践(第2版)
HBase入门与实践(第2版)
HBase入门与实践
HBase入门与实践
HBase权威指南
HBase权威指南
HBase实战
HBase实战

相关文章

相关课程