2020.9.13(mapreduce入门及计算模型)

为什么叫MapReduce?

Map:以一条记录为单位做映射~!
Reduce:以一组为单位做计算~!
什么叫做一组?分组~!抽取相同的特征,key
依赖一种数据格式:key:value 键值对
k,v的实现:由map映射实现的

为什么叫MapReduce?

Map:
映射、变换、过滤 1进N出
Reduce:
分解、缩小、归纳 1组进N出
(KEY,VAL):
键值对的键划分数据分组
输入数据集 =Map()=> 中间数据集 =Reduce()=> 最终结果集
后面还有一个计算数据集SPARK,提出了RDD弹性的分布式数据集
MapReduce可以实现分布式计算
在这里插入图片描述
Reduce的计算来自于Map的输出,所有的map计算完,rudece才开始计算,map的输出,左边一共画了3个矩形,代表3个并行度,3个map任务,split切片,一个切片会对应一个map计算

一个切片是什么语义(什么是切片)?

在文件层会做真正的物理切割得到块(block)1 2 3
在计算框架中,默认情况下,切片(split) 等于块,加一层来解耦。有多少个块就有多少个并行,切片(split)是逻辑的并非物理的。
计算分为两大类,一类叫CPU密集型计算,一类是IO密集型计算
CPU密集型计算:比如我的文件当中有若干行,读出其中的第一行,其中切割出了5个参数,这5个参数要进行几千几百次循环迭代计算才能算出一个结果,一行可能会造成CPU计算一小时
IO密集型计算:只是想把数据传输,只做数据的映射,对CPU计算没有要求

为什么要使用切片split?

一个block块大一点对IO密集型友好,但CPU密集型的场景就不友好,未来有两个项目组,一个项目组做数据挖掘需要CPU密集型的计算,另外一个项目组只拿相同的数据做数据的映射和抽取,不可能给出最完美的块的大小的定义。
所以在计算层提出一个新的切片的概念,切片是一种窗口机制,在默认情况下切片等于块,在非默认情况下,切片可能小于块,也可能大于块。切片和块的比例关系有三种,等于,大于,小于。满足不同项目组的不同类型的计算。
每个切片是相等的,只有最后一个切片可能不太等因为切片不满足了,控制并行度,比如有3个块,如果不控制,并行度就是3,因为切片等于块。6个切片,一个块支撑起两个计算,一个切片计算块的前半部分,一个切片计算块的后半部分。有6个并行度。也可以定义只有一个切片,这个切片读取整个文件的所有内容,拉过来计算。很灵活,这一层这就是软件工程学常听说的加一层来解耦

结论:在mapreduce中,左边map的并行度是由什么决定的?是由切片的数量决定的,且一个切片必须对应一个map计算,1:1的关系。如果这个文件默认情况下一个block块对应一个切片,块是物理切割的,块有偏移量。以块的大小,间接的切片可以复用块的偏移量和块的大小,块上还有location位置信息(块在哪3台机器有副本),切片也可以复用副本的信息,当切片拿到了这些信息之后,最终通过切片的这些信息也可以决定我这个map,这个计算程序最终移动到哪一台计算机去,很好的实现了计算向数据移动的语义(即便我一个切片是块的一半,另一个切片是块的另外一半,也可以复用这个块的副本信息,又因为副本有3个位置,所有一个切片对应的map在这一台,另外一个切片对应的map可以去另外一台,两者也不冲突)

问题,在hdfs的时候说到,一个块会把数据切割开。默认切片等于块,那么切片读到的数据是不是也是切割开的?这时候等于一个切片拿了he一个拿了llo
的确,如果真正这么直白的去读的话,的确会拿到切割的东西,但是在计算层框架去读取的时候,会规避这个问题,会把llo恢复到he的后面,最终得到的还是hello这个单词,恢复不需要人去操作,是由计算框架解决的。

