MapReduce分布式计算

一、MapReduce分布式计算

1、MapReduce计算模型介绍

1.1理解MapReduce思想

​ MapReduce思想在生活中处处可见。或多或少都成接触过这种思想。MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。即使是发布过论文实现分布式计算的谷歌也只是实现了这种思想,而不是自己原创。

Map负责"分",即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。

Reduce负责“合”,即对map阶段的结果进行全局汇总。

​ 这两个阶段合起来正是MapReduce思想的体现。

image-20210303214051906

​ 图:MapReduce思想模型

还有一个比较形象的语言解释MapReduce:

我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是“Map”。我们人越多,数书就更快。

现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。

image-20210303214249556

1.2Hadoop MapReduce设计构思

​ MapReduce是一个分布式运算程序的编程框架,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。

​ 既然是做计算的框架,那么表现形式就是有个输入(input),MapReduce操作这个输入(input),通常本身定义好的计算模型,得到一个输出(output)。

​ 对许多开发者来说,自己完完全全实现一个并行计算程序难度太大,而MapReduce就是一种简化并行计算的编程模型,降低了开发并行应用的入门门槛、

Hadoop MapReduce构思体现在如下的三个方面:

  • 如何对付大数据处理:分而治之

    对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略。并行计算的第一个重要问题是如何划分计算任务或者计算数据以便对划分的子任务或数据块同时进行计算。不可分拆的计算任务或相互间有依赖关系的数据无法进行并行计算!

  • 构建抽象模型:Map和Reduce

    MapReduce借鉴了函数式语言中的思想,用map和Reduce两个函数提供了高层的并行编程抽象模型。

    Map:对一组数据元素进行某种重复式的处理;

    Reduce:对Map的中间结果进行某种进一步的结果整理。

    MapReduce中定义了如下的Map和Reduce

    map: (k1; v1) → [(k2; v2)]

    reduce: (k2; [v2]) → [(k3; v3)]

Map和Reduce为程序员提供了一个清晰的操作接口抽象描述。通过以上两个编程接口,大家可以看出MapReduce处理的数据类型是<key,value>键值对。

  • 统一构架,隐藏系统层细节

    ​ 如何提供统一的计算框架,如果没有统一封装底层细节,那么程序员则需要考虑诸如数据存储、划分、分发、结果收集、错误恢复等诸多细节;为此,MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节。

    ​ 如何提供统一的计算框架,如果没有统一封装底层细节,那么程序员则需要考虑诸如数据存储、划分、分发、结果收集、错误恢复等诸多细节;为此,MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节。

image-20210304141021953

2、MapReduce编程规范及示例编写

2.1编程规范

MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为2个步骤,Shuffle 阶段 4 个步骤,Reduce 阶段分为2个步骤

Map阶段2个步骤

1、设置InputFormat类,读取输入文件内容,对输入文件的每一行,解析成key、value对(K1和V1)

2、自定义map方法,对一个键值对调用一次map方法,将第一步的K1和V1结果转换成另外的Key-Value(K2和V2)对,输出结果。

image-20210304141553829

Shuffle阶段4个步骤

3、对map阶段输出的k2和v2对进行分区

4、对不同分区的数据按照相同的key排序

5、(可选)对数据进行局部聚合,降低数据的网络拷贝

6、对数据进行分组,相同key的Value放入一个集合中,得到K2和[V2]

image-20210304143356709

Reduce 阶段 2 个步骤

7、对map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。
8、对多个map任务的输出进行合并、排序。编写reduce方法,在此方法中将K2和[V2]进行处理,转换成新的key、value(K3和V3)输出,并把reduce的输出保存到文件中。

image-20210304144313037

2.2编程步骤:

​ 用户编写的程序分为三个部分:Mapper,reducer,Driver(提交运行mr程序的客户端)

Mapper

(1) 自定义类继承Mapper类

(2) 重写自定义类中的map方法,在该方法中将K1和V1转为K2和V2

(3) 将生成的K2和V2写入上下文中

Reducer

(1) 自定义类继承Reducer类

(2) 重写Reducer中的reduce方法,在该方法中将K2和[V2]转为K3和V3

(3) 将K3和V3写入上下文中

Driver

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

(1)定义类,编写main方法

(2)在main方法中指定以下内容:

1、创建一个job任务对象

2、指定job所在的jar包

3、指定源文件的读取方式类和源文件的读取路径

4、指定自定义的Mapper类和K2、V2类型

5、指定自定义分区类(如果有的话)

6、指定自定义分组类(如果有的话)

7、定义自定义的Reduce类和K3、v3的数据类型

8、指定输出方式类和结果输出路径

9、将job提交到yarn集群

2.3wordCount示例编写

image-20210304145527435

需求:在一堆给定的文本文件中统计输出每一个单词出现的总次数

第一步:数据准备

1、创建一个新的文件

cd /export/server
vim wordcount.txt

2、向其中放入以下内容并保

hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop

3、上传到HDFS

hdfs dfs -p -mkdir /input/wordcount
hdfs dfs -put wordcount.txt /input/wordcount

