上一篇文章我们已经进行了伪分布式配置,也通过几个例子,看到了如何在伪分布式下进行MapReduce操作。今天的文章主要看看在分布式下的MapReduce是如何工作的,然后通过一个小例子,看看Java操作HDFS的方式。
上图是分布式下MapReduce的执行流程,可以看到除了map和reduce之外,还有很多的步骤,这些步骤,我们或多或少的都可以通过配置或者编码的方式对其进行控制,这也是我们对MapReduce进行性能调优的手段。
-
输入文件被上传到HDFS之后,会被分片,默认Hadoop集群会保存每个分片的三个副本(伪分布式下我们设置为只有一个副本),如果按照上图来看,在HDFS中会分别有3个spilt1,3个spilt2和3个spilt3。这是为了通过数据冗余保证数据存储的可靠性,当数据出现问题是,可进行恢复。分片的大小默认为HDSF文件块的大小(128M),这样可以有更好的性能。
-
Hadoop会为每一个spilt分配一个map任务,也就是说,spilt的数目决定了map任务的数目。分配过程遵循一定的原则,首先,如果保存spilt数据的节点处于空闲状态,那么就在这个节点上执行map任务;但是,如果数据存储的节点正在执行其他的任务,那么Hadoop会找数据节点同一个机架中距离最近的空闲节点执行map任务;最后,如果上面的情况都无法满足,就只好跨机架执行map任务了。总的目的就是为了尽可能减少数据的传输,优化性能。
-
这样,map任务就分别在各自的节点中并发运行了,这时会对map任务的输出进行排序。这里就需要注意了,由于文件分片只有文件的一部分数据,map处理后的排序是分别针对各自的处理结果进行的,也就是说,这里排序后的结果是部分排序,并不是对输入文件所有数据的排序。
如果需要对整个文件排序,也是有方法的,一种是阻止Hadoop对文件进行分片,还有一种方式是将分片后的文件聚合后,交给一个map任务处理。
-
为了优化性能,map之后的结果是在内存中进行排序的,这时候没有必要将结果写入到磁盘上,但是,reduce需要从磁盘上获取数据,数据写入磁盘的操作被推后到merge阶段,各个map任务处理后的结果,被复制到reduce任务所在的节点,在merge阶段写入磁盘,进而reduce运行,输出结果。reduce任务的数量可以配置,默认为1,如果有多个reduce任务,会在各自的节点上生成结果的分区。
以上就是MapReduce在分布式情况下的运行过程,我们再来看看如何操作HDFS,上一篇文章中,我们使用命令行操作了HDFS,今天看看如何用Java代码实现。我们使用一个简单的例子,将一个字符串写入到HDFS,然后再读取出来显示到终端。
package com.yjp.hdfs;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
public class HDFSEcho {
// yjp替换为你的用户名
private static final URI TMP_URI =
URI.create("hdfs://localhost/user/yjp/temp");
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("Usage: HDFSEcho string");
System.exit(1);
}
String echoString = args[0] + "\n";
// 通过URI创建FileSystem,由于是hdfs,会返回代表HDFS的实例
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(TMP_URI, conf);
FSDataOutputStream out = null;
FSDataInputStream in = null;
try {
// 写入文件
Path path = new Path(TMP_URI);
out = fs.create(path);
out.writeUTF(echoString);
IOUtils.closeStream(out);
//从文件读取并输出
in = fs.open(path);
IOUtils.copyBytes(in, System.out, conf);
IOUtils.closeStream(in);
//删除文件
fs.delete(path, true);
} finally {
IOUtils.closeStream(out);
IOUtils.closeStream(in);
}
}
}
打包为jar文件,记得启动hadoop的后台进程,然后运行试试。
hadoop jar 你的包名.HDFSEcho "Hello HDFS!"
输出为
Hello HDFS!
这部分代码在hdfs包中。