一个切片是抽象的,描述了这个map未来启动之后,切片告诉了map只能从这个文件的这个位置开始读,读到另外一个位置, 这是他可以计算的范围。另外一个切片可以从另外一个位置开始读,读到另外一个位置结束
切片指定了文件后来被处理的范围,注意:这个范围是若干的字节,就是一个字节数组,最终计算的时候,我们说了,map是以一条记录为单位,(一条记录为单位,调用一次map方法),记录record是怎么来的呢?在split既有范围,还有格式化format,要从这一大堆范围的字节数组当中,切割/格式化出一条一条的记录,这一条一条的记录有可能是什么,比如说,在格式化的时候,以换行符切割,那就是以换行符,拿到一行调用一次,如果给出了复杂的格式化,比如说以开闭标签,(一个开标签一个闭标签为一个单位),拿出若干行作为一条记录传给map。
最终还是还原到以一条记录为单位,但是每一条记录长什么样子,未来还有一个东西叫做,输入格式化类。能格式化的就来自于切片给的范围当中去拿着这些数组去切,简单的split切割的过程,切片最终代表的是范围,里面会拿出一条记录,调用一次map

map输出是给reduce计算做准备的
map最终要做一个映射,映射成KV键值对, 前面map有并行度是由切片的数量决定的,后面reduce有两个框,也有并行度的概念,这个并行度是什么意思,为什么可以并行呢?
回到讲reduce分组的时候,前面的数据集,被一些map处理之后,
映射的key,key的种类可能不止一个,男/女,不同的专业,key的数量会有很多 。如果后面只有一个reduce,(先不想并行度的事情),可以把所有的map数据都拉去过来,先把男的数据计算一遍,再线性的把女的数据计算一遍,根据reduce的定于,在reduce当中计算其中一组的时候,不需要另外一组,在计算男的时候,不需要知道女的记录。这时候完全可以启动两个reduce一个去map中拉取性别为男的数据,另一个reduce去拉取性别为女的数据,这两个reduce可以并行计算,输出结果
为什么要追求并行?因为在一个进程里计算的时候,是线性的一个一个来,时间线是线性的时间线,比如计算性别男1小时,计算性别女1小时,但是两者的计算没有依赖关系,这时候如果男拉一个节点,女拉一个节点同时计算,最终计算的时间为1小时。比2小时少了一般。这只是在key为2个的情况下,如果前面的key有很多,(10种、100种、1000种),数据量很大(1T 2T 10T),并行量足够大,相对来说,速度最快
怎么知道哪个reduce计算男,哪个reduce计算女?
性别为男女hashcode不一样,如果有2个reduce可以mod2,假设男会mod为0,女会mod为1,数据就会被不同的reduce拉走了。这就是一种hash表分治的过程(使用hashmap的的时候也是把KV往里去put,put的时候也是关心key)

当map输出的时候,映射的是KV,这时候key在每个map当中已经按堆分好组了,(男的一堆,女的一堆)(男的一堆,女的一堆)
**在MapReduce计算中,组是一个最小力度不可以被拆开不可破坏不可分的。**如果男的数据分别被拉到两个reduce当中,那么计算是不准的。
前面说了,map的并行度是由切片的数量决定的,那么reduce的并行度由什么决定呢?
reduce的并行度由人来决定!!做的是大数据计算,前的的数据集可能有10亿组,可能每两条记录就是一组,因为只有这两条记录他们的key相同。每组的容量很小,如果按照有多少组就有多少个reduce,那就是JVM里有10亿个reduce进程(都是JVM进程)。每个进程启动之后又只计算两条,性能浪费。10亿组只需要10个reduce每个里面各2亿条记录,那么10台机器并行计算速度也不低。因为不可能有10亿台机器做并行度。reduce的并行度是由开发人员来决定的!!开发人员知道只有10台或者8台机器,理论上起10个或8个reduce就够了,这是最大的利用率(无非就是未来一个reduce不是处理其中一组而是线性的处理其中几组)。框架当中默认reduce的数量是一个。框架中默认切片的并行度是block块的数量,在生产环境种,map的切片都很少去调,默认一个切片对应一个块,但是必须要调整的是reduce的数量。因为一个太低了没有达到并行。
如何分组?按着需求进行计算,比如要统计性别就不可能按照地址去划分组,可能北京里既有男有女,算出数是不准的。 分组也很浅显,就是sql中的group by后面key值是什么
在这里插入图片描述
在这张图种需要知道哪些词汇:
记录、分组、KV、reduceTask、分区

