MapReduce

MapReduce

mapreduce是apache hadoop项目的一个核心模块。是一个运行在hdfs上的分布式运算程序的编程框架,用于大规模的数据集(大于1TB)的并行运算

优点:

1.MapReduce易于编程
2.良好的可扩展性
3.高容错性
4.适合pb以上海量数据的离线处理

缺点:

1.不适合做实时运算
2.不适合流式计算
3.不适合有向图的计算

核心思想:

1.mapreduce设计的一个理念就是"计算向数据靠拢",移动计算而不移动数据
2.将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,移动到有数据存储的集群节点上,一是可以减少结点间的数据移动开销,二是在存储节点上可以并行运算,大大提高了计算效率的问题
3.mapreduce一个完整的运算分为map阶段和reduce阶段,map阶段会处理本节点的原始数据,产生的数据会临时存储到本地磁盘,reduce阶段会跨节点fetch属于自己的数据,并进行处理,处理结果存放到hdfs上

mapreduce的两个阶段

第一阶段(map阶段):

概述:
这个阶段会有若干个maptask实例,完全并行运行,互不相干,每个maptask会读取分析一个inputSplit(输入分片)对应的原始数据,计算的结果会临时保存在所在结点的本地磁盘中

数据扭转:
该阶段的编程模型中,会有一个map函数需要开发人员重写,map函数的输入是一个<key,value>对,输出也是一个<key,value>对,key和value的类型需要开发人员指定

第二阶段(reduce阶段):

概述:
这个阶段会有若干个reducetask实例并发运行,互不相干,但是他们的数据依赖于上一个阶段的所有maptask并发实例的输出,一个reducetask会从多个maptask运行节点上fetch自己要处理的分区数据,经过处理之后,上传到hdfs上

数据扭转:
该编程模型中有一个reduce函数需要开发人员重写,reduce函数的输入也是一个<key,value>对,ruduce函数的输出也是一个<key,value>对,值得注意的是,reduce的输入其实就是map的输出,只不过map的输出经过shuffle技术变成了<key,List>而已

注意:mapreduce编程模型中只能包含一个map阶段和一个reduce阶段,如果客户要处理的逻辑十分复杂,那么只能有多个mapreduce程序串行运行

MapReduce编程规范

用户编写的程序分为三个部分,Mapper,Reducer,Driver(提交mr程序的客户端)

Mapper部分:

1.自定义类,继承Mapper类型
2.定义k1,k2,v1,v2的泛型(k1,v1是mapper的输入数据参数,k2,v2是mapper的输出数据参数)
3.重写map方法(处理逻辑)
    /*
    Mapper阶段
     */
    static  class WordCountMapper extends Mapper<LongWritable,Text, Text, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        }
    }

注意:map方法,每一个kv对都会调用一次

Reducer部分:

1.自定义类,继承Reducer类型
2.定义k2,v2,k3,v3的泛型(k2,v2是reducer的输入数据类型,k3,v3是ruducer的输出数据类型)
3.重写reduce方法的处理逻辑
   /*
    Reducer阶段
     */
    static  class WordCountReducer extends Reducer<Text,IntWritable,Text,LongWritable>{
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {

        }
    }

注意:reduce方法默认按key分组,每一组调用一次

Driver部分:

整个程序需要一个Driver来进行提交,提交的是一个描述了各种必要信息的job对象

1.获取Job对象
2.指定驱动类
3.设置Mapper和Reducer的类型
4.设置Mapper的输出k2,v2的输出类型(如果类型与k3和v3类型相同,可以忽略)
5.设置Reducer的输出k3,v3的类型
6.设置Reducer的个数(默认为1)
7.设置Mapper的输入数据的路径
8.设置Reducer的输出数据的路径
9.提交作业
		//1.获取配置信息
        Configuration configuration = new Configuration();
        //2.获取job对象
        Job job = Job.getInstance(configuration);
        //3.设置驱动类型
        job.setJarByClass(WordCount.class);
        //4.设置mapper和reducer类型
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        //由于map的输出类型k2和v2类型与reduce的输入类型k3,v3类型相同,所以可以省略
//        //5.设置map的输出类型
//        job.setMapOutputKeyClass(Text.class);
//        job.setMapOutputValueClass(IntWritable.class);
        //6.设置reduce的输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        //7.可以设置reduceTask的个数
        job.setNumReduceTasks(2);
        //8.设置mapreduce程序的输入路径和输出路径
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //9.提交任务
        System.exit(job.waitForCompletion(true)?0:1);

