Hadoop之MapReduce

本文详细介绍了Hadoop的MapReduce模型,包括其分而治之的思想、工作流程、核心组件如Mapper、Reducer和Combiner。Map过程涉及数据切分、自定义逻辑处理、Shuffle阶段的分区、排序和归约。Reduce阶段则涵盖数据合并和Shuffle过程。文中还探讨了WordCount实操案例、二次排序、自定义InputFormat和OutputFormat以及Join操作。
摘要由CSDN通过智能技术生成

MapReduce的思想核心是“分而治之”,适用于大规模数据处理场景。Map负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系;Reduce负责“合”,即对map阶段的结果进行全局汇总
在这里插入图片描述

MapReduce为程序员提供了一个清晰的操作接口抽象描述,MapReduce中定义了如下的MapReduce两个抽象的编程接口,由用户去编程实现.MapReduce,MapReduce处理的数据类型是<key,value>键值对
Map: (k1; v1)[(k2; v2)]
Reduce: (k2; [v2])[(k3; v3)]

一个完整的MapReduce程序在分布式运行时有三类实例进程:

  1. MRAppMaster 负责整个程序的过程调度及状态协调
  2. MapTask 负责Map阶段的整个数据处理流程
  3. ReduceTask 负责Reduce阶段的整个数据处理流程

Map 阶段 2 个步骤

  1. 设置InputFormat类, 将数据切分为Key-Value(K1和V1)对, 输入到第二步
  2. 自定义Map逻辑, 将第一步的结果转换成另外的Key-Value(K2和V2) 对, 输出结果

Shuffle阶段四个步骤

  1. 对输出的Key-Value对进行分区
  2. 对不同分区的数据按照相同的Key排序
  3. (可选) 对分组过的数据初步规约, 降低数据的网络拷贝
  4. 对数据进行分组, 相同KeyValue放入一个集合中

Reduce 阶段 2 个步骤

  1. 对多个Map任务的结果进行排序以及合并, 编写Reduce函数实现自己的逻辑, 对输入的
    Key-Value进行处理, 转为新的Key-Value(K3和V3)输出
  2. 设置OutputFormat处理并保存Reduce输出的Key-Value数据

WordCount实操案例

数据格式准备

cd /export/servers
vim wordcount.txt
#向其中放入以下内容并保存
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
#上传到 HDFS
hdfs dfs -mkdir /wordcount/
hdfs dfs -put wordcount.txt /wordcount/

Mapper

public class WordCountMapper extends Mapper<LongWritable,Text,Text,LongWritable> {
    @Override
    public void map(LongWritable key, Text value, Context context) throws
IOException, InterruptedException {
        String line = value.toString();
        String[] split = line.split(",");
        for (String word : split) {
            context.write(new Text(word),new LongWritable(1));
       }
   }
}

Reducer

