动手实现第一个Hadoop程序的扩展

上篇文章简单总结了一下,在独立模式下实现测试第一个MapReduce程序,下面算是对上篇文章的一个补充吧。

主要 分为  Hadoop横向扩展、combiner函数、Hadoop Streaming 三个部分。

1 Hadoop横向扩展

    前面介绍了 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<K,V> extends Partitioner<K,V>{
    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数据流 

2 combiner函数

集群中的可用带宽限制了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);
3 Hadoop Streaming

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权威指南》






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值