云计算 频道

用Hadoop进行分布式并行编程, 第3 部分

  【IT168 资讯】一 前言 

在本系列文章的第一篇:用 Hadoop 进行分布式并行编程,第 1 部分: 基本概念与安装部署中,介绍了 MapReduce 计算模型,分布式文件系统 HDFS,分布式并行计算等的基本原理, 并且详细介绍了如何安装 Hadoop,如何在单机及伪分布式环境 (在一台单机中用多进程模拟) 运行基于 Hadoop 的并行程序。在本系列文章的第二篇:用 Hadoop 进行分布式并行编程,第 2 部分:程序实例与分析中,介绍了如何针对一个具体的计算任务,基于 Hadoop 编写 MapReduce 并行程序。 本文将介绍真实的 Hadoop 分布式运行环境, 包括如何在多台普通的计算机上部署分布式运行环境,如何将 MapReduce 程序远程部署并运行在这个分布式环境上,并简略介绍了”云计算平台” 以及计算能力按需租用服务。

二 准备工作

1. 硬件与网络

使用三台机器,机器名分别为 homer06, homer07, homer08,均安装 Redhat Enterprise Linux 5.0 (其它 Linux 发行版亦可), 确保各台机器之间网络畅通,机器名与 IP 地址之间解析正确,从任一台机器都可以 ping 通其它机器的机器名。如有机器名的解析问题,可通过设置 /etc/hosts 文件解决,当然更好的解决方法是在你的网络中配置 DNS 服务器。此外,需要在三台机器上创建相同的用户帐号,如 caoyuz, 或直接使用 root 帐号亦可。

我们将使用 homer06 作为分布式文件系统 HDFS 的 Name Node 及 MapReduce 运行过程中的 Job Tracker 结点,我们将 homer06 称之为主结点。其它两台机器 (homer07, homer08) 作为 HDFS 的 Data Node 以及 MapReduce 运行过程中的 Task Tracker 结点,这些结点可统称为从结点。如你需要部署更多的机器,也是很容易的,将新加入的机器作为 Data Node 以及 Task Tracker 结点即可,其配置过程与本文介绍的三台机器的环境类似,此不赘述。

2. SSH 配置

在 Hadoop 分布式环境中,Name Node (主节点) 需要通过 SSH 来启动和停止 Data Node (从结点)上的各类进程。我们需要保证环境中的各台机器均可以通过 SSH 登录访问,并且 Name Node 用 SSH 登录 Data Node 时,不需要输入密码,这样 Name Node 才能在后台自如地控制其它结点。可以将各台机器上的 SSH 配置为使用无密码公钥认证方式来实现。

现在流行的各类 Linux 发行版一般都安装了 SSH 协议的开源实现 OpenSSH, 并且已经启动了 SSH 服务, 即这些机器缺省应该就是支持 SSH 登录的。如果你的机器缺省不支持 SSH, 请下载安装 OpenSSH。

以下是配置 SSH 的无密码公钥认证的过程。首先,在 homer06 机器上执行命令,如代码清单 1 所示:

代码清单1

homer06: $ ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/caoyuz/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/caoyuz/.ssh/id_rsa.
Your public key has been saved in /home/caoyuz/.ssh/id_rsa.pub.
The key fingerprint is:
2e:57:e2:bf:fd:d4:45:5c:a7:51:3d:f1:51:3c:69:68 root@krusty04

这个命令将为 homer06 上的当前用户 caoyuz 生成其密钥对,密钥对的保存路径使用缺省的 /home/caoyuz/.ssh/id_rsa, 要求输入 passphrase 的时候,直接回车。这样生成的证书以及公钥将存储在 /home/caoyuz/.ssh 目录,形成两个文件 id_rsa,id_rsa.pub。然后将 id_rsa.pub 文件的内容复制到每一台机器(包括本机 homer06)的  /home/caoyuz/.ssh/authorized_keys 文件的尾部,如果机器上不存在 /home/caoyuz/.ssh/authorized_keys 文件,可以自行创建一个。请注意 id_rsa.pub 文件的内容是长长的一行,复制时需注意,不要遗漏字符或混入了多余换行符。

