MapReduce是一种编程模型,用于大规模数据集的并行运算,是用户开发“基于hadoop的数据分析应用”的核心框架
Mapreduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个 hadoop 集群上。
-
MapReduce易于编程 。它简单的实现一些接口,就可以完成一个分布式程序,这个 分布式程序可以分布到大量廉价的 PC 机器上运行。也就是说你写一个分布式程序,跟写一 个简单的串行程序是一模一样的。就是因为这个特点使得 MapReduce 编程变得非常流行。
-
良好的扩展性。当你的计算资源不能得到满足的时候,你可以通过简单的增加机器 来扩展它的计算能力。
-
高容错性。MapReduce 设计的初衷就是使程序能够部署在廉价的 PC 机器上,这就 要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一 个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由 Hadoop 内部完成的。
-
适合PB级以上海量数据的离线处理。
MapReduce 不擅长做实时计算、流式计算、DAG(有向图)计算。
- 实时计算:MapReduce 无法像 Mysql 一样,在毫秒或者秒级内返回结果。
- 流式计算:流式计算的输入数据是动态的,而 MapReduce 的输入数据集是静态的, 不能动态变化。这是因为 MapReduce 自身的设计特点决定了数据源必须是静态的。
- DAG(有向图)计算:多个应用程序存在依赖关系,后一个应用程序的输入为前一 个的输出。在这种情况下,MapReduce 并不是不能做,而是使用后,每个 MapReduce 作业 的输出结果都会写入到磁盘,会造成大量的磁盘 IO,导致性能非常的低下。
用户编写的程序分为三个部分:Mapper、Reducer、Driver
-
Mapper阶段
-
用户自定义的Mapper类需要继承Hadoop提供的Mapper父类,并重写其map()方法
-
Mapper的输入数据是KV对的形式
-
Mapper的业务逻辑写在map()方法中
-
Mapper的输出数据是KV对的形式
-
map()方法对每一个<K,V>调用一次
-
-
Reducer阶段
-
用户自定义的Reducer类需要继承Hadoop提供的Reducer父类,并重写其reduce()方法
-
Reducer的输入数据类型对应Mapper的输出数据类型
-
Reducer的业务逻辑写在reduce()方法中
-
ReducerTask进程对每一组相同k的<k,v>组调用一次reduce()方法
-
-
Driver阶段
整个MapReduce程序需要一个Driver来进行提交任务,提交的是一个描述了各种必要信息的job对象。
一般来说,"活的"对象只生存在内存里,关机断电就没有了。而且“活的”对象只能 由本地的进程使用,不能被发送到网络上的另外一台计算机。然而序列化可以存储"活的" 对象,可以将"活的"对象发送到远程计算机。
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输。
反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。
Java 的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带 很多额外的信息(各种校验信息,header,继承体系等),不便于在网络中高效传输。所以, hadoop 自己开发了一套序列化机制(Writable),精简、高效。
Java类型 | Hadoop类型 |
boolean | BooleanWritable |
byte | ByteWritable |
int | IntWritable |
float | FloatWritable |
long | LongWritable |
double | DoubleWritable |
string | Text |
map | MapWritable |
array | ArrayWritable |
步骤:
-
提供空参构造函数(hadoop反序列化创建对象时调用)
public SortBean() { super(); }
-
实现Writable接口,重写序列化和反序列化方法
//此处有个注意事项:序列化与反序列化时对对象属性的读写顺序要保持一致 @Override public void write(DataOutput dataOutput) throws IOException { dataOutput.writeUTF(name); dataOutput.writeInt(age); dataOutput.writeInt(height); } @Override public void readFields(DataInput dataInput) throws IOException { name=dataInput.readUTF(); age=dataInput.readInt(); height=dataInput.readInt(); }
-
重写toString()方法
@Override public String toString() { return name + '\t' + age+'\t' + height; }
window下如果想本地运行hadoop程序,需要配置HADOOP_HOME。首先我们下载 hadoop-common-2.7.1-bin-master 并解压。然后添加环境变量,如下(这里我们需要注意一下:我们配置的环境变量里面的hadoop的版本是2.7,所以我们开发时使用的hadoop的jar包也必须是2.7的):
修改Path环境变量 如下:
经过上述步骤之后,我们就能愉快的在本地进行MapReduce程序调试了。。。至于Mac系统如何本地运行MR程序,请自行百度。。。
一般来说,我们用到MapReduce最多的就是统计、排序、分区这三个知识点了,,下面给出三个案例来学习。
在码代码前,必要的工作还是先得提起说的。。首先我们得引入MapReduce开发所需要的jar包
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
本地开发时,还需导入log4j的配置文件,才能看到MapReduce程序执行的情况。
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
这样子我们的准备工作就已经做完了。。正式开始码代码
-
自定义的Mapper
package com.yunhui.mapreduce.wordcount; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-18 17:57 */ public class WordCountMapper extends Mapper<LongWritable,Text,Text,IntWritable> { Text k=new Text(); IntWritable v=new IntWritable(1); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line=value.toString(); String [] strs=line.split(","); for(String str:strs){ k.set(str); context.write(k,v); } } }
-
自定义的Reducer
package com.yunhui.mapreduce.wordcount; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-18 18:00 */ public class WordCountReduce extends Reducer<Text,IntWritable,Text,LongWritable> { @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { long count=0L; for(IntWritable intWritable:values){ count+=intWritable.get(); } context.write(key,new LongWritable(count)); } }
-
Driver
package com.yunhui.mapreduce.wordcount; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-18 18:02 */ public class WordCountDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //创建job Configuration configuration=new Configuration(); Job job= Job.getInstance(configuration); //设置jar job.setJarByClass(WordCountDriver.class); //设置Map类 job.setMapperClass(WordCountMapper.class); //设置Reducer类 job.setReducerClass(WordCountReduce.class); //设置Map的输出K V类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //设置Reducer的输出K V类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(LongWritable.class); //设置输入输出路径 FileInputFormat.setInputPaths(job,new Path(args[0])); FileOutputFormat.setOutputPath(job,new Path(args[1])); //提交job 为true代表任务执行成功 为false代表执行失败 boolean result = job.waitForCompletion(true); System.exit(result?0:1); } }
-
自定义bean 作为key
package com.yunhui.mapreduce.sort; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-18 11:17 */ public class SortBean implements WritableComparable<SortBean>{ private String name; private Integer age; private Integer height; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Integer getHeight() { return height; } public void setHeight(Integer height) { this.height = height; } public SortBean() { super(); } public SortBean(String name, Integer age, Integer height) { this.name = name; this.age = age; this.height = height; } @Override public int compareTo(SortBean o) { return Integer.compare(age,o.getAge()); } @Override public void write(DataOutput dataOutput) throws IOException { dataOutput.writeUTF(name); dataOutput.writeInt(age); dataOutput.writeInt(height); } @Override public void readFields(DataInput dataInput) throws IOException { name=dataInput.readUTF(); age=dataInput.readInt(); height=dataInput.readInt(); } @Override public String toString() { return name + '\t' + age+'\t' + height; } }
-
Mapper
package com.yunhui.mapreduce.sort; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-17 18:57 */ public class SortMapper extends Mapper<LongWritable,Text,SortBean,NullWritable> { @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line=value.toString(); String [] strs=line.split(","); context.write(new SortBean(strs[0],Integer.parseInt(strs[1]),Integer.parseInt(strs[2])),NullWritable.get()); } }
-
Reducer
package com.yunhui.mapreduce.sort; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-17 19:01 */ public class SortReducer extends Reducer<SortBean,NullWritable,SortBean,NullWritable> { @Override protected void reduce(SortBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException { context.write(key,values.iterator().next()); } }
-
Driver
package com.yunhui.mapreduce.sort; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; import java.io.IOException; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-17 19:02 */ public class SortDriver { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration configuration=new Configuration(); Job job=Job.getInstance(configuration); job.setJarByClass(SortDriver.class); job.setMapperClass(SortMapper.class); job.setReducerClass(SortReducer.class); job.setMapOutputKeyClass(SortBean.class); job.setMapOutputValueClass(NullWritable.class); job.setOutputKeyClass(SortBean.class); job.setOutputValueClass(NullWritable.class); FileInputFormat.setInputPaths(job,new Path(args[0])); FileOutputFormat.setOutputPath(job,new Path(args[1])); boolean result = job.waitForCompletion(true); System.exit(result ? 0 : 1); } }
首先先说一下MapReduce默认的分区规则是HashPartitioner,其分区方法如下:
public int getPartition(K key, V value, int numReduceTasks) {
return (key.hashCode() & 2147483647) % numReduceTasks;
}
我们如果想要自定义分区,需要继承Partitioner,重写其分区方法。
这里我们是基于第二个Demo 将不同姓氏的人输出到不同的文件
-
自定义Partioner
package com.yunhui.mapreduce.sort; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.Partitioner; /** * @Author: Yun * @Description: * @Date: Created in 2018-04-18 14:12 */ public class SortPartitioner extends Partitioner<SortBean,NullWritable> { @Override public int getPartition(SortBean sortBean, NullWritable nullWritable, int i) { String name=sortBean.getName(); if(name.startsWith("王")){ return 0; }else if(name.startsWith("陈")){ return 1; }else{ return 2; } } }
-
在Driver设置自定义Partioner作为分区类
//设置分区 job.setPartitionerClass(SortPartitioner.class); job.setNumReduceTasks(3);