目录
- MapReduce框架原理
- 一、InputFormat数据输入
- 1. 切片与MapTask并行度决定机制
- 2. FielInputFormat切片机制
- 3. FileInputFormat切片大小的参数设置
- 4. TextInputFormat
- 1).FileInputFormat实现类
- 2).TextInputFormat
- 5. CombineTextInputFormat切片机制
- 1). 应用场景
- 2). 虚拟存储切片最大值设置
- 3). 切片机制
- 二、MapReduce工作流程
MapReduce框架原理
一、InputFormat数据输入
1. 切片与MapTask并行度决定机制
1.问题:
MapTask的并行度决定Map阶段的任务处理并发度,进而影响整个job的处理速度。
但是相对于1G数据启动8个MapTask,可以提高集群的并发处理能力。1k的数据数据启动8个MapTask不一定会提高集群性能;MapTask并行任务是否越多越好?哪些因素影响了MapTask并行度?
MapTask 并行度由切片个数决定,切片个数由输入文件和切片规则决定。
2.MapTask并行度决定机制
数据块:block是HDFS物理上把数据进行分块(0-128MB)。数据块是HDFS存储数据单位
;
数据切片:数据切片只是逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位
,一个切片会对应启动一个MapTask。
tips: 切片大小最好与block大小一致,即设置默认128MB,处理更加有效率。
⚠️:
- ReduceTask=0,表示没有Reduce阶段,输出文件个数和Map个数一致;
- ReduceTask默认值就是1,所以输出文件个数为一个。 (3)如果数据分布不均匀,就有可能在Reduce阶段产生数据倾斜;
- ReduceTask数量并不是任意设置,还要考虑业务逻辑需求,有些情况下,需要计算全 局汇总结果,就只能有1个ReduceTask;
- 具体多少个ReduceTask,需要根据集群性能而定;
- 如果分区数不是1,但是ReduceTask为1,是否执行分区过程。答案是:不执行分区过 程。因为在MapTask的源码中,执行分区的前提是先判断ReduceNum个数是否大于1。不大于1 肯定不执行。
FielInputFormat切片源码解析:
2. FielInputFormat切片机制
3. FileInputFormat切片大小的参数设置
4. TextInputFormat
1).FileInputFormat实现类
在运行MapReduce程序时,输入的文件格式包括:基于行的日志文件、二进制格式文件、数据库表等。那么,针对不同的数据类型,MapReduce时如何读取这些数据的呢?
FileInputFormat常见的接口实现类包括:TextInputFormat
、KeyValueInputFormat、NLineInputFormat、CombineTextInputFormat
和自定义InputFormat等。
2).TextInputFormat
TextInputFormat是默认的FileInputFormat实现类。按行读取每条记录。键时存储该行在整个文件中的起始字节偏移量,LongWritable类型。值是这行的内容,不包括任何行终止符(换行符和回车符),Text类型。
5. CombineTextInputFormat切片机制
框架默认的
TextInputFormat切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片
,都会交给一个MapTask,这样如果有大量小文件,就会产生大量的Maptask,处理效率极其低下。
1). 应用场景
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中
,这样,多个小文件就可以交给一个MapTask处理。
2). 虚拟存储切片最大值设置
CombineTextInputFormat.setmaxInputSplitSize(job,4194304); // 4M
tips: 虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。
3). 切片机制
生成切片过程包括:虚拟存储过程和切片过程两部分。
二、MapReduce工作流程
切片数量影响Maptask,分区数量影响ReduceTask。
MapReduce详细工作流程一:
⭐️:MapTask工作机制共分为5个阶段:Read阶段、Map阶段、Collect阶段、溢写阶段、Merge阶段。
- Read 阶段: MapTask 通过 InputFormat 获得的 RecordReader,从输入 InputSplit 中 解析出一个个 key/value。
- Map 阶段: 该节点主要是将解析出的 key/value 交给用户编写 map()函数处理,并 产生一系列新的 key/value。
- Collect 收集阶段: 在用户编写 map()函数中,当数据处理完成后,一般会调用 OutputCollector.collect()输出结果。在该函数内部,它会将生成的 key/value 分区(调用 Partitioner),并写入一个环形内存缓冲区中。
- Spill 阶段: 即“溢写”,当环形缓冲区满后,MapReduce 会将数据写到本地磁盘上, 生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
溢写阶段:
.
a. 利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号 Partition 进行排序,然后按照 key 进行排序。这样,经过排序后,数据以分区为单位聚集在 一起,且同一分区内所有数据按照 key 有序。
.
b. 按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文 件 output/spillN.out(N 表示当前溢写次数)中。如果用户设置了 Combiner,则写入文件之 前,对每个分区中的数据进行一次聚集操作。
.
c. 将分区数据的元信息写到内存索引数据结构 SpillRecord 中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大 小超过 1MB,则将内存索引写到文件 output/spillN.out.index 中。
- Merge 阶段: 当所有数据处理完成后,MapTask 对所有临时文件进行一次合并, 以确保最终只会生成一个数据文件。
当所有数据处理完后,MapTask 会将所有临时文件合并成一个大文件,并保存到文件 output/file.out 中,同时生成相应的索引文件 output/file.out.index。
在进行文件合并过程中,MapTask 以分区为单位进行合并。对于某个分区,它将采用多 轮递归合并的方式。每轮合并 mapreduce.task.io.sort.factor(默认 10)个文件,并将产生的文 件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
让每个 MapTask 最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量 小文件产生的随机读取带来的开销。
tips: 步骤7. 数据往内存中写到80%的时候,新开了一个线程把内存中的旧数据往磁盘的文件进行溢写,另一个线程继续把从MapTask来的数据写到内存里,因为此时还未到100%,所以新的数据可以正常写,不需要等所有的数据都溢写完后再开始。
若是往内存写数据的线程写到交界点,它会等溢写完成后再继续写,此处的等待时间会比写到100%之后溢写等待的时间短,且此时的等待是为了保证数据干净必须等待的。
步骤8. 在溢写之前对数据进行排序,通过对key的索引按照字典顺序进行快速排序。
步骤10. 对溢写之后的数据进行归并排序。
combiner在聚合操作的场景下,使得传到Reduce的数据量变小( <a,1><a,1> ==> <a,2> ),从而提高效率。
MapReduce详细工作流程二:
⭐️:ReduceTask工作机制共分为3个阶段:Copy阶段、Sort阶段、Reduce阶段。
- Copy 阶段: ReduceTask 从各个 MapTask 上远程拷贝一片数据,并针对某一片数 据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
- Sort 阶段: 在远程拷贝数据的同时,ReduceTask 启动了两个后台线程对内存和磁 盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。按照 MapReduce 语义,用 户编写 reduce()函数输入数据是按 key 进行聚集的一组数据。为了将 key 相同的数据聚在一 起,Hadoop 采用了基于排序的策略。由于各个 MapTask 已经实现对自己的处理结果进行了 局部排序,因此,ReduceTask 只需对所有数据进行一次归并排序即可。
- Reduce 阶段: Reduce()函数将计算结果写到 HDFS 上。
tips: 步骤13. ReduceTask主动从MapTask分区拉取数据(copy阶段),而不是等待MapTask传递数据给它。