接下来可以做一下 SSH 连接测试,从 homer06 分别向 homer06, homer07, homer08 发起 SSH 连接请求,确保不需要输入密码就能 SSH 连接成功。注意第一次 SSH 连接时会出现如下提示信息:

The authenticity of host [homer06] can't be established. The key fingerprint is: 74:32:91:f2:9c:dc:2e:80:48:73:d4:53:ab:e4:d3:1a Are you sure you want to continue connecting (yes/no)?

请输入 yes, 这样 OpenSSH 会把连接过来的这台主机的信息自动加到 /home/caoyuz/.ssh/know_hosts 文件中去,第二次再连接时,就不会有这样的提示信息了。

三 安装部署 Hadoop

1. 安装 Hadoop 及 jre1.5

我们首先在主控结点 homer06 上安装和配置好 Hadoop,安装过程可以参考本系列文章的第一篇。假定我们把 Hadoop 安装在 /home/caoyuz/hadoop-0.16.0目录中,并且 JRE 1.5 安装在 /home/caoyuz/jre 目录下。

2. 修改 conf/hadoop-env.sh 文件

在其中设置 JAVA_HOME 环境变量:export JAVA_HOME=”/home/caoyuz/jre”

3. 修改 conf/hadoop-site.xml 文件

在本系列文章的第一篇中,我们通过修改此文件,配置了 Hadoop 的伪分布式运行模式。现在,我们同样可以通过配置此文件,配置 Hadoop 的真实的分布式运行环境。请参照代码清单 2 修改 conf/hadoop-site.xml:

代码清单2
<configuration>
<property>
<name>fs.default.name</name>
<value>homer06.austin.ibm.com:9000</value>
<description>The name of the default file system. Either the literal string
"local" or a host:port for DFS.</description>
</property>
<property>
<name>mapred.job.tracker</name>
<value>homer06.austin.ibm.com:9001</value>
<description>The host and port that the MapReduce job tracker runs at. If
"local", then jobs are run in-process as a single map and reduce task.</description>
</property>
<name>dfs.name.dir</name>
<value>/home/caoyuz/hadoopfs/name</value>
<description>Determines where on the local filesystem the DFS name node
should store the name table. If this is a comma-delimited list of directories
then the name table is replicated in all of the directories,
for redundancy. </description>
</property>
<property>
<name>dfs.data.dir</name>
<value>/home/caoyuz/hadoopfs/data</value>
<description>Determines where on the local filesystem an DFS data node
should store its blocks. If this is a comma-delimited list of directories,
then data will be stored in all named directories, typically on different devices.
Directories that do not exist are ignored.</description>
</property>
<property>
<name>dfs.replication</name>
<value>2</value>
<description>Default block replication. The actual number of replications
can be specified when the file is created. The default is used if replication
is not specified in create time.</description>
</property>
</configuration>

参数 fs.default.name 指定 Name Node 的 IP 地址和端口号,此处我们将其设定为 homer06 及 9000 端口,参数 mapred.job.tracker 指定 JobTracker 的 IP 地址和端口号,此处我们将其设定为 homer06 及 9001 端口。 参数 dfs.name.dir 指定 Name Node 相关数据在本地文件系统上的存放位置, 此处我们将其设定为 /home/caoyuz/hadoopfs/name ,参数 dfs.data.dir 指定 Data Node 相关数据在本地文件系统上的存放位置,此处我们将其设定为 /home/caoyuz/hadoopfs/data 。注意, Hadoop 会自动创建这两个目录,无需事先创建。

更多的参数配置,可以参考 conf/hadoop-default.xml 文件,并在 conf/hadoop-site.xml 文件中设置。

4. 设定主从节点

修改 conf/masters 文件,将其中的 localhost 改为 homer06 ,修改 conf/slaves 文件, 删掉其中的 localhost, 将我们的另两台机器 homer07, homer08 加入, 注意每个机器一行。