public class WordCountReducer extends
Reducer<Text,LongWritable,Text,LongWritable> {
    /**
     * 自定义我们的reduce逻辑
     * 所有的key都是我们的单词,所有的values都是我们单词出现的次数
     * @param key
     * @param values
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
        long count = 0;
        for (LongWritable value : values) {
            count += value.get();
       }
        context.write(key,new LongWritable(count));
   }
}
二次排序

默认情况下,在MapReduce中的shuffer阶段会自动进行排序,而且是根据key进行排序的。但是有时候需要对key排序的同时再对value进行排序,这时候就要用到二次排序了。

MapReduce过程详解

MapReduce 排序和序列化

序列化 (Serialization) 是指把结构化对象转化为字节流,反序列化 (Deserialization) 是序列化的逆过程. 把字节流转为结构化对象. 当要在进程间传递对象或持久化对象的时候, 就需要序列化对象成字节流, 反之当要将接收到或从磁盘读取的字节流转换为对象, 就要进行反序列化。WritableHadoop的序列化格式, Hadoop定义了这样一个Writable接口. 一个类要支持可序列化只需实现这个接口即可。

Map过程概述

整个Map阶段流程简单概述:inputFile通过split被逻辑切分为多个split文件,通过Record按行读取内容给map(用户自己实现的)进行处理,数据被map处理结束之后交给OutputCollector收集器,对其结果key进行分区(默认使用hash分区),然后写入缓冲区每个maptask都有一个内存缓冲区,存储着map的输出结果,当缓冲区快满的时候需要将缓冲区的数据以一个临时文件的方式存放到磁盘,当整个map task结束后再对磁盘中这个map task产生的所有临时文件做合并,生成最终的正式输出文件,然后等待reduce task来拉数据。
在这里插入图片描述

Mapper
  1. 读取数据组件InputFormat 会通过getSplits方法对输入目录中文件进行逻辑切片规划得到block, 有多少个block就对应启动多少个MapTask
  2. RecordReader对象进行读取, 以\n作为分隔符, 读取一行数据, 返回<key,value>Key 表示每行首字符偏移值, Value表示这一行文本内容
  3. 读取block返回<key,value>, 进入用户自己继承的Mapper类中,执行用户重写的map函数, RecordReader读取一行这里调用一次

分区Partition

Mapper逻辑结束之后, 将Mapper的每条结果通过context.write进行collect数据收集。 在collect中, 会先对其进行分区处理,默认使用HashPartitioner。当map函数通过context.write()开始输出数据时,不是单纯地将数据写入到磁盘。为了性能,map输出的数据会写入到缓冲区,并进行预排序的一些工作

MapReduce提供Partitioner接口, 它的作用就是根据KeyValueReducer的数量来决定当前的这对输出数据最终应该交由哪个Reducetask处理, 默认对Key Hash后再以Reducer数量取模. 默认的取模方式只是为了平均Reducer的处理能力, 如果用户自己对Partitioner有需求, 可以订制并设置到Job上

环形Buffer数据结构
每一个map任务有一个环形Buffer,map将输出写入到这个Buffer(之所以MapReduce选择环形缓冲区主要是为了能够并发读写)。环形Buffer是内存中的一种首尾相连的数据结构(环形队列是在实际编程极为有用的数据结构,它是一个首尾相连的FIFO的数据结构,采用数组的线性空间,数据组织简单。能很快知道队列是否满为空。能以很快速度的来存取数据。 因为有简单高效的原因,甚至在硬件都实现了环形队列),专门用来存储Key-Value格式的数据,Hadoop中,环形缓冲其实就是一个字节数组,kvbuffer包含数据区和索引区,数据区是存放数据的,索引区是为了你存放进入的数据还能原样的拿出来,保证数据不出错!这两个区是相邻不重叠的区域,用一个分界点来标识。分界点不是永恒不变的,每次Spill之后都会更新一次。初始分界点为0,数据存储方向为向上增长,索引存储方向向下.。缓冲区的大小默认为100M。在这里插入图片描述

环形缓冲区其实是一个数组,数组中存放着keyvalue的序列化数据和keyvalue的元数据信息,key/value的元数据存储的格式是int类型,每个key/value对应一个元数据,元数据由4个int组成,第一个int存放value的起始位置,第二个存放key的起始位置,第三个存放partition,最后一个存放value的长度。key/value序列化的数据和元数据在环形缓冲区中的存储是由equator分隔的,key/value按照索引递增的方向存储,meta则按照索引递减的方向存储,将其数组抽象为一个环形结构之后,以equator为界,key/value顺时针存储,meta逆时针存储。

Spill溢写

Mapper的输出结果很多时, 就可能会撑爆内存, 所以需要在一定条件下将缓冲区中的数据临时写入磁盘, 然后重新利用这块缓冲区. 这个从内存往磁盘写数据的过程被称为Spill, 中文可译为溢写. 这个溢写是由单独线程来完成, 不影响往缓冲区写Mapper结果的线程. 溢写线程启动时不应该阻止Mapper的结果输出, 所以整个缓冲区有个溢写的比例。 这个比例默认是 0.8, 也就是当缓冲区的数据已经达到阈值溢写线程启动,锁定这80MB的内存, 执行溢写过程。Mapper的输出结果还可以往剩下的20MB内存中写, 互不影响(需要注意的是,在写入文件之前,MapTask会首先对数据进行一次排序,如果用户指定了压缩和combiner函数,那么还会对数据进行压缩和合并

规约Combiner

每一个Map都可能会产生大量的本地输出,Combiner的作用就是对Map端的输出先做一次合并,以减少在MapReduce节点之间的数据传输量,以提高网络IO 性能,减少写入磁盘的IO数,是MapReduce的一种优化手段之一。Combiner组件的父类就是ReducerCombinerReducer的区别在于运行的位置,Combiner是在每一个maptask所在的节点运行,而Reducer是接收全局所有Map的输出结果。Combiner的意义就是对每一个maptask的输出进行局部汇总,以减小网络传输量。

Combiner需要严谨使用

因为CombinerMapReduce过程中可能调用也肯能不调用,可能调一次也可能调多次,无法确定和控制所以,Combiner使用的原则是:有或没有都不能影响业务逻辑,使不使用Combiner都不能影响最终Reducer的结果。而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。因为有时使用Combiner不当的话会对统计结果造成错误的结局,还不如不用。比如对所有数求平均数:
Mapper端使用Combiner 3、5、7 ->(3 + 5 + 7) / 3 = 52、6 ->(2 + 6) / 2 = 4
Reducer (5 + 4) / 2= 9 / 2 不等于 (3 + 5 + 7 + 2 + 6) / 5 = 23 / 5

Combiner既然可以汇总,能不能用Combiner取代Reduce函数?
虽然Combiner可以帮我们减少MapperReducer之间的数据传输量,对MapperReducer的数据进行局部汇总,减轻Reducer的工作量和减少网络IO,但是我们仍然需要用Reduce函数来处理不同Map输出的具有相同键的记录。

合并溢写文件

每次溢写会在磁盘上生成一个临时文件 (写之前判断是否有Combiner), 如果Mapper的输出结果真的很大, 有多次这样的溢写发生, 磁盘上相应的就会有多个临时文件存在. 当整个数据处理结束之后开始对磁盘中的临时文件进行Merge合并, 因为最终的文件只有一个, 写入磁盘, 并且为这个文件提供了一个索引文件, 以记录每个reduce对应数据的偏移量

Reduce过程

Reduce大致分为copysortreduce三个阶段,重点在前两个阶段。copy阶段包含一个
eventFetcher来获取已完成的map列表,由Fetcher线程去copy数据,在此过程中会启动两个merge线程,分别将内存中的数据merge到磁盘和将磁盘中的数据进行merge。待数据copy完成之后,开始进行sort,阶段,sort阶段主要是执行finalMerge操作,纯粹的sort阶段,完成之后就是reduce阶段,调用用户定义的reduce函数进行处理

  1. Copy阶段 ,简单地拉取数据。Reduce进程启动一些数据copy线程,通过HTTP方式请求maptask获取属于自己的文件。
  2. Merge阶段 。这里的mergemap端的merge动作,只是数组中存放的是不同mapcopy来的数值。Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活。merge有三种形式:内存到内存;内存到磁盘;磁盘到磁盘。默认情况下第一种形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的文件。
  3. 合并排序 。把分散的数据合并成一个大的数据后,还会再对合并后的数据排序。
  4. 对排序后的键值对调用reduce方法 ,键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对,最后把这些输出的键值对写入到HDFS文件中。

Shuffle过程

shuffle(洗牌) 核心机制:数据分区,排序,分组,规约,合并等过程

shuffle是由map段和reduce段的shuffle共同组成的,一般把从Map产生输出开始到Reduce取得数据作为输入之前的过程称作Shuffle

  1. Collect阶段 :将MapTask的结果输出到默认大小为100M的环形缓冲区,保存的是
    key/valuePartition分区信息等。
  2. Spill阶段 :当内存中的数据量达到一定的阀值的时候,就会将数据写入本地磁盘,
    在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置了combiner,还会将
    有相同分区号和key的数据进行排序。
  3. Merge阶段 :把所有溢出的临时文件进行一次合并操作,以确保一个MapTask最终只
    产生一个中间数据文件。
  4. Copy阶段ReduceTask启动Fetcher线程到已经完成MapTask的节点上复制一份属于
    自己的数据,这些数据默认会保存在内存的缓冲区中,当内存的缓冲区达到一定的阀值
    的时候,就会将数据写到磁盘之上。
  5. Merge阶段 :在ReduceTask远程复制数据的同时,会在后台开启两个线程对内存到本
    地的数据文件进行合并操作。
  6. Sort阶段 :在对数据进行合并的同时,会进行排序操作,由于MapTask阶段已经对数
    据进行了局部的排序,ReduceTask只需保证Copy的数据的最终整体有效性即可。
Reduce 端实现 JOIN

通过将关联的条件作为map输出的key,将两表满足join条件的数据并携带数据所来源的文件信息,发往同一个reduce task,在reduce中进行数据的串联

Map端实现 JOIN

适用于关联表中有小表的情形。使用分布式缓存,可以将小表分发到所有的map节点,这样map节点就可以在本地对自己所读到的大表数据进行join并输出最终结果,可以大大提高join操作的并发度,加快处理速度,先在mapper类中预先定义好小表,进行join

求共同好友

A: B,C,D,F,E,O
B: A,C,E,K
C: A,B,D,E,I
D: A,E,F,L

给出A-O个人中每个人的好友列表,求出哪些人两两之间有共同好友,以及他们的共同好友都有谁。注意: 这些人好友都是单向的,可能A是B的好友,但是B不一定是A的好友,这种类似的微博的关注,A关注B,但是B不一定关注了A

思路分析:

  1. 从上面可以现在我们知道A-O每个人拥有哪些好友,但是我们现在是要找出两两之间的人有哪些共同好友,可以逆向思维,第一步找出哪些好友拥有A,哪些好友拥有B…依次找出
  2. 通过得出上面的数据后,可以对后面的好友进行排序,避免重复,将 “拥有这名朋友的所有人”进行两两配对,并将配对后的字符串当做键,“朋友”当做值输出,即输出<人-人,共同朋友>

自定义InputFormat合并小文件

无论HDFS还是MapReduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案,小文件的优化无非以下几种方式:

  1. 在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS
  2. 在业务处理之前,在HDFS上使用MapReduce程序对小文件进行合并
  3. MapReduce处理时,可采用combineInputFormat提高效率

程序的核心机制:自定义一个InputFormat。改写RecordReader,实现一次读取一个完整文件封装为KV,在输出时使用SequenceFileOutPutFormat输出合并文件

自定义OutputFormat合并小文件

例: 现在有一些订单的评论数据,需求,将订单的好评与差评进行区分开来,将最终的数据分开到不同的文件夹下面去,数据内容参见资料文件夹,其中数据第九个字段表示好评,中评,差评。0:好评1:中评2:差评 关键点是要在一个MapReduce程序中根据数据的不同输出两类结果到不同目录,这类灵活的输出需求可以通过自定义outputformat来实现。实现要点:

  1. MapReduce中访问外部资源
  2. 自定义outputformat,改写其中的recordwriter,改写具体输出数据的方法write()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值