文件有block块,默认一个block块对应一个切片,切片里会有记录,记录会调用map,map输出之后会调用reduce,reduce最终会输出,map图中的虚框叫做mapTask(map任务),reduce图中的虚框叫做reduceTask(reduce任务),蓝色的map叫做map方法,以一条记录调用一次方法,蓝色的reduce叫做reduce方法,以一组记录调用一次方法。它们都是线性的。
一个reduceTask中可以包含一组数据或多组数据,根据reduce的数量来决定的,将reduceTask可以包含若干组数据换成新的名词,叫做分区partition。map输出的是一种键值对KV的,相同的key有一个词汇叫做分组group
总结比例关系
在这里插入图片描述总结第一张图:
mapreduce从中间垂直一切,有左边的一个步骤叫做map步骤,右边一个步骤叫做reduce步骤。这两个步骤是线性的发展顺序关系,前面map计算完之后才能计算reduce环节。各自两边都会有并行度,左边map的并行度是由计算层解耦层切片的数量,且切片是一种可调的窗口机制,切片对应map是1:1的关系,且切片中格式化出一条条记录,以一条记录为单位调用一次map,即map方法是以记录为单位调用的。然后map输出的KV要完成分组的事情,所有的map计算完之后,reduce会根据相同的key为一组,一组数据调用一次reduce方法,这一组数据不能被破坏,每个reduce去拉取属于自己分区的数据,reduceTask是一个分区,分区里可以有若干分组,拉过来计算。reduce的并行度是由人来决定的,这个时候人要参考很多东西,key有很多种,机器有很多台,这时候最大的最合适的reduce数量,就是在合适的机器里放合适的进程数,而不要把进程数无限放大。千万记住,整个过程中,相同的一组数据不要被切割开。

简单总结
MR:通过map数据以一条记录为单位,经过map方法映射成KV,相同的key为一组,这一组数据调用一次reduce方法,在方法内,迭代计算这一组数据。(map是以一条数据为单位的,reduce是以组为单位的!)
作业:迭代器模式!!复习
reduce拿到一组数据可能很大,1T 2T 3T,把这一组数据传递给一个reduce方法,已经造成了内存的溢出。但是大数据计算框架无论是mapreduce还是spark都可以完成计算的过程,就是使用了迭代器模式。给reduce传递的不是这一组数据,而是这一组数据的迭代器。迭代器就是一个对象,迭代器迭代的时候数据在哪,不在内存而在磁盘。等于对着文件打开一个IO包装成一个迭代器,迭代器hasnext有没有,next取一条,就从文件当中readln读出第一条,迭代器再去一条,从文件当中拿出第二条。简单来说就是用了迭代器这种模式来规避了大数据有可能内存溢出的风险。如果没有迭代器模式,大数据根本算不出来,这一组数据很难在一个方法里执行完,因为传都传不进去。 数据只需要线性读一次,就可以算出所有的结果,要衡量数据读取的IO成本是快还是慢。
经验:数据集(List,Array或存放若干条记录的一个东西)一般是用迭代计算的方式(拿到一个迭代器一条条计算),通过迭代器的模式规避了大数据有可能内存溢出的风险

