Hadoop-mapreduce入门

一、简介

1、为什么叫MapReduce?

MapReduce由Map和Reduce组合而成
1、Map的含义

假设有一张表维护了用户名(NAME)、职业(MAJOR)、性别(GENDER)、住址(ADDRESS)
在这里插入图片描述
有三个需求:

  • 需求一:从这个表里的数据集中,过滤出性别为0的数据,也就是获取GENDER = 1 的数据
    这个需求的常规操作就是,搜索这个表里的记录,抽取出记录对应的GENDER字段。判断这条记录的gender字段值是否为0,如果为0则丢弃这条记录,并继续向下遍历剩余记录,重复这样判断筛选的过程。
  • 需求二:将表中的GENDER列的0、1由数字转成为字典值,0–>M,1–>W
    同需求一,需要逐条遍历表的数据,并抽取GENDER字段,并判断转换更新成对应的字典值
  • 需求三:将表中的ADDRESS字段的内容,展开字段复合值。
    这个需求的含义是将ADDRESS字段的值以逗号切割,并插入对应条数的记录。同理,也是需要逐条遍历,并按逗号切割ADDRESS的值,然后再分别插入到表中。

以上三个需求,都有一个共同点,就是在遍历操作当前数据的时候,不需要关注除此之外的任何一条表中的数据。

比如上面举的例子,你需要在原表中抽取一条记录,按照需求做转换操作,将得到的结果保存。也就是一个根据条件映射的过程。这也就是MapReduce中的Map该做的事情。

Map的核心功能:主要是以一条记录为单位做映射

注意只能是一条记录,Map不接受多条记录作为输入。是一条记录一条记录来操作的。

2、Reduce的含义

举个例子,有一张表维护了学生及其所选的专业课,现在想要统计每个专业有多少人选
在这里插入图片描述
这个需求我们如果采用Map的思想,一条一条记录的去计数,就会出现下面的结果:
在这里插入图片描述
当然,这里如果要计算每个专业课所选的人数,就要按照Key进行分组,相同Key的在一组,于是就有下面的结果:
在这里插入图片描述
然后按组计算总和就能得到对应专业的选课人数。

上面的分组计算总和的步骤,就是Reduce所作的事情

Reduce的核心功能:以一组为单位做计算

这里有个前置条件,我们必须要将数据先分组,也就是抽取数据的相同特征。这个抽取数据相同特征的过程,依赖一种数据格式,也就是键值对格式:KEY - VALUE, 而KEY-Value的实现则是由Map映射实现的!

所以MapReduce主要是使用Map+Reduce完成复杂的映射组合计算。当然单单一个MapReduce可能不能够完成很复杂的计算,可以采用多个MapReduce一同完成。

3、总结

MapReduce的处理流程:
在这里插入图片描述

Map的功能主要做映射、变换、过滤。实现1进N出的结果(一条数据映射出N条结果)

Reduce的功能主要做分解、缩小、归纳。 实现一组进N出的结果(一组数据作为输入,输出N条数据结果)

Map和Reduce是根据键值对(Key Value)衔接在一起的。键值对的建是划分数据分组的重要依据
Reduce的计算来源于Map计算的输出!

二、分布式环境下的MapReduce计算

在这里插入图片描述
1、split切片的概念

这张图的split相当于一个切片,是逻辑上的概念。切片的数据来源于hdfs文件系统的输入。在hdfs中,我们会将一个大文件切割成不同的小文件块存放,但这个过程是真实的切割划分。实际上这些文件块就可以直接代替split切片,但为什么多一个split切片的概念呢?主要目的用于解耦

在理解为什么需要多出一个split切片的概念之前,首先需要理解下面的计算类型:

在计算类型通常分为CPU密集计算类型和IO密集计算类型。这两者的区别主要看你处理一条数据花费在cpu上的时间多还是利用IO在读取文件的时间多。

  • CPU密集型计算
    假设有个文件,你就读取一行数据,但是这一行数据你还要做切割转换等复杂操作,占用cpu大量时间,这就是CPU密集型计算。

  • IO密集型计算
    假设有个文件,要求读取所有的数据,只是做个简单的判断即可,占用CPU时间很少,反而占用IO时间很多,这就是IO密集型计算。

