【Hadoop】(三) Hadoop计算框架 MapReduce

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);
    }
}
发布了27 篇原创文章 · 获赞 19 · 访问量 1280

猜你喜欢

转载自blog.csdn.net/weixin_42804692/article/details/103452442