在这里插入图片描述
描述了在一个MapTask里发生了哪些细节,已经一个reduceTask里发生了哪些细节:(注意因果关系、常识知识)
把上面的图拿出一个map,拿出其中的一个reduce
从宏观上来说,还是map执行完之后才能执行reduce,这是基本常识,在一个mapTask里面包含了一个切片,这个切片会对应它的一个map方法,在mapTask中,切片叫做input split
第一个知识点:
1.切片会格式化出记录,以记录为单位调用map方法
第二个知识点:
2.map会收到一条记录,map的输出要映射成KV,这张图少画了一个环节,当map输出KV之后,kv会参与一次计算,这个计算叫做分区计算,拿着key算出P(分区号),最终输出的是K,V,P(因为每条
记录要算出它最终要到哪个分区里去)
提问:相同的key,它的P一样么?一定是一样的,相同的数据为一组,这个组的分区号一定是一个分区,它不能去到别的分区,不能打散。
总的语义:一个mapTask当中,map处理完切片里的数据之后,会得到输出的一个中间的数据集的状态,这一个map里面的中间的数据是要放在本机的本地的文件目录当中的,并没有输出到hdfs,就拿本机的一个目录来存了,而且要最终存成一个文件。
简单语义:
mapTask的输出是一个文件,存在本地的文件系统中
如果一个mapTask计算了5000条,调起了5000次,那么输出可能也输出了5000条,这5000条要存成一个文件,推算两个成本:map输出一条可以存到文件里去,再输出一条可以存到文件里去,继续向这个文件写,最容易想到的方式,但是这种方式在编程的基本常识里,会有一个问题,JVM里每拿到一条数据就向磁盘写,慢是因为IO会触发调用内核,CPU本来是计算JVM里的逻辑代码的,if判断,变量赋值…但是如果调用IO的话,字符串需要从进程写入磁盘,CPU不会读取代码了,会做用户态到内核态的切换,会切换到内核,操作系统Kernel级别,kernel里面才是真正IO的api调用的native实际的方法。在用Java语言写IO的时候只是调用了api,但是api也不是我们写的,api做了什么事情也不是JVM来写的,JVM调用了统一的内核,任何的进程如果想访问硬件的话,就调一个内核,所有CPU会有一次切换,从用户态切到内核态,这时候由内核读取数据,放到它的缓冲区,然后满了再刷到磁盘去,如果每产生一条数据就去写文件就会很累。
(小故事:住在10层,楼下有小卖部,免费送24瓶啤酒喝,喝啤酒有几种喝法,第一种,从10楼跑到楼下,拿一瓶啤酒,回到家,喝掉它,成本上下往复24次,类比24次系统状态的切换系统调用。第二种,从10楼跑下楼,拿一个箱子装了24瓶啤酒,搬上楼,坐在家里慢慢喝,往复1次,不能在店里喝,因为说的是进程和磁盘,不是一个位置。拿箱子装啤酒就是常说的要使用buffered带缓冲区的IO)
再来看细节:
map输出之后,在内存中动用了一个buffer in memory,但是注意,从map输出的数据已经变成KVP了,这样的数据一条一条的像buffer缓冲区去放,缓冲区默认大小是100M,可以根据数据特征硬件内存大小调整,因为buffer也是在内存的堆里的。
假设buffer满了,可以拿着这个buffer(啤酒箱子)做一次系统IO的调用,把它一股脑写到磁盘去,这个线性操作的过程,只会调用一次系统调用(用户态到内核态的切换)。
可以直接把它写到磁盘,溢写很多次得到很多的小文件,不考虑分区的事情,当map不输出了会有一堆小文件,这些小文件最终可以拼成一个文件,先不想排序的事情,不同的KVP在里面混乱的摆放的。如果只有一个buffer,只有简单的溢写,简单的拼接,这个大文件一定是乱序的 。
当所有map都这样去计算的话 ,所有map都是乱序的。所有map都计算完之后,reduce要拉取了,假设0号分区第一个reduce。这个reduce要去所有map当中拉回属于这个文件当中表示为0号分区(P为0)的记录,如果没有排序,reduce在拉取的时候要估算IO成本了,打开这个文件从第一行遍历到最后一行,把属于0号分区的记录抽出来,一起拉走。因为这个文件是乱序的,所有必须要全局遍历。有几个reduce这个文件就要全量IO访问reduce数量次。
把IO瓶颈解决,就是从程序员到大牛的转变。
如果数据预先排序,数据有序就好做了。
map方法在内存里,buffer在内存里,还没有IO,寻址来说内存是磁盘速度10W倍,如果在产生IO之前,数据都在内存,可以优先在内存动用IO之前做一次快速排序,最简单的可以分区有序。
当这样处理之后,这些小文件有什么特征?
文件内部有序,外部无序。这个时候可以动用归并排序算法,只会对这些文件产生一次IO可以快速得到一个按照分区排好序的文件。
这个时候再来算IO成本,0号reduce只需要打开这个文件,根据0号分区的范围把数据取走,不需要遍历全部IO,前后不需要去读了,IO成本由N变成了1。O(n)变成O(1)了
当reduce拉回来只是分区有序,而分区内无序的数据(只对P做了排序,没有对K做处理),假设这个reduce拉回了两组数据,在结果当中也是乱序的。定义是以一组为单位调用一次reduce方法,处理完之后再处理第二组,1个T的数据假设有两组,每组500G,因为是乱序的,这时候IO成本是2T,在文件大小不变,但是分组变多了,有1000组数据,乱序,时间复杂度1000次1TIO的时间复杂度O(n),n就是有多少组
第三个知识点:
内存缓冲区溢写磁盘时,做一个2次排序,分区有序,且分区内key有序,未来相同的一组key会相邻的排在一起。会让reduce取去数据,拉取后按照组计算的成本指数级下降,reduce拉过去的小文件又具有了内部有序外部无序,归并成一个文件,全排序O(1)的IO复杂度
在这里插入图片描述从图中可以看到,在reduce计算的时候,不像之前描述的,reduce将这些文件拉取回来之后,必须要合并成一个文件,它是合并成两个文件就发起计算了。这是因为什么?
还是复杂度的问题,如果去考虑复杂度,很多map跑完了,其中一些map可以走一次归并,如果把归并完的结果又是内部有序外部无序,如果再走一次全IO把它们合并成一共文件,合并成一个文件的过程就是走了归并排序算法。合并成一个文件这件事可以砍掉(因为有迭代器模式的支持),完全套一个归并排序算法在两个内部有序外部无序的文件,直接得到有序输出。只不过这个输出可以直接写文件,也可以向reduce传入了。
第四个知识点:
reduce的归并排序其实可以和reduce方法的计算同时发生,会尽量减少IO
因为有迭代器模式的支持~!!!!!
(迭代器模式真的看懂了,mapreduce很好理解,后面学spark的时候顺风顺水,秒懂所有概念!!迭代器模式是批量计算中非常优秀的处理数据的方式,在后面分析源码的时候可以窥知一二)

