MapReduce 执行过程分析
Mapreduce-分布式运算框架,用户开发“基于hadoop的数据分析应用”的核心框架
Map
1.读数据
mapTask读取数据源文件进行切片,每一个block启动一个mapTask,每个map task按照行读取一个block中的内容
2.处理数据
map函数对输入的每一行数据进行处理
在内存中将数据存到环形缓冲区(byte[]数组),进行sort排序,自定义分区(每个分区对应一个reduceTask,缓解数据倾斜问题,可以让后面的reduce平均分摊任务,默认对key hash后再以reduce task数量取模),让结构变得简单
3.写入数据到磁盘
缓冲区达到80%(默认大小100M,超过则溢写磁盘临时文件)在map端执行spill,输出<word,次数>(尽可能一次性合并多个spill)按照key进行分组排序( 有combiner就combiner降低网络数据传输时 间,让后面的reduce阶段,平均分摊任务),归并后可以进行压缩操作(对CPU造成压力,对IO有利)
Reduce
1.copy:拉取数据,获取map task的在磁盘里面的输出文件(添加并发下载的线程数)
2.Merge:这里的merge如map端的merge动作,只是数组中存放的是map端copy来的数值,Copy过来的数据会先放入内存缓冲区中,这里的缓冲区大小要比map端的更为灵活,它基于JVM的heap size设置
merge有三种形式:1)内存到内存 2)内存到磁盘(默认) 3)磁盘到磁盘。默认情况下第一种 形式不启用。当内存中的数据量到达一定阈值,就启动内存到磁盘的merge。与map 端类似,这也是溢写的过程,这个过程中如果你设置有Combiner,也是会启用的,然后在磁盘中生成了众多的溢写文件。第二种merge方式一直在运行,直到没有map端的数据时才结束,然后启动第三种磁盘到磁盘的merge方式生成最终的那个文件。
3.Reducer的输入文件。不断地merge后,最后会生成一个“最终文件”。默认情况下,这个文件是存放于磁盘中的。
环形缓冲区
1.为什么要环形缓冲区?
答:使用环形缓冲区,便于**写入**缓冲区和**写出**缓冲区**同时进行**,批量收集map结果,减少磁盘IO的影响
2.为什么不等缓冲区满了再spill?
答:**会出现阻塞。**
3.数据的分区和排序是在哪完成的?
答:分区是根据**元数据**meta中的**分区号**partition来分区的,**排序是在spill**的时候排序。
4.Shuffle 中的缓冲区大小会影响到 mapreduce 程序的执行效率,原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快
shuffle阶段做了什么?什么是shuffle阶段?
map输出到reduce输入的中间过程就是shuffle阶段
(1)collect阶段:将 MapTask 的结果输出到默认大小为 100M 的环形缓冲区,
保存的是 key/value,Partition 分区信息等。
(2)spill阶段:当内存中的数据量达到一定的阀值的时候,就会将数据写
入本地磁盘,在将数据写入磁盘之前需要对数据进行一次排序的操作,如果配置
了 combiner,还会将有相同分区号和 key 的数据进行排序。
(3)merge阶段:把所有溢出的临时文件进行一次合并操作,以确保一个
MapTask 最终只产生一个中间数据文件。
(4)copy阶段:ReduceTask中Fetcher 线程到已经完成 MapTask 的节点
上复制一份属于自己的数据,这些数据默认会保存在内存的缓冲区中
(5)merge阶段:当内存的缓冲区达到一定的阀值的时候,就会将数据写到磁盘之上。在 ReduceTask 远程复制数据的同时,会在后台开启两个线程对内存到本地的数据文件进行合并操作。
(6)sort阶段:在对数据进行合并的同时,会进行排序操作,由于 MapTask
阶段已经对数据进行了局部的排序,ReduceTask 只需保证 Copy 的数据的最终整
体有效性即可。
Mapreduce数据倾斜原因和解决方案
数据倾斜:
简单来说数据倾斜就是
大量的相同key被partition分配到一个分区里
造成一部分数据很多,一部分数据很少的局面。
数据倾斜发生时的现象:
1、绝大多数task执行得都非常快,但个别task执行的极慢。
2、原本能正常执行的Spark作业,某天突然爆出OOM(内存溢出)异常。观察异常栈,是我们写的业务代码造成的
HiveSQL语句中的发生数据倾斜的状况
1.group by 维度过小,某值的数量过多,导致处理某值的reduce非常耗时,
2.特殊值过多,解决:去重操作distinct
3.其中一个表较小,但是key集中,分发到某一个或几个Reduce上的数据远高于平均值
4.分桶的判断字段0值或空值过多
5.没有把重复关联键少的表放在join前面,写在关联左侧的表每有1条重复的关联键时底层就会多1次运算处理
解决方案
1、增加jvm内存,通过硬件的手段来进行调优,显著的提高运行效率
2、增加reduce的个数,计算的节点多了,就算工作量还是不均匀的,那也要小很多
3、自定义分区,指定分区策略
5、使用combinner合并,大量的相同key数据先进行一个合并(local reduce),减轻了map端向reduce端发送的数据量(减轻了网络带宽),也减轻了map端和reduce端中间的shuffle阶段的数据拉取数量(本地化磁盘IO速率),推荐使用这种方法。
oom
一.为什么会OOM
主要是以下两点:
1、 分配的内存少了:比如虚拟机本身可使用的内存(一般通过启动时的VM参数指定)太少。
2、 应用程序太多,并且用完没释放,浪费了。此时就会造成内存泄露或者内存溢出。
内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
二.OOM的类型有哪些
Xms 是指设定程序启动时占用内存大小。一般来讲,大点,程序会启动的快一点,但是也可能会导致机器暂时间变慢。
Xmx 是指设定程序运行期间最大可占用的内存大小。如果程序运行需要占用更多的内存,超出了这个设置值,就会抛出OutOfMemory异常。
Xss 是指设定每个线程的堆栈大小。这个就要依据你的程序,看一个线程大约需要占用多少内存,可能会有多少线程同时运行等。
**1、*java.lang.OutOfMemoryError: Java heap space*java
堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms(初始堆大小),-Xmx(最大堆大小)等修改。
2、java.lang.StackOverflowError,不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数**-Xss(栈大小)**来设置栈的大小。
public class StackOverflowErrorDemo {
public static void main(String[] args) {
javaKeeper();
}
private static void javaKeeper() {
javaKeeper();
}
}
//JVM 虚拟机栈是有深度的,在执行方法的时候会伴随着入栈和出栈,上边的方法可以看到,main 方法执行后不停的递归,迟早把栈撑爆了
3.GC overhead limit exceeded
JVM 内置了垃圾回收机制GC,所以作为 Javaer 的我们不需要手工编写代码来进行内存分配和释放,但是当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误(俗称:垃圾回收错误,GC开销超过限制)。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。
假如不抛出 GC overhead limit exceeded 错误,那 GC 清理的那么一丢丢内存很快就会被再次填满,迫使 GC 再次执行,这样恶性循环,CPU 使用率 100%,而 GC 没什么效果。
3.1 写个 bug
出现这个错误的实例,其实我们写个无限循环,往 List 或 Map 加数据就会一直 Full GC,直到扛不住,这里用一个不容易发现的栗子。我们往 map 中添加 1000 个元素。
检查项目中是否有大量的死循环或有使用大内存的代码,优化代码
dump内存分析,检查是否存在内存泄露,如果没有,加大内存