理解上面的概念之后,切片的概念就已经非常明了了。

当你上传一个文件到HDFS中,你需要定义划分该文件的块大小是多少呢?

当你把这个块定义的很大的时候,又需要cpu为这个块里的每行数据做计算的时候,就可能需要占用CPU很长很长的时间,而IO的占时并没受太大的影响(读取一次文件的内容即可)。也就是说,文件块定义的偏大/偏小,对CPU密集型和IO密集型计算都会存在一定的影响。

故此,如果直接以文件块抛给Map做映射,就非常的不灵活。你想下如果有两个项目组,一个要用这个文件块做数据挖掘,需要CPU密集型计算,而另一个则需要对文件读取操作。这时候定义文件块大小就非常难以满足两者。

所以这时候就在计算层就引出了split切片的概念,当你的文件块定义死的时候,默认情况下,切片的大小就等于文件块大小。除此之外切片的大小还可以大于、小于文件块大小。这样就可以让不同计算类型的项目组自定义切片大小来完成计算。

一句话概括切片的用处:控制并行度,并行度是由split切片的数目定义的,且一个split切片对应一个map

split指明了map启动后可以读取的文件中的数据范围同时也满足了计算向数据移动,因为split可以复用块的元数据信息,获取块所在的副本位置,挑选最近的访问

2、map输出结果到reduce的处理

上图中,split切片把一行一行的数据往map中送,map计算出来的结果提供给reduce进行计算。那么这里给出map数量和reduce数量的一个映射关系:

  • Map:Reduce = N:1

这是MapReduce计算框架的默认设置,说明Map数量可能存在多个,而Reduce默认值为1个。当然这样不利于并行计算。就拿上面的计算男女数量的例子,如果只有1个reduce,在做统计男女的时候,不管是男还是女,都交给这台reduce计算,在计算的过程中,计算性别男的数据时,不关注性别为女的数据。如果数据量很大,全丢给一个reduce做处理,就比较耗费时间。

  • Map:Reduce = N:N

Map数量和Reduce数量的关系可以是多对多的,还是拿统计男女数量为例,多个Map并发的将文件中的数据按性别整理好之后,可以使用两台Reduce,一台Reduce读取性别男的数据,另一台读取性别为女的数据。两台Reduce并行做统计,这样就相对于1台Reduce的效率提高一半!

换一个例子,如果一个文件非常大,通过Map进行映射之后,得到的KEY-VALUE分组有10亿组,此时就不能够要求有10亿个reduce进程分别拉取这10亿组做计算了。可以选择10个reduce进行,每个reduce进程拉取多个分组,并同时计算,即可提升计算的效率。实际上当一个reduce拉取多个分组的时候,就可以把它称作为分区,一个reduce的分区可以容纳多个分组

ReduceTask称为Reduce分区

这里需要注意下,当Reduce数量从1个变成多个的时候,拉取的分组是不可以被拆分的,不能一台Reduce拉取A分组10条数据统计,另一台拉取A分组20条做统计。

Reduce的并行度是由人来自定义的。

  • 1:1

Map数量和Reduce数量关系可以是1对1的

  • 1:N
    Map数量和Reduce数量关系可以是1对多的
    同样的Map中的多个组和Reduce中的分区对应的关系也和map和Reduce数量对应关系。

一句话概括下MapReduce框架:数据以一条记录为单位,经过Map方法映射成KeyValue键值对,相同的Key为一组(Map映射完之后得到的Key-Value中的Key为分组依据),这一组数据调用一次Reduce方法,在方法内迭代计算着这一组数据。

数据集一般是用迭代计算的方式,为什么呢?因为当传递给Reduce的分组非常大的时候,假设1个TB的组数据量,那全抛给Reduce就可能会出现Reduce没计算就内存溢出了,显然是不合理的。所以一般而言,抛给Reduce的是这1TB数据的迭代器,Reduce慢慢的迭代数据集进行计算。迭代的过程中,未迭代到的数据是存放在磁盘,而不是在内存中的。相当于迭代器提供个IO,迭代一条就从IO读取磁盘一条数据到内存

