咱们前面简单说过,shuffer是一个网络拷贝的过程,是指通过网络把数据从map端拷贝到reduce端的过程,下面我们来详细分析一下这个过程。
看这张图
接下来我们来根据这张图分析一下shuffle的一些细节信息,
首先看map阶段,最左边有一个inputsplit,最终会产生一个map任务,map任务在执行的时候会把k1,v1转化为k2,v2,这些数据会先临时存储到一个内存缓冲区中,这个内存缓冲区的大小默认是100M(io.sort.mb属性),当达到内存缓冲区大小的80%(io.sort.spill.percent)也就是80M的时候,会把内存中的数据溢写到本地磁盘中(mapred.local.dir),一直到map把所有的数据都计算完,最后会把内存缓冲区中的数据一次性全部刷新到本地磁盘文件中,在这个图里面表示产生了3个临时文件,每个临时文件中有3个分区,这是由于map阶段中对数据做了分区,所以数据在存储的时候,在每个临时文件中也划分为了3块,最后需要对这些临时文件进行合并,合并为一个大文件,因为一个map任务最终只会产生一个文件,这个合并之后的文件也是有3个分区的,
这3个分区的数据会被shuffle线程分别拷贝到三个不同的reduce节点,图里面只显示了一个reduce节点,下面还有两个没有显示。不同map任务中的相同分区的数据会在同一个reduce节点进行合并,合并以后会执行reduce的功能,最终产生结果数据。
在这里shuffle其实是横跨map端和reduce端的,它主要是负责把map端产生的数据通过网络拷贝到reduce阶段进行统一聚合计算。
咱们前面在开发MapReduce程序的时候使用到了LongWritable和Text这些数据类型,这些数据类型对应的是Java中的Long和String,那MapReduce为什么不直接使用Java中的这些数据类型呢?那肯定是嫌弃Java中的这些数据类型使用起来不爽,那具体不爽在什么地方呢?
这个其实就涉及到序列化这个知识点了,下面我们来分析一下,来看这张图,
我们的map阶段在读取数据的是需要从hdfs中读取的,这里面需要经过磁盘IO和网络IO,不过正常情况下map任务会执行本地计算,也就是map任务会被分发到数据所在的节点进行计算,这个时候,网络io几乎就没有了,就剩下了磁盘io,再往后面看,map阶段执行完了以后,数据会被写入到本地磁盘文件,这个时候也需要经过磁盘io,后面的shuffle拷贝数据其实也需要先经过磁盘io把数据从本地磁盘读出来再通过网络发送到reduce节点,再写入reduce节点的本地磁盘,然后reduce阶段在执行的时候会经过磁盘io读取本地文件中的数据,计算完成以后还会经过磁盘io和网络io把数据写入到hdfs中。
经过我们刚才的分析,其实在这里面占得比重最高的是磁盘io,所以说影响mapreduce任务执行效率的主要原因就是磁盘io,如果想要提高任务执行效率,就需要从这方面着手分析。
当程序在向磁盘中写数据以及从磁盘中读取数据的时候会对数据进行序列化和反序列化,磁盘io这些步骤我们省略不了,但是我们可以从序列化和反序列化这一块来着手做一些优化,
首先我们分析一下序列化和反序列化,
看这个图,当我们想把内存中的数据写入到文件中的时候,会对数据序列化,然后再写入,这个序列化其实就是把内存中的对象信息转成二进制的形式,方便存储到文件中,默认java中的序列化会把对象及其父类、超类的整个继承体系信息都保存下来,这样存储的信息太大了,就会导致写入文件的信息过大,这样写入是会额外消耗性能的。
反序列化也是一样,reduce端想把文件中的对象信息加载到内存中,如果文件很大,在加载的时候也会额外消耗很多性能,所以如果我们把对象存储的信息尽量精简,那么就可以提高数据写入和读取消耗的性能。
基于此,hadoop官方实现了自己的序列化和反序列化机制,没有使用java中的序列化机制,所以hadoop中的数据类型没有沿用java中的数据类型,而是自己单独设计了一些writable的实现了,例如、longwritable、text等
那我们来看一下Hadoop中提供的常用的基本数据类型的序列化类。
Java基本类型 Writable 序列化大小(字节) 布尔型(boolean) BooleanWritable 1 字节型(byte) ByteWritable 1 整型(int) IntWritable 4 VIntWritable 1~5 浮点型(float) FloatWritable 4 长整型(long) LongWritable 8 VLongWritable 1~9 双精度浮点型(double) DoubleWritable 8
在这需要注意一下
Text等价于java.lang.String的Writable,针对UTF-8序列
NullWritable是单例,获取实例使用NullWritable.get()
那下面我们来总结一下hadoop自己实现的序列化有什么特点
紧凑: 高效使用存储空间 快速: 读写数据的额外开销小 可扩展: 可透明地读取老格式的数据 互操作: 支持多语言的交互
对应的我们也对java中序列化的不足之后做了一个总结
不精简,附加信息多,不太适合随机访问 存储空间大,递归地输出类的超类描述直到不再有超类