第四部:导入相关依赖

<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-mapreduce-client-core</artifactId>
        <version>2.7.5</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!--    <verbal>true</verbal>-->
            </configuration>
        </plugin>
     <!--
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.4.3</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <minimizeJar>true</minimizeJar>
                    </configuration>
                </execution>
            </executions>
        </plugin>
-->
    </plugins>
</build>

第三步:代码编写

(1)定义一个mapper类

//首先要定义四个泛型的类型
//keyin:  LongWritable    valuein: Text
//keyout: Text            valueout:IntWritable
public class WordCountMapper exports Mapper<LongWritable,Text,Text,Writable>{
    //map方法的生命周期:  框架每传一行数据就被调用一次
	//key :  这一行的起始点在文件中的偏移量
	//value: 这一行的内容
	@Override
    	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
		//拿到一行数据转换为string
		String line = value.toString();
		//将这一行切分出各个单词
		String[] words = line.split(" ");
		//遍历数组,输出<单词,1>
		for(String word:words){
			context.write(new Text(word), new LongWritable (1));
		}
	}
}
}

(2)定义一个reduce类

public class WordCountReducer extends Reducer<Text,LongWritable,Text,LongWritable> {
    //生命周期:框架每传递进来一个kv 组,reduce方法被调用一次
     @Override
    protected void reduce(Text key, Iterable<LongWritable > values, Context context) throws IOException, InterruptedException {
        //定义一个计数器
        int count=0//遍历这个一组kv的所有v,累加到count中
           	for(LongWritable value:values){
		count += value.get();
	}
       context.write(key, new LongWritable (count));
 }
}  

(3)定义一个Driver主类,用来描述job并提交job

public class WordCountRunner{

    //该方法用于指定一个job任务

    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        
        //1:创建一个job任务对象,并指定job的名字
        Job job = Job.getInstance(conf, "wordcount");
        
        //2:指定job所在的jar包
        job.setJarByClass(WordCountRunner.class);
    
        //3:指定源文件的读取方式类和源文件的读取路径
        job.setInputFormatClass(TextInputFormat.class);
        TextInputFormat.addInputPath(job, new Path("file:///D:\\input\\wordcount"));


        //4:指定自定义的Mapper类和K2、V2类型
        job.setMapperClass(WordCountMapper.class);
		//设置K2类型
        job.setMapOutputKeyClass(Text.class);
		//设置V2类型
        job.setMapOutputValueClass(LongWritable.class);


        //分区和分组采用默认方式

        //5:指定自定义的Reducer类和K3、V3的数据类型
        job.setReducerClass(WordCountReducer.class);
        //设置K3的类型
        job.setOutputKeyClass(Text.class);
        //设置V3的类型
        job.setOutputValueClass(LongWritable.class);

        //6:指定输出方式类和结果输出路径
        job.setOutputFormatClass(TextOutputFormat.class);
        //设置输出的路径
        TextOutputFormat.setOutputPath(job, new Path("file:///D:\\output\\wordcount"));

    
        //7:将job提交给yarn集群
        boolean bl = job.waitForCompletion(true);

        System.exit(bl?0:1);
    }
}

3、MapReduce程序运行模式

3.1本地运行模式

(1)mapreduce程序是被提交给LocalJobRunner在本地以单进程的形式运行

(2)而处理的数据及输出结果可以在本地文件系统,也可以在hdfs上

(3)本地模式非常便于进行业务逻辑的调试

3.2集群运行模式

(1)将mapreduce程序提交给yarn集群,分发到很多的节点上并发执行

(2)处理的数据和输出结果应该位于hdfs文件系统

(3)提交集群的实现步骤:

1、将Driver主类代码中的输入路径和输出路径修改为HDFS路径

TextInputFormat.addInputPath(job, new Path("hdfs://node1:8020/input/wordcount"));
TextOutputFormat.setOutputPath(job, new Path("hdfs://node1:8020/output/wordcount"));

2、将程序打成JAR包,然后在集群的任意一个节点上用hadoop命令启动

hadoop jar wordcount.jar cn.itcast.WordCountDriver

4、深入MapReduce

4.1MapRecue的输入和输出

MapReduce框架运转在**<key,value>****键值对**上,也就是说,框架把作业的输入看成是一组<key,value>键值对,同样也产生一组<key,value>键值对作为作业的输出,这两组键值对可能是不同的。

一个MapReduce作业的输入和输出类型如下图所示:可以看出在整个标准的流程中,会有三组<key,value>键值对类型的存在。

image-20210304154153649

4.2MapReduce的处理流程解析

image-20210304155325951

4.3Mapper任务执行过程详解

  • 第一阶段是输入目录下文件按照一定的标准逐个进行逻辑切片,形成切片规划。默认情况下,Split size = Block size。每一个切片由一个MapTask处理。

image-20210304155637122

  • 第二阶段是对切片中的数据按照一定的规则解析成<key,value>对。默认规则是把每一行文本内容解析成键值对。key是每一行的起始位置(单位是字节),value是本行的文本内容。

(TextInputFormat)