3、mapTask和ReduceTask中的计算详细过程

在这里插入图片描述

上图,一个MapTask里包括了split切片,以及map方法。

1、切片会格式化出记录,以记录为单位调用map方法。

2、map的输出映射成KeyValue键值对,而KV将会参与分区的计算,计算的原理是拿到Key计算出P,也就是分区号(通过对Key计算hash然后取模reduce的数量得到P),最终得到K,V,P。通过P可得到每条KeyValue最终需要去到哪个Reduce分区里去。相同的Key算出的P一定一样,相同的组必定去到同一个分区里。

3、mapTask输出是一个文件,输出在本地的文件系统中,而不是hdfs上

那么这个过程如何时通过什么方法将map输出的数据写到磁盘上呢?

方法一、map每输出一条就往磁盘写一条数据

最直接能想要的是,map输出一条数据就往磁盘中指定的目录下写数据。这就会导致一个问题,当map映射好每一行数据时,就要向磁盘中的文件写数据,这就会出现CPU从用户态到内核态之间的转换,通过调用内核提供统一的读写数据接口,完成写操作。这个方法速度会变得非常慢,因为要做非常多次IO,CPU频繁的从用户态到内核态的切换操作,然后再由内核读取map输出的数据到内核的缓冲区,在刷到磁盘的这个操作,耗费大量资源和时间。

方法二、使用带缓冲区的IO

在map输出一条数据的时候,先不往磁盘中写数据,而是使用一个buffer缓冲区去缓存这条数据(buffer空间默认100MB可以调,使用堆内存),然后当缓冲区满的时候,再将buffer的数据调用一次IO溢写到磁盘中,生成一个小文件

当你map最终处理完所有split传递给他的记录时,就会生成N个小文件,这些小文件最终将会合并成一个大文件。但是如果buffer只是简简单单的溢写生成每个小文件,那么这些小文件势必是没有规则的,也就是说小文件内存的每一条map输出的K,V,P是杂乱无章的,合并成大文件之后也是混乱的。

这就会导致一个问题,当所有的map都是这样处理的话,最终生成的文件也是乱序的,这时Reduce将要按照文件中的每条记录的分区号拉取对应分区的键值对。此时就要考虑到IO打开读取的复杂度。

假设第一个Reduce拉取的是所有的map生成的文件中分区号为0的记录,因为所有map生成的文件记录都是没有预先按分区号排好序的,所以Reduce拉取这个文件的时候,就要遍历文件的所有记录。

当有N个Reduce去拉取不同分区号的键值对记录时,就要遍历N次文件。效率十分低。

初步解决

为了解决这个问题,我们要求在map输出记录到buffer缓冲区后,当buffer缓冲区满了,先不急着溢写到磁盘,而是按照记录分区号(P)做排序,然后再将结果溢写到文件中这些文件的特征是内部有序,外部无序的! 此时就可以使用归并排序算法,只需要一次IO即可将所有的小文件按照分区号排好序,归并为一个大文件

此时Reduce来拉取map生成的按分区号排好序的大文件记录,就不必要遍历整个文件了,而是直接拉取对应的分区号的记录即可。

但是这里又存在一个问题,Reduce拉取完这些记录之后,实际上也是乱序的,因为并没有将这些记录按照Key进行分组排序,还是得遍历分区所有的记录获取相同的分组记录

Reduce是以一组key-value为单位,调用Reduce方法的

举个例子,假设Reduce拉取分区号为0的数据,总共文件大小是1TB,其中这1TB只有两组(按Key划分组)。那么Reduce想要获取第一组的时候,就要打开这1TB的文件,遍历文件所有数据获取第一组的记录(文件内部只是按分区号排好序,并没有对Key排序,所以每条记录的所属组在文件中是乱序的),那么获取第二组数据时,又要打开这1TB文件,再遍历所有记录获取第二组的数据。这期间的消耗的时间复杂度就是2次1TB io的时间复杂度。

那么问题就来了,当你这1TB的文件里,分组变得非常多,假设有1w组,那Reduce处理完所有分组,就要打开遍历这1TB文件1w次。

进一步解决

