上篇文章简单总结了一下,在独立模式下实现测试第一个MapReduce程序,下面算是对上篇文章的一个补充吧。
主要 分为 Hadoop横向扩展、combiner函数、Hadoop Streaming 三个部分。
前面介绍了 MapReduce针对少量数据是如何工作的,当我们有大量的输入数据流时,为了实现横向扩展,我们需要把数据存储在分布式分拣系统中(典型的是HDFS),通过使用Hadoop资源管理系统YARN,Hadoop可以将MapReduce计算转移到存储有部分数据的各台机器上。
我们可以通过数据流查看具体过程。
MapReduce作业(job):客户端需要执行的一个工作单元,包括输入数据、MapReduce程序、配置信息。
作业又可分为若干个任务(task)来执行,包括map 任务和reduce任务。
输入分片(input split):Hadoop将MapReduce的输入数据划分成等长的小数据块。一个分片对应一个map任务。
如何决定输入分片的大小?
一般来说,分片切分的越细,集群的负载平衡的质量会更高。但是,反过来讲,如果分片太小,那么管理分片的总时间和构建map任务的总时间将决定整个执行时间。对于大多数作业来说,一个合理的分片大小趋向于一个HDFS数据块的大小,默认是128M,不过可以调整。
为什么最佳大小应该与块大小相同?
因为它是确保可以存储在单个节点上的最大输入块的大小。如果分片跨越两个数据块,那么对于任何一个HDFS节点,基本上都不太可能同时存储这两个数据块,因此分片中的部分数据可能需要网络传输到map任务运行的节点,这违背了“数据本地化优化”原则(Hadoop在存储有输入数据(HDFS中的数据)的节点上运行map任务,可以获得最佳性能,因为这样无需使用宝贵的集群带宽资源)。
为何map任务将其输出写入本地硬盘而非HDFS?
因为map的输出是中间结果,该结果由reduce任务处理后才产生最终结果,而且一旦作业完成,map的输出结果就可以删除。因此,如果把它存储在HDFS中并实现备份,难免小题大做。如果运行map的任务在将输出纯送给reduce任务之前失败,那么Hadoop经在另一个节点上重新运行map任务以再次获得map任务中间结果。
reduce任务并不具备数据本地化的优势,因为其输入通常来自于所有map任务的输出。排过序的 map输出需要通过网络传输发送到reduce任务的节点。数据在reduce端合并,然后有用户定义的reduce函数处理,reduce的输出通常存储在HDFS以实现可靠存储,对于每个reduce输出的HDFS块,第一个副本存储在本地节点上,其他节点处于可靠性考虑存储在其他机架的节点中。
注意,reduce任务的数量并非输入数据的大小决定,相反是独立指定的。
若果有多个reduce任务,那么map任务会针对输出进行分区(partition),即为每一个reduce任务建一个分区。分区可由用户定义的分区函数控制,但通常用默认的partitioner通过哈希函数分区 ,很高效。
默认的partitioner是HashPartitioner,它对每条记录的键进行哈希操作,以决定该条记录属于哪个分区。每个分区由一个reduce任务处理,所以分区数等于作业的reduce任务数。
public class HashPartitioner extends Partitioner{
public int getPartition(K key,V value,int numPartitions){
return (key.hashCode() & Integer.MAX_VALUE) % numPartitions;
}
}
可以看出,键的哈希码被转换为一个非负整数,它由键的哈希值与最大的整数值做一次按位与操作获得,然后用分区数进行取模操作,得到记录属于哪个分区。默认情况下,只有一个reducer,因此也只有一个分区。
为一个作业选择多少reducer,由于并行化程度的提高,增加reducer的数量能缩短reduce的过程,然而,如果做过了,小文件将会更多,这又不够优化,一条经验法则是:目标reduce保持在每个运行5分钟左右,且产生至少一个HDFS块的输出比较合适。
下面是 一个reduce任务的MapReduce数据流、多个reduce任务的数据流、无reduce任务的MapReduce数据流
其中,虚线框表示节点,虚线箭头表示节点内部的数据传输,而实线箭头表示不同节点之间的数据传输。
图1 .一个reduce任务的MapReduce数据流
图2. 多个reduce任务的数据流
图3. 无reduce任务的MapReduce数据流
集群中的可用带宽限制了MapReduce作业的数量,因此应尽量避免map和reduce任务之间的数据传输。Hadoop允许用户针对map任务的输出指定一个combiner,combiner的输出作为reduce函数的输入。
举例说明:(还是之前那个测试程序的例子)
假设第一个map的输出如下:
(1950,0)
(1950,20)
(1950,10)
第二个map的输出如下:
(1950,25)
(1950,15)
reduce函数被调用时,输入如下:
(1950,[0,20,10,25,15])
因为25是输入数据中最大的,所以输出 25
我们可以像使用reduce函数那样,使用combiner函数找到每个map输出的最大值,然后传递给reduce任务,如此,reduce函数将接受如下:
(1950,[20,25])
可以发现,reduce的输出还是和之前一致 25.可以通过以下表达式说明函数调用:
max(0,20,10,25,15) = max(max(0,20,10),max(25,15)) = max(20,25) = 25
注意,并不是所有函数都具有这样的性质。例如。如果求平均气温,就不能用mean函数作为combiner:
mean(0,20,10,25,15) = 14
但是 mean(mean(0,20,10),mean(25,15)) = mean(10,20) = 15
combiner不能取代reduce。因为我们仍需要reduce处理不同map输出的相同键的记录。
指定一个combiner
可以通过job对象的setCombinerClass()方法实现为作业指定combiner,例如,我们上篇的测试程序可以指定reduce函数作为
combiner:
job.setCombinerClass(MaxTemperatureReducer.class);
Hadoop 提供了 MapReduce的API,允许使用非java的其他语言实现自己的map和reduce函数。
Hadoop Streaming 使用 UNIX标准流作为 Hadoop和 应用程序之间的接口,所以我们可以使用任何编程语言通过标准输入输出流来写MapReduce程序。
Streaming 天生适合文本处理,map的输入数据通过标准输入流传递给map函数,并且是一行一行的传输,最后将结果行写到标准输出。map输出的键-值对是以制表符分割的行,reduce函数的输入与之相同。reduce函数通过标准输入流读取数据,注意,该输入已由Hadoop框架根据键拍过序,最终将结果写入标准输出。
下面按Streaming重写按年份查找最高气温的MapReduce程序(输出结果应该和上篇的结果一致)
本人对python比较熟悉,故只展示了Python版(书中另附有Ruby版)
查找最高气温的map函数:(max_temperature_map.py)
#!/usr/bin/python
import sys
import re
for line in sys.stdin:
val = line.strip()
(year,temp,q) = (val[15:19],val[87:92],val[92:93])
if (temp != "+9999" and re.match("[01459]",q)):
print "%s\t%s" % (year,temp)
查找最高气温的reduce函数:(max_temperature_reduce.py)
#!/usr/bin/python
import sys
(last_key,max_val) = (None,-sys.maxint)
for line in sys.stdin:
(key,val) = line.strip().split("\t")
#存储最后一个键以及迄今为止看到的该键的最高气温,MapReduce框架决定了键的有序性,所以当遇到一个不同的键时,就需要开始处理一个新的键组
if last_key and last_key != key:
print "%s\t%s" % (last_key,max_val)
(last_key,max_val) = (key,int(val))
else:
(last_key,max_val) = (key,max(max_val,int(val)))
#确保处理完最后一个键组时会有一行输出
if last_key:
print "%s\t%s" % (last_key,max_val)
测试运行:(sample.txt还是之前那个测试样例,并且和两个Python程序放在同一目录下)
cat sample.txt | ./max_temperature_map.py | sort | ./max_temperature_reduce.py
可以看到结果与上一篇的测试结果相同。
可以看到 ,map和reduce之间与sort处理,这也是MapReduce框架需要保证键的有序性。
参考:《Hadoop权威指南》