16 mapreduce_shuffle过程

在map端的shuffle部分

1、MapTask调用map方法处理数据,默认情况下是读取一行处理一行。map方法在处理完数据之后不是直接交给reduce阶段,而是写到MapTask自带的缓冲区中- 每一个MapTask都会自带一个缓冲区
2、数据在缓冲区中会进行分区、排序,如果指定了Combiner,那么还会进行combine合并。数据在缓冲区中进行排序的时候,将完全杂乱的数据排成有序的数据,这个过程采取的是快速排序
3、缓冲区本质上是一个环形的字节数组,默认大小是100M,维系在内存中 (设置成环形缓冲区的目的是为了重复利用这个缓冲区,不需要重新创建缓冲区,而且不用重新寻址。如果是条形缓冲区写到最后的话还需要重新寻址,将指针移动到起始位置)
4、当缓冲区使用达到阈值(溢写阈值默认是0.8,即表示当缓冲区的容量使用达到80%)的时候,那么会将缓冲区中的数据进行溢写(spill),将缓冲区中的数据溢写到磁盘上,产生一个溢写文件。(之所以设置成80%而不是100%,等待缓冲区满了以后再往磁盘上写,写的过程中map在在不断的产生数据,那么这个时候要么新产生的数据会将缓冲区中还未写出的数据覆盖掉,或者map过程先阻塞,等待spill过程结束。但是这两种情况都不是最好的。所以设置80%的阈值,使得在往外写溢写文件的时候,map还可以继续往那20%的区域写数据。而且写满之后,坑定有一部分已经spill完毕,这部分空间map又可以继续往里面写键值对了。)
5、当缓冲区溢写之后,MapTask产生的新数据依然是放到缓冲区中,达到条件再次溢写,每一次溢写都会产生一个新的溢写文件。所以最后会得到多个溢写文件。
6、单个溢写文件中的数据应该是分好区且排好序的【因为在缓冲区中会进行分区,排序,合并】,多个溢写文件之间是整体无序但局部有序的
7、在当前的maptask处理完所有数据之后,会将所有的溢写文件进行合并(merge),合并成一个文件(final out);如果MapTask处理完成之后,一部分结果结果在溢写文件中,还有一部分结果在缓冲区中,那么将溢写文件和缓冲区中的数据都merge到final out;如果没有产生溢写过程,那么MapTask在处理完成之后会将数据直接写到final out中 - 也就意味着MapTask在处理完数据之后一定会产生一个final out文件 【map的结果一定是一个文件,一定会和磁盘进行交互】
8、在merge过程中,数据会再次进行分区排序,所以final out文件中的数据是分好区且排好序的。如果指定了Combiner,并且溢写文件个数>=3个,那么在merge过程中还会进行combine操作。merge过程中的排序是将局部有序的数据整理为整体有序的数据,这个过程采取的归并排序(之所以不是无条件的combine,是因为combine也是比较耗时的操作)
9、其他注意事项:
① 溢写过程不一定会产生 (写不满缓冲区阈值就不会spill了,有一个0.8的阈值)
② 原始数据的大小并不能决定是否溢写,得看MapTask处理之后产生的数据量而不是处理之前的数据量【因为maptask可能会增删字段】
③溢写过程本质上是数据从内存写到磁盘的过程,这个过程中要考虑序列化因素(可能只序列化部分数据或者增加子弹),所以溢写文件的大小不一定等于缓冲区大小*溢写阈值
④将缓冲区设置为环形的目的是为了重复利用这个缓冲区,而且不用频繁的寻址
⑤设置溢写阈值的目的是为了尽量减少甚至避免MapTask在写出结果过程中产生阻塞
在这里插入图片描述
写出的溢写文件实际上是分好区,排好序,合并过的了。merge成final out文件也还会进行分区,排序,满足条件的话还会进行合并。

在reduce端的shffle部分

1、ReduceTask启动阈值默认是0.05,即当有5%的MapTask执行结束之后(有final out文件了,1000个map有50个执行完就开始执行reduce了),就会启动ReduceTask来抓取数据
2、ReduceTask启动fetch线程(默认每一个ReduceTask可以启动5个fetch线程)通过http请求去MapTask抓取数据,在抓取数据的时候只抓取当前ReduceTask处理的分区的数据 【抓取别的分区的数据也没有用】
3、ReduceTask通过fetch线程将数据抓取来之后,每一段数据(每一个final out的部分数据)都会临时存储在本地的一个小文件中,在抓取完成之后,会这个数据存储的小文件进行merge。(在Reduce的merge过程中,merge因子默认为10,即每10个小文件合并成一个文件),在merge过程中,数据会再次进行分区排序。这一次排序依然将数据从局部有序整理成整体有序,采用的归并排序。
4、所有的小文件都merge完成以后,会将相同的键对应的值放到一组中,这个过程称之为分组(group)。分组完成之后,每一个键调用一次reduce方法计算结果。- 分组之后,这一组值会产生一个迭代器,对应了reduce方法中values,注意这个迭代器实际上是一个伪迭代器 - 在分组的时候,实际上并不真的产生了一个迭代器,因为我们平时使用的迭代器底层一定是一个容器,通过迭代器来遍历容器。但是在这里,并没有这个容器。而是在迭代的时候就去读merge出来的最终结果文件。每次从这个文件中读取2行数据,比较2行的键是否一致(compareto),如果一致,则将第一行读取的数据放到reduce中处理同时继续读取;如果2行的值不一样,那么就会标记第二行重新调用reduce方法,同时标记上一行迭代结束- 这里的迭代器本质上并不是产生一个容器去把一组数据存储了下来,而是去读取文件
5、关于reduce上merge的一个细节:如果文件的个数大于merge因子,最后一次merge的文件数量一定是和merge因子相同的。
在这里插入图片描述
group的示意图:
在这里插入图片描述

整个shuffle的过程
在这里插入图片描述

shuffle优化

为什么mr比较慢,一是因为在mr的整个过程中存在大量的磁盘交互,磁盘交互是一个比较慢的过程。二是应为在整个过程中还存在一些额外的步骤,前面节点的排序等,其实只需要能够进行最后一次排序就可以了。spark的话基于内存,而且不指定的话是不会添加一些额外的的阶段的。

  • 增大缓冲区,减少溢写次数。实际开发过程中,会将缓冲区大小设置为250M~400M
    增大溢写阈值,例如可以将阈值增大为0.9,但是这种方案会增加阻塞的风险
  • 尽量增加Combiner过程 【适当的减少数据的总条目数】
  • 如果网络资源比较紧张,可以适当的对final out进行压缩【lzo压缩是不是就是这里??】,但是同样ReduceTask通过fetch抓取到数据之后会自动进行解压。所以这个方法是对网络资源和压缩解压时间的取舍
  • 适当的增加fetch的线程 数量,考虑服务器的线程承载量
    增大merge因子(默认是10,为了减少合并次数),但是这种方案增加底层运算的复杂度(不建议使用这种方案)
    适当的调小ReduceTask的启动阈值(提早启动,默认0.05,不建议使用这种方案)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值