6.2 MapReduce的计算流程⭐️
面试题常见
问题:计算1T数据中每个单词出现的次数–> wordcount
6.2.1原始数据File
1T数据被切分成块存放在HDFS上,每一个块有128M大小
6.2.2 数据库Block
- HDFS上数据存储的一个单元,同一个文件中块的大小都是相同的
- Block块存在的问题
- 因为数据存储到HDFS上不可变,所以有可能块的数量和集群的计算能力不匹配
- 所以我们需要一个动态调整本次参与计算节点数量的一个单位
- 同时,我们也可以动态的改变这个参与节点的单位(split)
6.2.3 切片Split
-
定义理解:
- 切片是一个逻辑概念,指的是文件中数据偏移量范围,并不是物理上的切分
- 在不改变现在数据存储的情况下,可以控制参与计算的节点数目
- 对于HDFS中存储的一个文件,要进行Map处理前,需要将它切分成多个块,才能分配给不同的MapTask去执行。
- 在执行MapReduce之前,原始数据会被切分成若干个split,每个split作为一个MapTask的输入,在Map执行过程中split会被分解成一个个记录(key-value的键值对,其中key是偏移量,不是行数),Map会依次处理每一个记录,
- split记录了要处理的数据的位置和长度
-
作用:
- 通过切片大小可以达到控制计算节点数量的目的
- 有多少个切片就会执行多少个Map任务(一对一)
- split划分可以让MapTask更好地获取数据输入
-
切片产生的原因:
-
切片的大小
- 一般切片的大小为Block块的整数倍(2倍或者1/2倍)
- 防止多余创建和很多的数据连接
- 默认情况下,Split切片的大小等于Block的大小,默认128M
- 如果Split大小>Block大小,则相对于Split=Block来说,计算的Map节点少了
- 如果Split大小<Block大小,则相对于Split=Block来说,计算的Map节点多了
- 一个切片对应一个MapTask
- 一般切片的大小为Block块的整数倍(2倍或者1/2倍)
-
切片中1.1倍的理解
每次切片的时候,都要判断前一个切片切完之后剩余的部分是否大于块(默认切片的块大小相同)的1.1倍,如果不大于块的1.1倍,就把剩余的部分切分到一个块中,避免很小的一部分也要切片,提高效率
举例:
-
MapReduce如何处理跨行的Block和split?
- 问题解析:Map读取数据是按照一行一行来读取数据的,当一行数据被切分成了两个split时Map如何读取数据?
- 通俗解答:如果Map在读取数据的时候发现有一行数据被切分成了两个split,在读取数据时发现有一行没有读完(没有遇到换行符\n),就会去下一个split中读取这一行接着读取没有读取完的数据,直到遇到换行符\n,这一行才读取完毕。第二个Map读取数据的时候就会查看上一个Map的最后一行是不是有换行符,如果没有,则说明第二个split的第一行内容已经被上一个Map读取过了,这时第二个Map会从已经读取过的这行的下一行开始读取
6.2.4 MapTask
- 定义理解:
- 数据拆分计算
- Map默认从所属切片读取数据,每次读取一行(默认读取器)到内存中,我们可以对这一行数据进行拆分操作
6.2.5 环形数据缓冲区
-
定义理解:
MapOutputBuffer
内部使用了一个缓冲区暂时存储用户输出数据,当缓冲区使用率达到一定阈值后,再将缓冲区中的数据写到磁盘上。
-
作用:
- 可以利用这块内存区域,减少数据溢写时Map的停止时间
- 数据可以循环写到硬盘,不用担心OOM的问题
- 在有效的空间内,能够更加高效的在内存中执行操作
-
大小设置:
- 在内存中构建一个环形数据缓冲区(kvBuffer),默认大小为100M
- 设置缓存区的阈值为80%,当缓冲区的数据达到80M时开始向外溢写到硬盘,溢写的时候还有20M的空间可以被使用,效率并不会被减缓
6.2.6 分区Partition
-
定义理解:
-
作用:
- 根据Key直接算出这个数据所对应的Reduce
- 方便以后数据的拉取,应该将相同分区的数据放到一起
-
特点
- 分区的数量和Reduce的数量是相等的,一个分区对应一个Reduce
- 如果我们的MapReduce操作只有一个Reduce操作,Partation就只有一个,如果我们有多个Reduce操作,就会有多个Partition。Partition因此就是Reduce的输入分片,这个程序员可以编程控制,主要是根据实际key和value的值,根据实际业务类型或者为了更好的reduce负载均衡要求进行,这是提高reduce效率的一个关键所在
- 默认分区的算法是Hash,然后取余
- hash(key) % Partition= num
- 如果两个对象equals,那么两个对象的hashcode一定相等
- 如果两个对象的hashcode相等,但是对象不一定equlas
- 分区的数量和Reduce的数量是相等的,一个分区对应一个Reduce
6.2.7 排序 Sort
-
定义理解:
-
排序步骤:
- 对要溢写的数据进行排序(快速排序)
- 然后先分区Partation后,按照Key的顺序排序,相同的分区在一起,相同的Key在一起
-
注意:
- 我们将来溢写出的小文件也是有序的
6.2.8 溢写 Spill
-
定义理解:
- Spill过程要结合环形数据缓冲区理解,
- map还会为输出操作启动一个守护线程,如果缓冲区的内存达到了阀值的80%时候,这个守护线程就会把内容写到磁盘上,这个过程叫spill
- 环形缓冲区的数据到达80%时,就会溢写到本地磁盘,当再次达到80%时,就会再次溢写到磁盘, 直到最后一次,不管环形缓冲区还有多少数据,都会溢写到磁盘。然后会对这多次溢写到磁盘的多个小文件进行合并,减少Reduce阶段的网络传输。
-
作用:
- 将内存中的数据循环写到硬盘,不用担心OOM的问题
-
注意:
- 每次会产生一个80M的文件
- 如果本次Map产生的数据较多,可能会溢写多个文件
- 如果环形缓冲区的数据没有达到80%的阶段就结束了,这时就直接把环形缓冲区的数据写到磁盘上,供下一步使用
6.2.9 合并 Merge
-
定义理解:
-
合并的原因:
- 因为溢写回产生很多有序(分区 key)的小文件,而且小文件的数目不确定,后面向Reduce传递数据的时候会带来很大问题
- 所以将小文件合并成一个大文件,将来拉取的时候数据直接从大文件拉取即可
- 合并小文件的时候同样进行排序(归并排序),最终产生一个有序的大文件
- 原因的深入理解:
- 把溢写出的文件合并到一起形成一个总的结果,这就意味着要么把这些数据调到一台机器上进行汇总(所有单词都在一个机器上进行统计),要么将不同部分的数据在不同节点上汇总(这几个单词在这台机器上统计,其他单词在另外一台机器上统计)。如果是前者,汇总的那一台机器的负载会很高,如果后者,还需要写一个中间数据的调度系统。
- 参考资料: 为什么要用MapReduce以及MapReduce的切片_黑白键的约定的博客-CSDN博客
6.2.10 组合器 combiner
6.2.11 拉取 Fetch
-
定义理解:
- 我们需要将Map的临时结果 拉取到Reduce节点
-
原则:
- 相同的key必须拉取到同一个Reduce节点
- 但是一个Reduce节点可以有多个key
-
未排序前的数据处理
- 未排序前,需要拉取数据的时候必须对Map产生的最终的合并文件做全序遍历(这样相比于先快排,再归并,效率低很多)
- 而且每一个Reduce都要做一个全序遍历
- 如果Map产生的大文件是有序的,那么每一个Reduce只需要从文件中读取自己所需的即可(效率将大大提升)
6.2.12再次合并 Merge
- 原因:
- 因为Reduce拉取的时候,会从多个Map拉取数据,那么每个Map都会产生一个小文件,这些小文件之间无序,文件内部有序
- 为了方便计算(没必要读取N个小文件),所以需要合并
- 归并算法合并
- 相同的key都在一起
6.2.13 写出Output
- 每个Reduce将自己计算的最终结果都会存放到HDFS上
6.2.14 MapReduce的过程图解
- 图解1:
- 图解2:
-
图解3:
详细过程图解:
6.15 MapReduce的工作流程总结 ☕️
-
客户端将每个block块切片(逻辑切分),每个切片都对应一个map任务,默认一个block块对应一个切片和一个map任务,split包含的信息:分片的元数据信息,包含起始位置,长度,和所在节点列表等
-
map按行读取切片数据,组成键值对,key为当前行在源文件中的字节偏移量,value为读到的字符串
-
map函数对键值对进行计算,输出<key,value,partition(分区号)>格式数据,partition指定该键值对由哪个reducer进行处理。通过分区器,key的hashcode对reducer个数取模。
-
map将kvp写入环形缓冲区内,环形缓冲区默认为100MB,阈值为80%,当环形缓冲区达到80%时,就向磁盘溢写小文件,该小文件先按照分区号排序,区号相同的再按照key进行排序,归并排序。溢写的小文件如果达到三个,则进行归并,归并为大文件,大文件也按照分区和key进行排序,目的是降低中间结果数据量(网络传输),提升运行效率
-
如果map任务处理完毕,则reducer发送http get请求到map主机上下载数据,该过程被称为洗牌shuffle
-
可以设置combinclass(需要算法满足结合律),先在map端对数据进行一个压缩,再进行传输,map任务结束,reduce任务开始
-
reduce会对洗牌获取的数据进行归并,如果有时间,会将归并好的数据落入磁盘(其他数据还在洗牌状态)
-
每个分区对应一个reduce,每个reduce按照key进行分组,每个分组调用一次reduce方法,该方法迭代计算,将结果写到hdfs输出
6.16 shuffle洗牌过程 ☕️
-
copy:一个reduce任务需要多个map任务的输出,每个map任务完成时间很可能不同,当只要有一个map任务完成,reduce任务立即开始复制,复制线程数配置mapred-site.xml参数“mapreduce.reduce.shuffle.parallelcopies",默认为5.
-
copy缓冲区:如果map输出相当小,则数据先被复制到reduce所在节点的内存缓冲区大小配置mapred-site.xml参数“mapreduce.reduce.shuffle.input.buffer.percent”,默认0.70),当内存缓冲区大小达到阀值(mapred-site.xml参数“mapreduce.reduce.shuffle.merge.percent”,默认0.66)或内存缓冲区文件数达到阀值(mapred-site.xml参数“mapreduce.reduce.merge.inmem.threshold”,默认1000)时,则合并后溢写磁盘。
-
sort:复制完成所有map输出后,合并map输出文件并归并排序
-
sort的合并:将map输出文件合并,直至≤合并因子(mapred-site.xml参数“mapreduce.task.io.sort.factor”,默认10)。例如,有50个map输出文件,进行5次合并,每次将10各文件合并成一个文件,最后5个文件。