5. 将 Hadoop 部署到其它机器上去

至此, 我们已经在 homer06 上安装和配置好了 hadoop 和 jre, 现在需要将其部署到其它机器上去,通过 scp 命令即可完成,如代码清单 3 所示:

代码清单3

homer06: $ scp -r /home/caoyuz/hadoop-0.16.0 homer07:/home/caoyuz/hadoop-0.16.0
homer06: $ scp -r /home/caoyuz/jre homer07:/home/caoyuz/jre
homer06: $ scp -r /home/caoyuz/hadoop-0.16.0 homer08:/home/caoyuz/hadoop-0.16.0
homer06: $ scp -r /home/caoyuz/jre homer08:/home/caoyuz/jre

其中用 scp 拷贝 jre 目录到其它机器上去不是必须的。你只需保证你的所有机器上均安装了 JRE1.5 以上版本,并且都是安装在同一目录。

6. 在 homer06 上格式化一个新的分布式文件系统

如代码清单 4 所示:

代码清单4

homer06: $ cd /home/caoyuz/hadoop-0.16.0
homer06: $ bin/hadoop namenode -format

7. 在 homer06 上启动 hadoop 进程

如代码清单5所示:

代码清单5

homer06: $ cd /home/caoyuz/hadoop-0.16.0
homer06: $ bin/start-all.sh

启动完成之后,运行 ps -ef 命令应该可以看到 homer06 上启动了 3 个新的 java 进程 (namenode, secondary namenode, jobtracker), 同时,我们可以到 homer07, homer08 两台机器上用 ps –ef 查看,这两台机器上应该已经自动启动了 2 个新的 java 进程 (datanode, tasktracker)

四 运行 Hadoop 程序

至此,整个 Hadoop 分布式环境已经部署完毕,并已启动相关后台进程。现在我们可以尝试运行一下我们在第二篇文章中介绍的 wordcount 程序,如代码清单 6 所示:

代码清单 6

