Hadoop学习回顾之MapReduce

MapReduce是批处理的计算框架,分治思想非常朴素,但是实现确实要注意很多细节,同时编程框架个人觉得WordCount部分与storm有相似之处,可以类比学习。

MapReduce过程

一个MapReduce作业经过了input、map、combine、reduce、output五个阶段,其中combine不一定发生。

如下图,在MRv1的架构中,MR job作业,是用户提交的最小单位,Map/Reduce任务是MapReduce计算的最小单位。JobTracker会把作业解析成不同的任务,交给各个TaskTracker执行,记录与反馈。

Input

最常见的情况下,大部分数据都以HDFS为存储引擎,以HDFS文件作为MapReduce的输入,会被org.apache.hadoop.mapreduce.InputFormat类的子类FileInputFormat类将输入的HDFS文件切分形成分片(InputSplit),分片将作为一个Map任务的输入,再将InputSplit解析为键值对,InputSplit的大小和数量对MapReduce的作业性能有非常大的影响。

InputSPlit的大小,由return Math.max(minSize,Math.min(maxSize,blockSize))决定,此方法等价于return Math.max(mapred.min.split.size,Math.min(mapreduce.max.split.size,dfs.block.size)),此处默认设置mapred.min.split.size=1,mapreduce.max.split.size是一个接近20位长度的大数(Byte为单位),进一步简化,可以推出在默认条件下InputSplit=dfs.block.size,一般为64MB或128MB。在实验环境中,dfs.block.size=128MB,进行1TB的Terasort任务,每行100B大小,配置10^10行,map数应该大致等于10^10*100B/128*1024*1024B,大约7450,实际运行结果为7452,非常接近。

InputSplit只是一个逻辑概念,并不会把实际的磁盘文件进行分片存储,会记录分片的元数据信息,例如文件路径、文件开始位置、文件结束位置、数据所在节点。另外,可以通过调整参数的方法使得InputSplit的大小大于blocksize或者小于blocksize。但是会有一定的缺点,例如当InputSplit的大小大于block时,由于两个block不一定在同一个DataNode上,数据本地性无法保证。所以通常不以修改mapred.min.split.size和mapreduce.max.split.size的值的方式来修改InputSplit的大小。

在文件切分为InputSplit后,FileInputFormat会有子类将InputSplit解析为键值对(即用户自定义的map函数的输入),在WordCount场景的泛型下为<K,V>,使用时实参为<LongWritable,Text>,代表实际意义为<文本行号,行号对应的文本内容>。

Map与中间结果的输出

每个Map任务都会有一个buffer,内存中的环形缓冲区,用于存储map函数的输出,buffer可以在配置项中配置其大小与阈值,buffer达到阈值时,后台线程会将buffer中的数据分成相应的分区,在每个分区中先进行一次快速排序,快排完成后一个后台线程会将缓冲区的内容spill到磁盘,即溢写。

由以上叙述可知,Map任务写完最后一个输出记录之后,会有n个溢写文件。这些文件已经进行了分区,排序,且大小和buffer一致。由于已经经过第一次快排,直接合并排序,可以生成一个整体的分区有序文件。该文件为map输出的中间结果,这也是单个map任务的输出结果。

Map-reduce中间的性能优化

Hadoop性能很大程度受限于网络带宽,而Reduce通过HTTP获取map输出,那么减少中间结果的数据量就可以提高磁盘I/O性能(运行Map任务与Reduce任务的节点很可能不是同一个节点),提升程序运行的效率

Combine

Combine过程发生在map和reduce之间,可以理解为其将单个map任务的输出先进行了一次reduce,将键值对中相同键的结果进行了一层合并。

压缩

压缩map输出,提高磁盘I/O性能。

Shuffle

Shuffle:数据混洗,代表map函数产生输出到reduce的消化输入的整个过程。

