MapReduce
目录
一、概述
- 是Hadoop中的一套分布式的计算框架
- 将整个计算过程拆分为2个阶段:Map阶段和Reduce阶段
- Map阶段负责数据的整理,Reduce阶段负责数据的汇总
- Reducer中的迭代器采用的是地址复用机制
- Reducer中的迭代器只能遍历一次
- 如果Mapper和Reducer的结果类型一致,可以只设置一个
- 如果输入路径是一个文件,则MapReduce只处理这个文件;如果输入的是一个目录,则处理这个目录下的所有的文件 --- 注意:如果文件以_开头,则该文件会被跳过。在MapReduce中,_开头的文件被认为是隐藏文件不需要处理
- 在MapReduce中,如果不指定,默认键值之间用\t分隔
- 如果Reducer没有业务逻辑,可以省略Reducer
二、序列化机制
- 在MapReduce中,要求被传输的数据必须能够被序列化
- Hadoop中,序列化机制默认使用AVRO,但是Hadoop对AVRO的序列化机制进行了进一步的封装,提供了更简单的序列化机制
- 在Hadoop要想实现序列化,需要实现Writable,重写其中的方法
- 在Hadoop中,序列化的时候,要求属性不能为null
- 案例:按人统计每一个的总流量 - cn.tedu.flow
- 练习:score.txt - 计算每一个人的总成绩 - cn.tedu.serialscore
三、分区
- 分区在MapReduce中用于进行数据的分类
- 在MapReduce中,如果不指定,则默认只有1个分区
- 每一个分区都必须对应一个ReduceTask,每一个ReduceTask都会产生一个结果文件
- 在MapReduce对分区进行了编号,编号默认从0开始递增
- 分区的顶级父类是Partitioner
- 在MapReduce中,默认使用的HashPartitioner
- 案例:分地区统计不同的人花费的总流量 - cn.tedu.partflow
四、排序
- 在MapReduce中,会对键做自动的排序 - 自然排序
- 如果自定义一个类产生的对象要想作为键,那么这个对象必须要允许被排序 - WritableComparable
- 多属性排序的场景称之为二次排序
- 练习:profit2.txt - 先按照月份进行升序,如果月份一样,则按照业绩降序 - cn.tedu.sortprofit
五、合并
- 在MapReduce中,因为默认只有1个Reduce,所以所有的计算压力都会落在Reduce上,导致Reduce的计算性能成了整个MapReduce的瓶颈,所以为了提高计算效率,可以在数据传输到Reduce之前先对数据进行一次整合,这个过程称之为合并 - Combine
- 合并不会影响最终的计算结果
- 一般而言,Combine和Reduce的逻辑是一致的,只需要在Driver中添加job.setCombinerClass(class);
六、数据本地化策略
- 当JobTracker接收到应用之后,会去访问NameNode获取要处理的文件信息
- NameNode将文件信息返回给JobTracker
- JobTracker收到文件信息之后会将文件进行切片(只包含切片信息不包含实际数据),一般习惯上,会将切片和切块设置的一样大。每一个切片会分给一个MapTask
- JobTracker会MapTask分发到TaskTracker来执行。在分发的时候,哪一个DataNode上有对应的Block,那么MapTask就会分发这个节点上 - 数据本地化
- 数据本地化的目的:减少对网络资源的消耗
七、job的执行流程
- 客户端提交一个job任务到JobTracker: hadoop jar xxx.jar
- JobTracker收集环境信息:
- 检测类型是否匹配
- 检测输入/输出路径是否合法
- JobTracker给job分配一个全局递增的jobid,然后将jobid返回给客户端
- 客户端收到jobid之后,将jar包提交到HDFS上
- 准备执行job任务
- JobTracker会将job任务进行划分,划分为MapTask和ReduceTask,其中MapTask的数量由切片数量决定,ReduceTask的数量由Partitioner/numReduceTask决定
- JobTracker等待TaskTracker的心跳。一般TaskTracker和DataNode会设置为同一个节点。当TaskTracker发送心跳信息的时候,这个时候JobTracker就会给TaskTracker分配任务。注意:在分配的时候,MapTask符合数据本地化策略(当TaskTracker上有这个数据的时候才会将MapTask分给它),ReduceTask分配到相对空闲的节点上
- TaskTracker领取到任务之后,到对应的节点下载jar包 - 体现的思想:逻辑移动而数据固定
- TaskTracker会在本节点内启动JVM子进程执行MapTask或者是ReduceTask - 注意:每一个MapTask或者ReduceTask都会启动一次JVM子进程
八、Shuffle过程
1.Map端的Shuffle
- 每一个Split会分给一个MapTask来处理
- MapTask默认是对数据进行按行读取,每读取一行调用一次map方法
- map方法在处理完一行数据的时候会将数据写出到缓冲区中
- 每一个MapTask都自带了一个缓冲区,缓冲区维系在内存中
- 缓冲区默认大小是100M
- 当缓冲区达到条件的时候,将缓冲区中的数据写到本地磁盘上,这个过程称之为溢写(Spill),产生的文件称之为溢写文件
- 溢写之后,后续产生的数据会继续写到缓冲区中
- 溢写过程可能发生不止一次
- 当map将结果放到缓冲区中之后,结果在缓冲区中会进行分区和排序过程;如果指定了Combiner,那么数据在缓冲区中还会进行合并
- 如果所有数据处理完成,但是有一部分数据在缓冲区中,那么将缓冲区中的数据冲刷到上一次的溢写文件中
- 因为可能会产生多次溢写,那么会产生多个溢写文件,在结果发往Reduce之前,会将多个溢写文件进行合并(merge),将多个溢写文件合并成一个结果文件
注意:
- 溢写过程不一定会发生。如果没有发生溢写过程,则将缓冲区中的数据直接冲刷到最后的结果文件中;如果只有一次溢写,那么这个溢写文件就是最后的结果文件
- merge过程也不一定发生
- 如果溢写文件个数>=3个,那么在merge过程中,自动进行一次combine(指定了Combiner)
- 在merge过程中,结果会进行整体的分区和排序
- 初始数据量并不能决定溢写次数
- 阈值默认是0.8 - 即缓冲区的使用达到这个大小的80%的时候,就开始溢写
- 缓冲区本质上是一个字节数组,而且是一个环形的缓冲区,目的是为了重复利用缓冲区
2.Reduce端的Shuffle
- ReduceTask启动fetch线程去MapTask上抓取数据,只抓取当前要处理的分区的数据,将抓取的数据放到文件中,每从一个MapTask中抓取到数据就会产生一个数据文件
- 抓取完数据之后,将这个数据文件再次进行merge
- 在merge过程中,对数据再次进行排序
- merge完成之后,将相同的键所对应的值放入一个List集合,然后利用List集合去产生迭代器,这个过程称之为分组(group)
- 每一组会调用一次reduce方法
- 每一个ReduceTask会产生一个结果文件
注意:
- 默认fetch线程的数量为5,表示每一个ReduceTask会启动5个线程去抓取数据
- fetch线程通过http请求去抓数据
- merge因子默认为10,表示每10个数据文件进行一次合并,最后合成1个数据文件
- reduce阈值默认为0.05,表示当有5%的MapTask结束,那么启动ReduceTask开始抓取数据
3.Shuffle调优
- 调大缓冲区,实际开发中,一般会将缓冲区设置为250~400M左右
- 尽量添加Combiner,不是所有场景下都可以添加Combiner
- 调大缓冲区的阈值 - 这种方式不推荐
- 将MapTask的结果进行压缩 - 这种方式能够有效的减少网络的传输时间 - 这种方式不算优化,只是在节点资源和网络资源之间进行取舍
- 增加fetch线程的数量 - 考虑服务器的线程承载量
- 增大merge因子 - 不推荐
- 降低Reduce的阈值 - 不推荐
九、小文件处理
- 危害:
- 存储:每一个小文件在存储的时候都会产生一条元数据,如果存储大量的小文件,会产生大量的元数据,导致NameNode的效率变低;如果小文件数量过多,可能会导致NameNode的内存崩溃
- 计算:每一个小文件都会作为一个切片来处理,每一个切片要对应一个MapTask,意味着如果产生了大量的小文件,会对应大量的切片,则需要大量的MapTask来处理。导致整个集群的并发量要增加,如果超过集群的承载能力导致服务器宕机
- 处理手段:合并(将多个小文件合并成一个大文件、利用一个任务处理多个小文件)、压缩
- 合并:
- 手动合并:手动编码、合并工具
- har包 - Hadoop Archive
- CompositeInputFormat - 多源输入
- har:将多个小文件合并成一个文件,并且达成一个har包