homer06: $ mkdir -p /home/test-in
# 请先将待测的文件放到本地文件系统的/home/test-in目录
homer06: $ cd /home/caoyuz/hadoop-0.16.0
homer06: $ bin/hadoop dfs –put /home/test-in input
# 将本地文件系统上的 /home/test-in 目录拷到 HDFS 的根目录上,目录名改为 input
$ bin/hadoop jar hadoop-0.16.0-examples.jar wordcount input output
#查看执行结果:
# 将文件从 HDFS 拷到本地文件系统中再查看:
$ bin/hadoop dfs -get output output
$ cat output/*
# 也可以直接查看
$ bin/hadoop dfs -cat output/*

代码清单 6 所示的执行 wordcount 程序的过程,与我们在第一篇文章中介绍的在伪分布式运行环境运行完全一致,但我们现在拥有了一个真正的分布式执行环境,我们的数据分布存储于数据节点 homer07 及 homer08 上,可以在这两台机器的 /home/caoyuz/hadoopfs/data 目录 (这是我们在 conf/hadoop-site.xml 中指定的 dfs.data.dir 参数) 下看到一些数据文件,并且整个 wordcount 的计算过程神奇地由 homer06, homer07, homer08 三台机器并行协同完成,我们还可以很方便的增加更多的机器来参与运算。这就是分布式并行程序的优势: 可以很容易地通过加入新的机器来获得更多的存储空间和计算能力, 部署的机器越多, 就越能有效地完成海量数据的计算。

五 使用 IBM MapReduce Tools 部署分布式程序

在第二篇文章中,已经介绍了 IBM MapReduce Tools 的基本功能和用法。现在我们重点介绍如何使用 IBM MapReduce Tools 将 MapReduce 程序远程部署到 Hadoop 分布式环境中去运行。

假定我们还是使用上一节部署完成的分布式环境,然后是在另一台机器上使用 Eclipse 开发 MapReduce 程序。

1. 定义 Hadoop server 的位置

首先请确保你的 Eclipse 已经安装了 IBM MapReduce Tools 这个插件。启动 Eclipse, 选择 Window -> Open Perspective ->other, 再从弹出框中选择 MapReduce, 这样 Eclipse 会进入专门的 MapReduce 视图 ( perspective )。

随后,请检查你的 MapReduce perspective中是否有一个专门的 MapReduce Servers view, 如果没有,请选择 Window -> Show View ->other, 再从弹出框中选择 MapReduce Tools 类别下面的 MapReduce Servers, 打开这个 view.

然后,请点击 MapReduce Servers view 右上角的蓝色图标,就会出现如图一所示的设置 Hadoop Server 的位置的界面。此处所说的 Hadoop server,具体到本文,就是 homer06 这台机器。在输入各项参数之后,请点击 ”Validate location” 按钮,检查是否能够正确的找到并连接上你的 Hadoop server. 如果出错,请尝试在命令行下执行命令:ssh the_hostname_of_your_hadoop_server, (或使用图形界面的 SSH 远程登录软件), 确保 ssh 能够连接成功。

2. 创立一个 MapReduce Project

在 Eclipse 中新创建一个 MapReduce Project, 将我们在第二篇文章中定义的 WordCount 类加到此 Project 中。这个类需要略作修改才能直接远程部署到我们已经搭建好的分布式环境中去运行,因为我们原来在 WordCount 程序中是通过读取命令行参数获得计算任务的输入路径和输出路径,而当前版本的 IBM MapReduce Tools 不支持远程部署时读取命令行参数。为测试的简便起见,我在程序中直接将输入路径定义为 input, 输出路径定义为 output。在测试 WordCount 程序之前,需要事先将需要做词频统计的一批文件拷贝到分布式文件系统的 input 目录下去。

完整的 WordCount 类的代码如代码清单 7 所示:

代码清单7

//import 语句省略
public class WordCount extends Configured implements Tool {

public static class MapClass extends MapReduceBase
implements Mapper<LongWritable, Text, Text, IntWritable> {

private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
private String pattern="[^\\w]";
public void map(LongWritable key, Text value,
OutputCollector<Text, IntWritable> output,
Reporter reporter) throws IOException {
String line = value.toString().toLowerCase();
line = line.replaceAll(pattern, " ");
StringTokenizer itr = new StringTokenizer(line);
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
output.collect(word, one);
}
}
}

public static class Reduce extends MapReduceBase
implements Reducer<Text, IntWritable, Text, IntWritable> {

public void reduce(Text key, Iterator<IntWritable> values,
OutputCollector<Text, IntWritable> output,
Reporter reporter) throws IOException {
int sum = 0;
while (values.hasNext()) {
sum += values.next().get();
}
output.collect(key, new IntWritable(sum));
}
}

public int run(String[] args) throws Exception {

Path tempDir = new Path("wordcount-temp-" +
Integer.toString(new Random().nextInt(Integer.MAX_VALUE)));

JobConf conf = new JobConf(getConf(), WordCount.class);
try {
conf.setJobName("wordcount");

conf.setOutputKeyClass(Text.class);
conf.setOutputValueClass(IntWritable.class);

conf.setMapperClass(MapClass.class);
conf.setCombinerClass(Reduce.class);
conf.setReducerClass(Reduce.class);

conf.setInputPath(new Path(args[0]));
conf.setOutputPath(tempDir);

conf.setOutputFormat(SequenceFileOutputFormat.class);

JobClient.runJob(conf);

JobConf sortJob = new JobConf(getConf(), WordCount.class);
sortJob.setJobName("sort");

sortJob.setInputPath(tempDir);
sortJob.setInputFormat(SequenceFileInputFormat.class);

sortJob.setMapperClass(InverseMapper.class);

sortJob.setNumReduceTasks(1);
sortJob.setOutputPath(new Path(args[1]));
sortJob.setOutputKeyClass(IntWritable.class);
sortJob.setOutputValueClass(Text.class);

sortJob.setOutputKeyComparatorClass(IntWritableDecreasingComparator.class);
JobClient.runJob(sortJob);
} finally {
FileSystem.get(conf).delete(tempDir);
}
return 0;
}

private static class IntWritableDecreasingComparator extends IntWritable.Comparator {

public int compare(WritableComparable a, WritableComparable b) {
return -super.compare(a, b);
}

public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
return -super.compare(b1, s1, l1, b2, s2, l2);
}
}

public static void main(String[] args) throws Exception {

String[] paths = {"input" , "output"};
int res = ToolRunner.run(new Configuration(), new WordCount(), paths);
System.exit(res);
}
}

六 云计算与 Hadoop 

我们知道,在分布式集群环境中才能发挥 Hadoop 的并行优势,拥有的机器数量越多,越能快速有效的处理海量数据。现实问题是,虽然很多公司都有处理海量数据的需求,却又不可能专门投资去搭建大规模的集群环境,Hadoop 于他们,不免沦为”屠龙之技”,无处发挥其优势,如之奈何?在过去,这个问题还真是难以解决,今天的情况就不一样了。读者如果关注 IT 业界动态,当知现在 IT 业界正在极力鼓吹”云计算”, 并有一些公司开始投资搭建所谓的”云计算平台”,这里的”云”, 就是一堆机器组成的分布式环境外加一些基础构架软件和管理软件,其中便会有类似于 Hadoop 这样的分布式计算软件,HDFS 这样的分布式文件系统,有需求的公司和个人可以到这样的”云计算平台”上去租用存储空间,租用计算结点(计算能力)做分布式运算。

比如 Amazon 公司基于 Hadoop 推出了 Amazon S3 ( Amazon Simple Storage Service ),提供可靠,快速,可扩展的网络存储服务,以及一个商用的云计算平台 Amazon EC2 ( Amazon Elastic Compute Cloud )。用户可以将其数据存储在 Amazon S3 分布式存储平台上, 然后到 Amazon EC2 上去租用计算能力,完成对数据的计算。Amazon EC2 提供所谓的按需租用服务,目前的收费标准是每台虚拟计算机 (Amazon EC2 称之为一个 instance) 每小时0.10美元。与传统的主机租用服务完全不同,用户可以根据自己某次运算处理的规模,租用相应数量的虚拟计算机,运算完毕后就可以释放你租用的虚拟计算机,Amazon 则会根据你租用的虚拟计算机的数量以及本次计算的实际运行时间向你收费,等于说你花钱租用计算能力,但不会浪费一个子儿。IBM 公司的云计算平台"蓝云"也面向企业用户提供了类似的功能。

如果我们打算基于 Hadoop 编写分布式并行程序来处理大量的数据,完全可以到 IBM, Amazon 等提供的云计算平台上去进行计算,对于 IBM 蓝云,Amazon S3, Amazon EC2 的详细介绍超出了本文范围,有兴趣的读者可以去其官方网站了解更多的信息。

七 结束语

这是系列文章的最后一篇。第一篇文章介绍了 MapReduce 计算模型,分布式文件系统 HDFS,分布式并行计算等的基本原理, 如何安装和部署单机 Hadoop 环境, 在第二篇文章中,我们实际编写了一个 Hadoop 并行计算程序,并了解了一些重要的编程细节,了解了如何使用 IBM MapReduce Tools 在 Eclipse 环境中编译,运行和调试 Hadoop 并行计算程序。本篇文章则详细介绍了如何部署分布式 Hadoop 环境,如何利用 IBM MapReduce Tools 将程序部署到分布式环境中运行,并简略介绍了现在流行的”云计算平台” 以及计算能力按需租用服务。

希望这三篇文章能起到一个抛砖引玉的作用,让你感受到 MapReduce 分布式并行编程的乐趣并从此入门且乐在其中,为即将到来的所谓”云计算”时代提前热热身。

0
相关文章