我们还是回到buffer内存缓冲区的处理上,内存缓冲区溢写磁盘的时候,做一次2次排序,保证分区有序,且分区内的Key有序也就是说未来相同的一组key会相邻的排在一起

此时Reduce取拉取同一个分区的所有分组,就是分组有序的

Reduce的归并排序其实可以和reduce方法的计算同时发生,尽量减少IO。就是因为又迭代器模式的支持。

在这里插入图片描述
4、举例描述mapReduce工作流程
给出个需求,现在客户端上传了一个大文件,客户要求查找出文件里面重复的数据,此时客户端将这个大文件上传到hdfs集群中。利用mapReduce框架处理查找出这个大文件的重复数据,流程图如下:
在这里插入图片描述
首先通过定义好split切片的大小,并做split格式化操作,得到多条记录,split将一条条记录发给map做一个分组处理,对记录数据计算hash值模上reduce数量得到分区号,从而map输出K,V,P键值对存放到buffer内存缓冲区中,当buffer缓冲区满时,将这部分键值对记录溢写到磁盘文件,生成一个内部有序外部无序的小文件。最终将该map生成的所有小文件归并排序,生成一个有序的大文件。大文件中都是按照分区号进行排好序的。

其次Reduce开始从所有map中拉取对应分区号的文件,并将拉取后的多个小文件,做一次归并,此时归并的过程中就可以同时进行reduce方法计算(一组一组传入数据即可),然后通过reduce方法统计出文件中Key为ABC的总数为2,大于1,表示重复数据。

至此就计算完成了,将重复的数据汇报给客户端即可。

5、word Count案例

5.1wordCount 计算文件中重复出现的单词次数

wordCount的含义就是统计每个单词出现的总数。假设有一个客户上传了一份文件到hdfs中,要求使用mapReduce框架计算统计文件中的单词出现的总数。

先看下面的整个流程图:
在这里插入图片描述

首先split切片会去获取指定的文件数据,并格式化成为一条条记录,假设split的内容都是:

    Hello World
    Hello Hadoop

此时交给map进行分组,而采取的分组策略是将一行记录按单词拆分成多行,然后再根据拆分后每行的单词计算hash值并取模reduce数量,得到分区号P,其中Value的值可以随意,可以设置成1。

当map分组完后,reduce就会拉取所有map中对应分区号的记录,比如reduce分区号为0的,就会拉取(Hello,1,0)这样的键值对。拉取完毕后,得到的文件则是按照K排好序的记录,将相同分组的记录传入reduce中计算,reduce根据一组内出现的记录数,算除总数即为这个单词在文件中出现的总数。

5.2 统计相同词频的单词个数

词频的含义就是单词出现的次数,词频个数则是不同单词出现次数的统计值。也就是假设Hello在5.1案例中统计出现4次,World出现2次,Hadoop出现2次。那么文件中出现2次的单词总共有2个(Word,Hadoop),出现1次的只有4个(Hello)。

要计算出上面的结果,还是以5.1中reduce计算完成之后的结果,作为下一个mapReduce的map输入值,如下图:
在这里插入图片描述

同时map的处理逻辑需要修改下,此时map的分组不再是以单词作为分组Key,而是以上一个mapReduce中计算出的该单词对应的出现总数作为key,也就是说输入的(Hello,4)给map,map以4作为Key而不是Hello(相当于一个反转)。而Value值依旧可以随意定义,分区号的计算则同5.1处理。

map按单词总数进行分组之后,reduce就可以拉取map生成的文件了,其中reduce分区号为1的就会去拉取(2,1,1)这条记录,再将拉取到的结果做归并,以组为单位调用reduce方法,reduce则以单词出现总数作为key,统计相同词频的个数(一组内的都是相同词频),最终计算得到(2,2)。其余的组计算流程类似。

这里需要提到一点,5.2中的输入是依赖5.1mapReduce的输出,也就是说,由用户一整个文件,到最终统计相同词频的个数这个需求,这期间经历了两次mapReduce的计算,这个处理流程也是线性阻塞的,第二个mapReduce的计算必须要等待第一个mapReduce计算处理完毕后,方可计算

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值