目录
一、关于片和块
假如我现在500M这样的数据,如何存储?
500M = 128M + 128M + 128M + 116M 分为四个块进行存储。
计算的时候,是按照片儿计算的,而不是块儿。
块是物理概念,一个块就是128M ,妥妥的,毋庸置疑。
片是逻辑概念,一个片大约等于一个块。
假如我现在需要计算一个300M的文件,这个时候启动多少个MapTask任务?答案是有多少个片儿,就启动多少个任务。
一个片儿约等于 一个块,但是最大可以 128M*1.1倍= 140.8
300M
128M 启动一个Map任务进行读取
172M 172M 和 128M * 1.1 =140.8M 进行比较,如果大于 ,继续进行切割
128M 启动一个任务Map任务
剩余44M 剩余的44M 和 128M*1.1倍比较,小于这个值,剩余的44M 就单独起一个Map任务
300m的数据,分给了3个MapTask任务进行处理。
如果是260M的数据,由多少个Map任务处理?
128M 第一个任务
132M 跟 128M * 1.1 进行比较,发现小于这个值,直接一个Map任务搞定,不在启动第三个任务了。
比如大家一起搬砖,每人规定搬3块,假定砖还剩4块,到某个人了,他就直接搬完即可,没必要让下一个人因为一块砖,而专门跑一趟。
1、什么是片,什么是块?
块是物理概念,片是逻辑概念。一般片 = 块的,但是到最后一次的时候,有可能片> 块,但是绝对不能超过块的1.1倍。
2、mapreduce 启动多少个MapTask任务?
跟片有关系,有多少个片,就启动多少个map任务。跟块儿无关。
二、MapReduce的原理
简单版本:
AppMaster: 整个Job任务的核心协调工具
MapTask: 主要用于Map任务的执行
ReduceTask: 主要用于Reduce任务的执行
一个任务提交Job --> AppMaster(项目经理)--> 根据切片的数量统计出需要多少个MapTask任务 --> 向ResourceManager(Yarn平台的老大)索要资源 --> 执行Map任务,先读取一个分片的数据,传递给map方法。--> map 方法不断的溢写 --> reduce 方法 --> 将统计的结果存放在磁盘上。
分开讲解版:
MapTask执行阶段
1. maptask调用FileInputFormat的getRecordReader读取分片数据
2. 每行数据读取一次,返回一个(K,V)对,K是offset(偏移量),V是一行数据
3. 将k-v对交给MapTask处理
4. 每对k-v调用一次map(K,V,context)方法,然后context.write(k,v)
5. 写出的数据交给收集器OutputCollector.collector()处理
6. 将数据写入环形缓冲区,并记录写入的起始偏移量,终止偏移量,环形缓冲区默认大小100M
7. 默认写到80%的时候要溢写到磁盘,溢写磁盘的过程中数据继续写入剩余20%
8. 溢写磁盘之前要先进行分区然后分区内进行排序
9. 默认的分区规则是hashpatitioner,即key的 hash%reduceNum
所有的mapreduce,其实都用到了分区,如果不写,使用的是默认的分区。
job.setNumReduceTask(3);
10. 默认的排序规则是key的字典顺序,使用的是快速排序
11. 溢写会形成多个文件,在maptask读取完一个分片数据后,先将环形缓冲区数据刷写到磁盘
12. 将数据多个溢写文件进行合并,分区内排序(外部排序===》归并排序)
ReduceTask的执行流程:
1. 数据按照分区规则发送到reducetask
2. reducetask将来自多个maptask的数据进行合并,排序(外部排序===》归并排序)
3. 按照key相同分组
4. 一组数据调用一次reduce(k,iterable<v>values,context)
5. 处理后的数据交由reducetask
6. reducetask调用FileOutputFormat组件
7. FileOutputFormat组件中的write方法将数据写出。
ReduceTask任务的数量是由谁决定的?
job.setNumReduceTasks(5);
是指定的,设置的几个就执行几个。
这个值不能瞎设置,要参考分区数量,加入有三个分区,ReduceTask任务就需要指定为3个。
三、Shuffle 过程
MapReduce的Shuffle过程指的是MapTask的后半程,以及ReduceTask的前半程,共同组成的。
从MapTask中的map方法结束,到ReduceTask中的reduce方法开始,这个中间的部分就是Shuffle。是MapReduce的核心,心脏。
map端:
1、map中的context.write方法,对外写出的时候,其实是写入到了一个环形缓冲区内(内存形式的),这个环形缓冲区大小是100M,可以通过参数设置。如果里面的数据大于80M,就开始溢写(从内存中将数据写入到磁盘上)。溢写的文件存放地址可以设置。
2、在溢写过程中,环形缓冲区不会停止工作,是会利用剩余的20%继续存入环形缓冲区的。除非是环形缓冲区的内存满了,map任务就被阻塞了。
在溢写出来的文件中,是排过序的,排序规则:快速排序算法。在排序之前,会根据分区的算法,对数据进行分区。是在内存中,先分区,在每一个分区中再排序,接着溢写到磁盘上的。
3、溢写出来的小文件需要合并为一个大文件,因为每一个MapTask只能有一份数据。就将相同的分区文件合并,并且排序(此处是归并排序)。每次合并的时候是10个小文件合并为一个大文件,进行多次合并,最终每一个分区的文件只能有一份。
假如100个小文件,需要合并几次呢?
100 每10分合并一次,第一轮:100个文件合并为了10个文件,这10个文件又合并为一个大文件,总共合并了11次。
4、将内存中的数据,溢写到磁盘上,还可以指定是否需要压缩,以及压缩的算法是什么。
reduce端:
1、reduce端根据不同的分区,拉取每个服务器上的相同的分区的数据。
reduce任务有少量复制线程,因此能够并行取得map输出。默认值是5个线程,但这个默认值可以修改设置mapreduce.reduce.shuffle. parallelcopies 属性即可。
2、如果map上的数据非常的小,该数据会拉取到reduce端的内存中,如果数据量比较大,直接拉取到reduce端的硬盘上。
环形缓冲区:
环形缓冲区,其实是一个数组,将数组分为两部分,分割的这个点就称之为轴心。存储KV键值对,是从左到右,类似于顺时针,因为每一个KV键值对都有对应的元数据。元数据是从轴心开始,从右向左执行。
当两者数据占用空间达到80%的时候,需要清理数组,清理完之后,轴心发生了变化。
KV键值对的元数据,
(每四个是一组,共计4组)
前面四个第一组::表示Value的起始位置,第二组:Key值的起始位置,第三组:分区信息,第四组:val的长度。这些内容称之为KV键值对的meta数据(元数据)。
四、Combiner 【可有可无】
这个Combiner是一个优化的代码,对于我们最终的结果没有任何的影响。
map端产生的数据,会被拉去到reduce端进行合并,有可能map端产生的数据非常的大,不便于在网络间传输,那么有没有办法可以缩小map端的数据呢?
之前: java 1 java 1 java 1 传递给reduce
现在: java 3 传递给reduce
Combiner其实就是运行在mapTask中的reducer。 Reducer其实就是合并代码的。Combiner是作用在Map端的。
这个结果不是最终的结果,而是一个临时的小统计。 最终reduce是会将所有的map结果再次进行汇总才是我们最终想要的统计结果。
Combiner 只能用于对统计结果没有影响的场景下。
一般只用于 统计之和,统计最大值最小值的场景下。统计平均值等情况是不能用的。
在代码中如何使用?
Combiner起作用的地方:
Combiner 其实作用于两个地方,一个是环形缓冲区溢写磁盘的时候,除了分区,排序之外,还可以做合并操作,将内存中的 hello 1 hello 1 hello 1 会合并为 hello 3
第二个位置是小文件合并为MapTask的大文件的时候,会将多个 hello 的值相加 hello 19,但是这个不是最终的答案,最终答案是将多个MapTask任务中的hello 进行合并才是最终的结果。
五、需要记忆的内容
1. 从map函数输出到reduce函数输入数据,这个过程称之为shuffle.
2. map函数的输出,存储环形缓冲区(默认大小100M,阈(阈值、阀值)值80M)
环形缓冲区:其实是一个字节数组kvbuffer. 这个字节数组,由两部分组成,一部分顺时针存储KV键值对,一部分逆时针存储 KV键值对的元数据。
3. 当达到阈值80%时,准备溢写到本地磁盘(因为是中间数据,因此没有必要存储在HDFS上)。在溢写前要进行对元数据分区(partition)整理,然后进行排序(quick sort,通过元数据找到出key,同一分区的所有key进行排序,排序完,元数据就已经有序了,在溢写时,按照元数据的顺序寻找原始数据进行溢写)
4. 如果有必要,可以在排序后,溢写前调用combiner函数进行运算,来达到减少数据的目的
5. 溢写文件有可能产生多个,然后对这多个溢写文件进行再次合并(也要进行分区和排序)。当溢写个数>=3时,可以再次调用combiner函数来减少数据。如果溢写个数<3时,默认不会调用combiner函数。每10个小文件合并一次,最终合并为一个大文件。
6. 合并的最终溢写文件可以使用压缩技术来达到节省磁盘空间和减少向reduce阶段传输数据的目的。(存储在本地磁盘中)
7. Reduce阶段通过HTTP写抓取属于自己的分区的所有map的输出数据(默认线程数是5,因此可以并发抓取)。
小文件拉过来放入内存,大文件拉过来放入磁盘。
8. 抓取到的数据存在内存中,如果数据量大,当达到本地内存的阈值时会进行溢写操作,在溢写前会进行合并和排序(排序阶段),然后写到磁盘中。
9. 溢写文件可能会产生多个,因此在进入reduce之前会再次合并(合并因子是10),最后一次合并要满足10这个因子,同时输入给reduce函数,而不是产生合并文件。reduce函数输出数据会直接存储在HDFS上。