Partitioner组件的应用

Partitioner简介:

1.partitioner的作用是将mapper阶段的输出key/value划分为不同的partition,每个reducer对应一个partition
2.默认情况下,partitioner先计算key的散列值(hash值),然后通过reducer个数执行取模运算:key.hashCode%(reducer个数),这样能够随机的将整个key空间平均分发给每个reducer,同时可以保证不同mapper产生相同的key能够被分发到同一个reducer
3.目的:
	可以使用自定义的Partitioner来达到reducer的负载均衡,提高效率
4.适用范围:
	需要值得注意的是:必须提前知道有多个分区,比如自定义的Partitioner会返回4个不同的int值,而ruducer number设置小于4,那么就会报错,所以我们可以通过运行分析任务来确定分区数,例如,有一堆包含时间戳的数据,但是不知道它能追溯到的时间范围,此时可以运行一个作业来计算出时间范围
注意:
	在自定义partitioner时,一定要注意防止数据倾斜

Hadoop序列化机制

序列化:
	序列化是指将具有结构化的内存对象转成0和1组成的字节序列,以便进行网络传输和持久化存储到设备的过程
反序列化:
	将字节序列转为内存中具有结构化的对象的过程
序列化的应用:
	1.网络传输(进程通信)
	2.永久存储

Hadoop和Java序列化过程的比较:

1.Java序列化是一个重量级的序列化框架(Serilizable),一个对象被序列化之后,会附带很多的额外信息(各种校验信息,header,继承体系...),这些数据我们并不需要,也不便于在网络中进行高效传输
2.由于Hadoop在集群之间进行通讯或者RPC的时候,数据的序列化要快,体积要小,占用带宽要小的需求,因为Hadoop专门研发了一套精简高效的序列化机制(Writable),此序列化机制拥有紧凑,快速,可扩展,互操作的特点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VFwhUGN-1626074933647)(C:\Users\16326\Desktop\dayHomework\QQ截图20210703172757.png)]

需要注意的是:MapReduce的key和value都必须是可序列化的,而针对key而言,是数据排序的关键字,因此还需要提供WritableComparable

常用数据类型简介:

Java类型Hadoop中的Writable类型释义
booleanBooleanWritable标准布尔型数值
byteByteWritable单字节数值
intIntWritable整数数值
floatFloatWritable单精度数值
longLongWritable长整型数值
doubleDoubleWritable双精度数值
StringText使用UTF8格式存储的文本
mapMapWritable以键值对的形式存储Writable类型的数据
arrayArrayWritable以数组的形式存储Writable类型的数据
nullNullWritable当<key,value>中的key或value为null是使用
shortShortWritable短整型数值
NullWritable:
	NullWritable是Writable的一个特殊类型,它的序列化长度为0,它并不从数据流中读取数据,也不写入数据,他充当占位符。例如:在MapReduce中,如果你不需要使用键或值,就可以将键或值声明称NullWritable---结果是存储常量的空值,如果希望存储一系列的数值与键/值对应,那么NullWritable也可以用作在SequenceFile中的键,它是一个不可变的单实例类型,通过调用NullWritable.get()方法可以获取这个实例

Hadoop序列化接口—Writable

write();			//将每个对象序列化到输出流
readFields();	//把输入流字节反序列化

自定义对象实现MR的序列化接口:

使用原因:
	自定义的类型无法满足用户需求,需要用户自己定义数据类型
自定义一个类型:
	第一种方式:可以直接实现WritableComparable接口
	第二种方式:可以实现Writable和Comparable接口
	注意:如果自定义的类型,会被作为key进行传输,那么必须要实现Comparable接口,因为shuffle阶段会对key进行排序,如果不作为key使用,那只用实现Writable接口即可

MapReduce基础

MapReduce运行流程简述:

一个完整的MapReduce程序在分布式运行时有三类实例进程:
1.MRAppMaster:负责整个程序的过程调度以及状态协调
2.MapTask:负责map阶段的整个数据处理流程
3.ReduceTask:负责reduce阶段的整个数据处理流程

一个mr程序提交后,大概的流程如下:

1.一个mr程序启动的时候,会先启动一个进程application master,它的主类时MRAppMaster
2.appmaster启动之后会根据本次job的描述信息,计算出inputSplit的数据,也就是MapTask的数量
3.appmaster然后向resourcemanager来申请对应数量的container来执行mapTask进程
4.mapTask进程启动之后,根据inputSplit来进行数据处理,处理流程如下:
	4.1利用客户指定的inputformat来获取recordReader读取数据,形成kv键值对
	4.2将kv传递给客户定义的mapper类的map方法,做逻辑运算,并将map方法的输出kv收集到缓存中
	4.3将缓存中的数据按照k进行分区排序后不断地溢出到磁盘文件
5.appmaster监控maptask进程完成之后,会根据用户指定的参数来启动相应的reduceTask进程,并告诉reduceTask需要处理的数据范围
6.reduceTask启动之后,根据appmaster告知的待处理的未知数据,从若干的已经存到磁盘的数据中拿到数据,并在本地进行一个归并排序,然后再按照相同的key的kv为一组,调用客户自定义的reduce方法,并收集输出结果kv,然后按照用户指定的outputFormat将结果存储到外部设备 

运行流程之分片机制

分片的概念:

MapReduce在进行作业提交时,会预先对将要分析的原始数据进行划分处理,形成一个个等长的逻辑数据对象,称之为输入分片(inputSplit)简称”分片“,MapReduce为每一个分片构建一个单独的MapTask,并由该任务来运行用户自定义的map方法,从而处理分片中的每一条记录

分片大小选择:

1.拥有许多分片,意味着处理每个分片所需要的时间之和要小于处理整个输入数据所花的时间(分而治之的优势)
2.并行处理分片,且每个分片比较小,负载均衡,性能优越的计算机处理更快,可以腾出时间做别的任务
3.如果分片太小,管理分片的总时间和构建map任务的总时间将决定作业的整个执行时间
4.如果分片跨越两个数据块,那么分片的部分数据需要通过网络传输到map任务运行的节点,占用网络带宽,效率更低
5.综上所述,最佳分片大小应该和HDFS的块的大小一致,hadoop2.x的默认大小为128M
6.当然在切片的时候,有一个1.1倍原则,如果剩余文件在分片大小*1.1以内,我们也将其放入到一个分片中

MapTask运行流程:

1.maptask调用FileInputFormat的getRecordReader读取分片数据
2.每行数据读取一次,返回一个(k,v)对,k是offset,v是一行数据
3.将kv对交给mapTask处理
4.每对kv调用一次map(k,v,context)方法,然后context.write(k,v)
5.写出的数据交给收集器OutputCollector.colletor()处理
6.将数据写入环形缓冲区,并记录写入的起始偏移量,终止偏移量,环形缓冲区的默认大小为100M
7.默认写到80%的时候要溢写到磁盘,溢写磁盘的过程中数据继续写入剩下的20%
8.溢写磁盘之前要先进行分区然后在分区内进行排序
9.默认的分区规则是hashPartitioner,即key的hash%reduceNum
10.默认的排序规则是key的字典排序,使用的是快速排序
11.溢写会形成多个文件,在maptask读取完一个分片数据后,先将环形缓冲区数据刷写到磁盘
12.将数据多个溢写文件进行合并,分区内排序(外部排序--->归并排序)
mapTask的并行实例是否越多越好?
答:1.如果硬件配置为2*12core+64G,恰当的map并行度是大约每个结点20-100个map,最好每个map的执行时间至少1分钟
	2.如果job的每个map或者reduce task的运行时间都只有30-40秒,那么就减少该job的map和reduce数,每一个task(map|reduce)的setup和加入到调度器中进行调度,这个中间的过程可能都要花费几秒钟,所以如果每个task都很快的跑完了,就会在task的开始和结束时间浪费太多的时间
	3.配置task的JVM重用可以改善该问题:(mapred.job.reuse.jvm.num.tasks,默认是1,表示一个JVM上最多可以顺序执行的task数量(属于同一个job)是1,也就是一个task开启一个jvm)
	4.如果input的文件非常大,比如1TB,可以考虑将hdfs上的每个block size变大,比如256M乃至512M

ReduceTask运行流程:

1.数据按照分区规则发送给reducetask
2.reducetask将来自多个maptask的数据进行合并(归并排序)
3.按照key相同进行分组
4.一组数据调用一次reduce(k,iterator<v> values,context)
5.处理后的数据交给reducetask
6.reducetask调用FileOutputFormat组件
7.FileOutputFormat组件中write方法将数据写出

ReduceTask的并行度同样影响整个job的执行并发度和执行效率,但与MapTask的并发数由切片数决定不同 ,ReduceTask数量的决定可以手动设置job.setReduceTasks(4)。如果数据分布不均,就很可能在reduce阶段产生数据倾斜