查找重复行

在这里插入图片描述

一个文件切成两个块,一个块里有abc def ghi,另外一个块里有jkl mno abc,这个文件重复的行是abc分别在两个块里。这个时候把mapreduce拿过来如何解决查找重复行的需求?
map环节:
经过map之后会得到一个中间集(以行自身为key,value可以为null,再根据mapreduce的逻辑语义,在这个过程当中,下次如果有reduce):做KV的映射,以行为key,value值无所谓,可以是一个null,第一个:abc,null,1 def,null,0 ghi,null,3
第二个:jkl,null,2 mno,null,1 abc,null,1
reduce环节:
假设有4个分区。reduce相同的key为一组,做迭代计算,迭代一条自增1,abc统计值为2,其他的统计值都为1。如果reduce以迭代一组统计值大于1就告诉这个组找到了,最终只有1号分区输出了一个文件找到了重复的行abc

wordcount

在这里插入图片描述

一个块有hello world hello hadoop
另一个块是 hello world hello hadoop
什么是wordcount面向文件为单位,算一算这个文件里hello一共出现了几次,word出现了几次,hadoop出现了几次,怎么做这个事情?
每个块都会启动一个自己的map程序,map是并行计算的,map要做一个怎么样的kv映射,mapreduce就是在做分组统计计算。只要以单词划分组就可以了,所以key可以是单词自己,map是1进N出,第一条记录调用map输出两条hello,1 world,1 。value可以是null也可以是1,关键看reduce怎么去计算,下面的也是这个样子,除了有单词最终map算出的东西要做kvp,相同的key分区号一样的
reduce环节:
假设reduce有三个,每个reduce去把属于自己分区的数据拉过来,最终统计的结果和我们自己算出来的是一样的。
变更需求,不是简单的wordcount,是要在wordcount的基础之上,做更加深入的计算,统计相同词频的个数,最终不是要每个单词出现多少次,而是查出现两次的有几个单词,出现四次的有几个单词…
在这里插入图片描述

做一次mapreduce能实现需求么?
不能。但是可以拿着最终输出的结果集,把输入集替换成曾经的输出集,再走一次mapreduce
,这时候有3个map,以值为key做一次翻转。映射成4,1,2 2,1,1 2,1,1 假设还是3个reduce,0号分区拉不到数据为空,1号分区会把2,1,1拉过来,拉回了两条。2号分区会把4,1,2拉过来。最终的输出就是期望的结果,词频为2的出现了2次 。这两个mapreduce也是线性阻塞的关系,先跑第一个mapreduce,得到结果集再跑第二个mapreduce

猜你喜欢

转载自blog.csdn.net/m0_48758256/article/details/108446378