文章目录
MapRedeuce
MapReduce框架由一个主资源管理器,一个集群节点一个工作器NodeManager和每个应用程序MRAppMaster组成(请参阅
YARN体系结构指南)。应用程序通过适当的接口和/或抽象类的实现来指定输入/输出位置和供应图,并减少功能。这些
以及其他作业参数构成作业配置。然后,Hadoop 作业客户端将作业(jar /可执行文件等)和配置提交给ResourceManager,
然后由ResourceManager负责将软件/配置分发给工作人员,安排任务并对其进行监视,为工作提供状态和诊断信息,客户。
Tracker
- Job Tracker:运行在NameNode上面,接受客户端请求,初始化作业,分配作业给Task Tracker,Job Tracker 与Task Tracker之间通过心跳机制完成通信;
- Task Tracker: 接受Job Tracker的任务,定期与Job Tracker通信,执行map和reduce任务
world count mapreduce执行过程
world count 流程
shuffle阶段 数据从Map输出到Reduce输入过程
环形缓冲区==> 环状数组,超出80M溢出写入硬盘
MapReduce整个过程可以概括为
输入阶段==>map阶段==>shuffle阶段==>reduce阶段==>输出阶段
1、输入文件分片,每一片都由一个MapTask来处理
2、Map输出的中间结果会先放在内存缓冲区中,这个缓冲区的大小默认是100M,当缓冲区中的内容达到80%时(80M)会将缓冲区的内容写到磁盘上。也就是说,一个map会输出一个或者多个这样的文件,如果一个map输出的全部内容没有超过限制,那么最终也会发生这个写磁盘的操作,只不过是写几次的问题。
3、从缓冲区写到磁盘的时候,会进行分区并排序,分区指的是某个key应该进入到哪个分区,同一分区中的key会进行排序,如果定义了Combiner的话,也会进行combine操作
4、如果一个map产生的中间结果存放到多个文件,那么这些文件最终会合并成一个文件,这个合并过程不会改变分区数量,只会减少文件数量。例如,假设分了3个区,4个文件,那么最终会合并成1个文件,3个区
5、以上只是一个map的输出,接下来进入reduce阶段
6、每个reducer对应一个ReduceTask,在真正开始reduce之前,先要从分区中抓取数据
7、相同的分区的数据会进入同一个reduce。这一步中会从所有map输出中抓取某一分区的数据,在抓取的过程中伴随着排序、合并。
8、reduce输出
二、Yarn资源调度
Apache Hadoop YARN (Yet Another Resource Negotiator,另一种资源协调者)是一种新的 Hadoop 资源管理器,
它是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了巨大好处
Hadoop处理文件
Key&Value类型
必须可序列化(serializable)
作用:网络传输以及持久化存储
IntWritable、LongWriteable、FloatWritable、Text、DoubleWritable, BooleanWritable、NullWritable等
都继承了Writable接口
并实现write()和readFields()方法
Keys必须实现WritableComparable接口
Reduce阶段需要sort
keys需要可比较
序列化的两个作用
(1)进程间通信 (2)数据持久化存储
两个进程间进行通信时,无论是发送何种类型的数据,都以二进制序列形式在网络上传送。所以发送方必须将数据对象(比如Java对象)转化为字节序列,然后接收方则需要将接收到的字节序列再还原成Java对象。
同样,我们保存在磁盘中的数据也是二进制数据,当内存对象太多,此时就可以将对象序列化为字节序列,永久保存至磁盘。当需要再次使用时,又可以从磁盘中反序列化成内存对象。
在Hadoop中,序列化机制是一个核心功能。无论是存储文件还是在计算中传输数据,都需要执行序列化过程。
因此,序列化与反序列化的速度、序列化后的数据大小都会直接影响数据传输的速度与计算效率。正是出于此原因,Hadoop并没有采用Java提供的序列化机制,而是重新写一套,核心为org.apache.hadoop.io.Writable接口,相比于JDK的序列化来说更加简洁、更节省存储空间。
任何Hadoop数据类型都实现了Writable接口,比如:文本Text、整型IntWritable、布尔型BooleanWritable等等。
核心功能描述
- Mapper
Mapper将输入键值对(key/value pair)映射到一组中间格式的键值对集合。
Map是一类将输入记录集转换为中间格式记录集的独立任务。 这种转换的中间格式记录集不需要与输入记录集的类型一致。一个给定的输入键值对可以映射成0个或多个输出键值对。
Hadoop Map/Reduce框架为每一个InputSplit产生一个map任务,而每个InputSplit是由该作业的InputFormat产生的
需要多少个Map?
Map的数目通常是由输入数据的大小决定的,一般就是所有输入文件的总块(block)数。
Map正常的并行规模大致是每个节点(node)大约10到100个map,对于CPU 消耗较小的map任务可以设到300个左右。由于每个任务初始化需要一定的时间,因此,比较合理的情况是map执行的时间至少超过1分钟。
这样,如果你输入10TB的数据,每个块(block)的大小是128MB,你将需要大约82,000个map来完成任务,除非使用 setNumMapTasks(int)(注意:这里仅仅是对框架进行了一个提示(hint),实际决定因素见这里)将这个数值设置得更高。
- Reducer
Reducer将与一个key关联的一组中间数值集归约(reduce)为一个更小的数值集。
用户可以通过 JobConf.setNumReduceTasks(int)设定一个作业中reduce任务的数目。
概括地说,对Reducer的实现者需要重写 JobConfigurable.configure(JobConf)方法,这个方法需要传递一个JobConf参数,目的是完成Reducer的初始化工作。然后,框架为成组的输入数据中的每个<key, (list of values)>对调用一次 reduce(WritableComparable, Iterator, OutputCollector, Reporter)方法。之后,应用程序可以通过重写Closeable.close()来执行相应的清理工作。
Reducer有3个主要阶段:shuffle、sort和reduce。
Shuffle
Reducer的输入就是Mapper已经排好序的输出。在这个阶段,框架通过HTTP为每个Reducer获得所有Mapper输出中与之相关的分块。
Sort
这个阶段,框架将按照key的值对Reducer的输入进行分组 (因为不同mapper的输出中可能会有相同的key)。
Shuffle和Sort两个阶段是同时进行的;map的输出也是一边被取回一边被合并的。
Reduce
在这个阶段,框架为已分组的输入数据中的每个 <key, (list of values)>对调用一次 reduce(WritableComparable, Iterator, OutputCollector, Reporter)方法。
Reduce任务的输出通常是通过调用 OutputCollector.collect(WritableComparable, Writable)写入 文件系统的。
应用程序可以使用Reporter报告进度,设定应用程序级别的状态消息,更新Counters(计数器),或者仅是表明自己运行正常。
Reducer的输出是没有排序的
- 作业配置Job Conf
代码实现
pom.xml
<hadoop.version>2.6.0</hadoop.version>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>${hadoop.version}</version>
</dependency>
Mapper类
/**
* 参数:
* LongWritable 第几行的行号
* Text 每一行读出的数据 因为 string不能改变大小 拼接序列化比较麻烦(比如拼接int) 所以新写入的一个类,底层是StringBuffer
* Text 最后输出的每一个 key
* IntWritable 输出的每一个key的个数
*/
public class WCMapper extends Mapper<LongWritable,Text,Text, IntWritable> {
private Text out = new Text();
//每个单词传输过来的值都为 1
private IntWritable one = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context ctx) throws IOException, InterruptedException {
//分割单词
String[] split = value.toString().split(" ");
for (String word : split) {
out.set(word);//转成 text 输出
ctx.write(out,one);//每个词都是1
}
}
}
OutPutCollector 将收集到的(k,v)写入到环形缓冲区,然后由缓冲区写到磁盘上。默认的缓冲区大小是100M,溢出的百分比是0.8,也就是说当缓冲区中达到80M的时候就会往磁盘上写。如果map计算完成后的中间结果没有达到80M,最终也是要写到磁盘上的,因为它最终还是要形成文件。
在spill溢出前,会对数据进行分区和排序,即在缓冲区对每个(k,v)键值对hash一个partition值,值相同则在同一个区,同一分区根据key来排序。不同分区在缓冲区根据partition和key值进行排序。
多个溢出的文件再通过merge合并,采用归并排序,合并成一个大的分区且区内有序的溢出文件。
reduce task 根据自己的分区号,到各自的map task节点拷贝相同的partition的数据到reduce task磁盘工作目录,再通过merge归并排序成一个有序的大文件。
Reducer类
/**
* 参数:
* Text :传输过来的key
* IntWritable : 传输过来的个数
* Text : 统计的每一个key
* IntWritable : 统计好的个数
*/
public class WCReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
//分割后传来的key,每个key的值(按上一组的组数),传出去
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable num : values) {
sum += num.get();
}
context.write(key, new IntWritable(sum));
}
}
Job
public class MyDemo {
public static void main(String[] args) throws Exception {
//使用计算框架
Job job = Job.getInstance(new Configuration());
// jar包开始的类
job.setJarByClass(MyDemo.class);
job.setJobName("wc"); //取个名字
//读文件 并用inputsplit的getSplit方法对文件进行分割生成逻辑区 根据逻辑区的大小开启对应数量的Map Task
FileInputFormat.addInputPath(job, new Path("hdfs://192.168.56.171:9000/data1/data.txt"));
//结果写入文件
FileOutputFormat.setOutputPath(job, new Path("hdfs://192.168.56.171:9000/my1"));
//设置对应的Mapper类
job.setMapperClass(WCMapper.class);
//设置Mapper类的输出的key和value的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//设置对应的Reduce类
job.setReducerClass(WCReduce.class);
//设置Reduce类的输出key和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//启动job
job.waitForCompletion(true);
}
}