注意:ReduceTask的数量并不是任意设置,还要考虑业务逻辑的要求,有些情况下,需要计算全局汇总结果,就只能有一个ReduceTask,尽量不要运行太多的ReduceTask,对于大多数job来说,最好reduce的个数最多和集群中的reduce持平或者更小,这对于小集群而言非常重要

Shuffle阶段

mapreduce会确保每个reduce的输出都是按键进行排序的,从map方法输出数据开始,到作为输入数据传给redcue方法的过程称之为shuffle,我们将学习shuffle阶段是如何工作的,因为它有助于我们理解工作机制(如果需要优化mapreduce程序),shuffle阶段属于不断被优化和改进代码库的一部分,因此会随着版本的不同,细节上可能发生变化,但不管怎样,shuffle仍然是mapreduce的心脏,是奇迹发生的地方

map端:

	每一个map任务都有一个环形内存缓冲区用于存储map的输出数据在默认情况下,缓冲区的大小为100Mb,一旦缓冲区的内容达到了阈值(默认是0.8),一个后台线程将会开始把内容溢写到磁盘里,在将数据溢写到磁盘的过程中,map的输出数据继续写到缓冲区,但如果在此期间缓冲区被填满,map会被阻塞直到写磁盘的过程完成。
	在写磁盘之前,线程会根据分区器的逻辑把数据划分成不同的分区(partitioner),然后在每个分区中,后台线程会按键在内存中进行快排,如果指定了一个combiner函数,它就在排序后的输出上运行,运行combiner函数使map输出结果更加紧凑,因此减少写到磁盘的数据和传递到reducer的数据。
	每次内存缓冲区达到阈值,就会新建一个一些文件(spill file),因此在map任务写完最后一个输出记录之后,可能会有几个溢出文件,在maprtask任务完成之前,多个溢写文件会被合并成一个已经分区并且已经排序的输出文件,默认情况下,一次最多可以合并10个文件。
	如果至少存在3个溢出文件时,则combiner就会在输出文件写到磁盘之前再次运行,combiner可以在输入上反复运行,但不影响最终结果,如果只有1或者2个溢写文件,那么由于map输出规模减少,因而不值得调用combiner产生开销,因此不会为该map输出再次运行combiner。
	为了使写磁盘的速度更快,节约磁盘空间,并且减少传给reducer的数据量,在溢写到磁盘的的过程中对数据进行压缩往往是个很好的主意,在默认情况下,输出是不压缩的,因此仅作参考

Reduce端:

	reducer通过http得到输出文件的分区。
	map输出文件位于运行maptask的本地磁盘(注意,尽管map输出经常要写到maptask的maptask本地磁盘,但是reduce输出并不是这样),现在tasktracker需要为分区文件运行reduce任务,并且reduce任务需要集群上若干个map任务的map输出作为其特殊的分区文件,每个map任务的完成时间可能不同,因此在每个任务完成时,reduce任务就开始复制其输出,这就是reduce任务的复制阶段,reduce任务有少量的复制线程,因此能够并行取得map输出,默认是5个。
	如果map输出相当小,会被复制到reduce任务JVM的内存(),否则map输出被复制到磁盘,一旦内存缓冲区达到阈值大小或者达到map输出阈值,则合并后溢出写道磁盘中,如果指定combiner,则在合并期间运行它以降低写入磁盘的数据量。
	随着磁盘中的溢写文件数量增多,后台线程会将他们合并成更大的,排序好的文件,这会为后面的文件节省一些时间,注意,为了合并,压缩的map输出(通过map任务)都必须在内存中被解压缩。
	复制完所有的map输出之后,reduce任务进入排序阶段(更确切的说法应该是:合并阶段,因为排序是在map阶段进行的),这个阶段将合并map输出,维持其顺序排序,这是循环进行的,比如,有50个map输出,而合并因子是10,那么合并将进行5趟,每趟将10个小文件合并成一个文件,因此最后有5个中间文件
	在最后的阶段,即reduce阶段,直接把数据输入reduce函数,从而省略了一次磁盘往返行程,并没有将这5个文件合并成一个已排序的文件作为最后一趟,最后的合并可以来自内存和磁盘片段