image-20210304155810143

  • 第三阶段是调用Mapper类中的map方法。上阶段中每解析出来的一个<k,v>,调用一次map方法。没次调用map方法会输出零个或多个键值对。

  • 第四阶段是按照一定的规则对第三阶段输出的键值对进行分区。默认是只有一个分区。分区的数量是Reducer任务运行的数量。默认只有一个Reducer任务。

  • 第五阶段是对每个分区中的键值对进行排序。首先,按照键进行排序,对于键相同的键值对,按照值进行排序。比如三个键值对<2,2>、<1,3>、<2,1>,键和值分别是整数。那么排序后的结果是<1,3>、<2,1>、<2,2>。如果有第六阶段,那么进入第六阶段;如果没有,直接输出文件。

  • 第六阶段是对数据进行局部聚合处理,也就是conbiner处理,键相等的键值对会调用一次reduce方法。经过这一阶段,数据量会减少。本阶段默认是没有的。

4.4Reduce任务执行过程详解

  • 第一阶段是Reduce任务会主动从Mapper任务复制其输出的键值对。Mapper任务可能会有很多,因此Reducer会复制多个Mapper的输出。
  • 第二阶段是把复制到Reduce本地数据,全部进行合并,即把分散的数据合并成一个大的数据。再对合并后的数据排序。
  • 第三阶段是对排序后的键值对调用reduce方法。键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对。最后把这些输出的键值对写入到HDFS文件中。

在整个MapReduce程序的开发过程中,我们最大的工作量是覆盖map方法和覆盖reduce方法。

5.MapReduce分区

5.1分区概述

​ 在MapReduce中,通过我们指定分区, 会将同一个分区的数据发送到同一个Reduce当中进行处理。例如:为了数据的统计,可以把一批类似的数据发送到同一个Reduce当中,在用一个Reduce当中统计相同数据的数据,就可以实现类似的数据分区和统计等

​ 其实就是相同类型的数据,有共性的数据,送到一起去处理,在Redcuce过程中,可以根据实际需求(比如按某个维度进行归档,类似于数据库的分组),把Map完的数据Reduce到不同的文件中。分区的设置需要与ReduceTaskNum配合使用。比如想要得到5个分区的数据结果。那么就得设置5个ReduceTask。

需求:将以下数据进行分开处理

详细数据参见partition.csv 这个文本文件,其中第五个字段表示开奖结果数值,现在需求将15以上的结果以及15以下的结果进行分开成两个文件进行保存

image-20210305231249905

5.2分区步骤

1.定义Mapper

这个Mapper程序不做任何逻辑,也不对Key-Value做任何改变,只是接收数据,然后往下发送

public class MyMapper extends Mapper<LongWritable,Text,Text,NullWritable>{
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        context.write(value,NullWritable.get());
    }
}
2.自定义Partitioner

主要的逻辑就在这里,这也是这个案例的意义,通过Partitioner 将数据分发给不同的Reducer

/**
 * 这里的输入类型与我们map阶段的输出类型相同
 */
public class MyPartitioner extends Partitioner<Text,NullWritable>{
       /**
     * 返回值表示我们的数据要去到哪个分区
     * 返回值只是一个分区的标记,标记所有相同的数据去到指定的分区
     */
    @Override
	public int getPartition(Text text, NullWritable nullWritable, int i){
        String result=text.toString().split("\t")[5];
        if(Integer.parseInt(result)>15){
            return 1;
        }else{
           return 0;
        }
    }
}
3、定义Reducer逻辑

这个Reducer也不做任何处理,将数据原封不动的输出即可

public class MyReducer extends Reducer<Text,NullWritable,Text,NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,NullWritable.get());
    }
}
4.主类中设置分区类和ReduceTask个数
public class PartitionRunner  extends Configured implements Tool {
    public static void main(String[] args) throws  Exception{
        //1:创建job任务对象
         Job job = Job.getInstance(super.getConf(), "partition_maperduce");
		 
		 //2:指定job所在的jar包
        job.setJarByClass(PartitionRunner.class);

        //3:指定源文件的读取方式类和源文件的读取路径
		job.setInputFormatClass(TextInputFormat.class);
		TextInputFormat.addInputPath(job, new Path("file:///D:\\input\\partition"));
		
		 //4:指定自定义的Mapper类和K2、V2类型
		job.setMapperClass(PartitionMapper.class);
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(NullWritable.class);

		//5:指定分区类
		job.setPartitionerClass(MyPartitioner.class);

		//6:指定自定义的Reducer类和K3、V3的数据类型
		job.setReducerClass(PartitionerReducer.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(NullWritable.class);

		//7、设置ReduceTask的个数
		job.setNumReduceTasks(2);

		//8、指定输出方式类和结果输出路径
		job.setOutputFormatClass(TextOutputFormat.class);
		TextOutputFormat.setOutputPath(job, new Path("file:///D:\\out\\partition_out6"));
		
        //将job提交给yarn集群
        boolean bl = job.waitForCompletion(true);
        System.exit(bl?0:1);
    }
   
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

章鱼哥TuNan&Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值