Map任务的输出结果位于运行Map任务的NodeManager所在的本地磁盘上。NodeManager需要为这些分区文件运行Reduce任务,但是Reduce任务可能需要多个map任务的输出以作为特殊的分区文件。每个Map任务完成的时间不同,只要有一个Map任务完成,Reduce任务就开始复制其输出,这就是上图的shuffle的copy阶段的fetch操作。Reduce有少量复制线程,可以并行获取Map任务的输出,默认值为5个线程。

Reduce的读buffer/输入buffer和Map的输出buffer非常类似。

复制完所有的map输出,shuffle将合并map的输出文件,并维持其顺序排序,其实做的是归并排序,排序过程循环进行。将所有键相同的数据交给Reduce处理,处理完成后,结果输出到hdfs上。

MapReduce之Helloworld—WordCount的简单设计

WordCount词频统计的程序,在MR、Storm、Spark中的地位,都可以类比helloworld,是入门小程序。简单介绍一下词频统计的实现。

整体的编程框架或者说模板与上图一致,

  1. 可以将setOutputFormatClass换成setOutputKeyClass和setOutputValueClass,setInputFormatClass同理
  2. 利用FileInput.Format.addInputPath方法来设置输入路径,FileOutputFormat.setOutputPath来指定输出路径。
  3. 需要加上提交任务的语句,System.exit(job.waitForCompletion(true) ? 0 : 1);否则Linux终端不会打印对应的执行进度。

设计基础知识

序列化

序列化(serialization)是指将结构化的对象转化为字节流(字节数组Byte[],以便在网络上传输或者写入到硬盘进行永久存储;相对的反序列化(deserialization)是指将字节流转回到结构化对象的过程。

 

在分布式系统中进程将对象序列化为字节流,通过网络传输到另一进程,另一进程接收到字节流,通过反序列化转回到结构化对象,以达到进程间通信。在Hadoop中,Mapper,Combiner,Reducer等阶段之间的通信都需要使用序列化与反序列化技术。举例来说,Mapper产生的中间结果(<key: value1, value2...>)需要写入到本地硬盘,这是序列化过程(将结构化对象转化为字节流,并写入硬盘),Reducer阶段读取Mapper的中间结果的过程则是一个反序列化过程(读取硬盘上存储的字节流文件,并转回为结构化对象),需要注意的是,能够在网络上传输的只能是字节流,Mapper的中间结果在不同主机间洗牌时,对象将经历序列化和反序列化两个过程。

序列化是Hadoop核心的一部分,在Hadoop中,位于org.apache.hadoop.io包中的Writable接口是Hadoop序列化格式的实现

Writable接口

Hadoop中,Writable接口定义了两个方法:

void write(DataOutput out) throws IOException;用户将其状态写入二进制格式的DataOutput流。

void readFields(DataInput in) throws IOException;用于从二进制格式的DataInput流读取其状态

有兴趣可以看看不同的Writable类下这两个接口方法的具体实现,印象中有和0xff做按位与的操作,原补反,计算机存的补码,为避免类似于负数补码高位补1的问题出现。

Java类与Hadoop类的相互转化

由于有序列化的要求,所以在写MR程序时不能使用Java的基本类型,而Hadoop对java的基本类型和string类型做了封装,string不是java的基本类型,java基本类型四整int、byte、short、long,两浮float、double,一布尔boolean,一字符char。

Java(基本)类型

Hadoop封装类型

boolean

BooleanWritable

byte

ByteWritable

int

IntWritable

float

FloatWritable

long

LongWritable

double

DoubleWritable

string

Text

同时转换关系如下:

在WordCount例子中不需要自己去实现Writable接口构造新的Writable类,即不需要重写序列化与反序列化方法,只要用好Hadoop封装类型即可。

 

代码设计

先明确一下WordCount整体的KV的变化,map input:<line_number,line_text>--->map output(reduce input)<word,count>--->reduce out<word,count>

最初的FileInputFormat调用时,已经切分成了InputSplit分片,同时利用子类做了处理,已得到map的输入<line_number,line_text>,mapper类在此基础上进行设计。代码不便传出,可以参看别的博客。

https://www.cnblogs.com/ahu-lichang/p/6645074.html

参考:https://www.cnblogs.com/ahu-lichang/p/6645074.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值