ruducer如何知道要从哪台机器上取得map输出呢?

	map任务完成之后,它们会使用心跳机制通知他们的application master,因此对于指定作业,application master知道map输出和主机位置之间的映射关系。
	reducer中的一个线程定期询问master以便获取map输出主机的位置,直到获得所有输出位置。
	由于第一个reducer可能失败,所以主机并没有在第一次reducer检索到map输出时就立即从磁盘上删除它们,相反,主机会等待直到application master告知它删除map输出,这是作业完成后执行的

Shuffle流程总结:

1.从map函数输出到reduce函数接收输入数据的过程称之为shuffle
2.map函数的输出,存储环形缓冲区(默认大小为100M,阈值为80%)
	环形缓冲区:其实是一个字节数组kvbuffer,有一个sequator标记,kv原始数据从左向右填充(顺时针)
	kvmeta是对kvbuffer的一个封装,封装成了int数组,用于存储kv原始数据的对应的元数据valstart,keystart,partition,vallen信息,从右向左(逆时针)
3.当达到阈值时,准备溢写到本地磁盘(因为是中间数据,因此没有必要将其存储到HDFS上),在溢写前要进行对元数据分区(partition)整理,然后进行排序(quick sort,通过元数据找到key,同一分区的所有key进行排序,排序完,元数据及已经有序了,在溢写时,按照元数据的顺序寻找原始数据进行溢写)
4.如果有必要,可以在排序之后,溢写前调用combiner函数进行运算,来达到减少数据的目的
5.溢写文件可能有多个,然后对这多个溢写文件进行再次合并(也要进行排序和分区),当溢写个数>=3时,可以再次调用combiner函数来减少数据,如果溢写个数<3时,默认不会调用combiner函数
6.合并的最终溢写文件可以使用压缩技术来达到节省磁盘空间的和减少向reduce阶段传输数据的目的(存储在本地磁盘中)
7.reduce阶段通过http协议抓取属于自己的分区的所有map的输出数据(默认线程数是5,因此可以并发抓取)
8.抓取到的数据存在内存中,如果数量大,当达到本地内存的阈值时会进行溢写操作,在溢写前会进行合并和排序(排序阶段),然后写到磁盘中 
9.溢写文件可能会产生多个,因此在进入reduce之前会再次合并(合并因子是10),最后一次合并要满足10这个因子,同时输入给reduce函数,而不是产生合并文件reduce函数输出数据直接存储在HDFS上
	在Hadoop这样的集群环境中,大部分的maptask和reducetask的执行是在不同的节点上,当然很多情况下reduce需要跨节点去拉去其他节点上的maptask结果,如果集群正在运行的job有很多,那么task的正常执行对集群内部的网络资源消耗会非常严重,这种网络消耗是正常的,我们不能限制,能做的就是最大化地减少不必要的消耗,还有在节点内,相较于内存,磁盘io和job完成时间地影响也是可观的,从最基本的要求来说,我们对shuffle过程的期望可以有:
	1.完整地从maptask端拉取数据到reducetask端
	2.在跨节点拉取数据时,尽可能减少对带宽的不必要消耗
	3.减少磁盘io对task执行的影响

combiner:

集群中可用带宽十分稀缺,因此在不影响结果数据的前提下,尽可能地减少磁盘IO和网络传输,是非常适合的,Hadoop允许用户针对map任务的输出指定一个combiner函数(其实是一个运行在map端的reduce函数),用于优化MR的执行效率

特点:
	1.Combiner是mr程序中mapper和redcuer之外的一个组件
	2.combiner组件的父类就是reducer
	3.combiner和reducer之间的区别在于运行位置
	4.reduce阶段的reducer是每一个接收全局的maptask所输出的结果
	5.combiner是在合并排序后运行的,因此map和reduce都能调用此函数
	6.combiner的存在就是为了提高当前网络io传输的效率,是mapreduce的一种优化手段
	7.combiner在驱动类中的设置:job.setConbinerClass(MyCombiner.class)
注意:combiner不适合做求平均值这类需求,很可能就影响了结果

,用于优化MR的执行效率

特点:
	1.Combiner是mr程序中mapper和redcuer之外的一个组件
	2.combiner组件的父类就是reducer
	3.combiner和reducer之间的区别在于运行位置
	4.reduce阶段的reducer是每一个接收全局的maptask所输出的结果
	5.combiner是在合并排序后运行的,因此map和reduce都能调用此函数
	6.combiner的存在就是为了提高当前网络io传输的效率,是mapreduce的一种优化手段
	7.combiner在驱动类中的设置:job.setConbinerClass(MyCombiner.class)
注意:combiner不适合做求平均值这类需求,很可能就影响了结果
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值