目录
- 一:MapReduce概述
- 1.MapReduce定义
- 2.MapReduce优势
- 3.MapReduce劣势
- 二:MapReduce核心思想
- 三:如何自定义一个map-reduce程序
- 1.建好Hadoop集群环境
- 2.参考官方WordCount案例
- 3.自定义WordCount案例
- 3.1 新建maven工程
- 3.2 日志配置log4j2.xml
- 3.3 编写Mapper类
- 3.4 编写Reducer类
- 3.5 编写Driver驱动类
- 3.6 在开发工具IDEA上运行测试程序
一:MapReduce概述
1.MapReduce定义
- MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
- MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
2.MapReduce优势
- 易于编程
它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行
- 良好的扩展性
当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
- 高容错性
MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。
- 适合PB级以上海量数据的离线处理
可以实现上千台服务器集群并发工作,提供数据处理能力。
3.MapReduce劣势
- 不擅长实时计算
MapReduce无法像MySQL一样,在毫秒或者秒级内返回结果
- 不擅长流式计算
流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的
- 不擅长DAG(有向图)计算
多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下
二:MapReduce核心思想
- 分布式的运算程序往往需要分成至少2个阶段
- 第一个阶段的MapTask并发实例,完全并行运行,互不相干
- 第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出
- MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行
三:如何自定义一个map-reduce程序
1.建好Hadoop集群环境
Hadoop3.x完全分布式集群完整搭建过程
2.参考官方WordCount案例
官方WordCount案例效果:
输入一个文本文件,输出该文件的所有单词出现的次数统计
反编译获得源码,查看wordcount源码
官方案例源码,加上注释理解:
package org.apache.hadoop.examples;
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class WordCount {
/**
* Mapper实现类,继承Mapper接口
* Mapper四个泛型 1.map输入key类型 2.map输入value类型 3.map输出key类型 4.map输出value类型
**/
public static class TokenizerMapper extends Mapper<Object, Text, Text, IntWritable> {
private static final IntWritable one = new IntWritable(1);
private Text word = new Text();
/**
* map阶段处理方法
* @param key 输入数据偏移量
* @param value 输入数据字符串
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void map(Object key, Text value, Mapper<Object, Text, Text, IntWritable>.Context context) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
this.word.set(itr.nextToken());
//循环每一行输入数据,对单词进行切分,然后按照 word:1 的形式写出
context.write(this.word, one);
}
}
}
/**
* Reducer实现类,继承Mapper接口
* Reducer四个泛型 1.map输出key类型 2.map输出value类型 3.reduce输出key类型 4.reduce输出value类型
**/
public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
/**
* reduce处理方法
* @param key 输入key--->这里的key是map中输出的key,即系word
* @param values 分组后的value集合,可以通过迭代得到每个value,这个案例里面每个value都是 one
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void reduce(Text key, Iterable<IntWritable> values, Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
//遍历value集合统计wordcount
sum += val.get();
}
this.result.set(sum);
context.write(key, this.result);
}
}
/**
* 驱动类
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//1.声明配置类
Configuration conf = new Configuration();
String[] otherArgs = (new GenericOptionsParser(conf, args)).getRemainingArgs();
if (otherArgs.length < 2) {
System.err.println("Usage: wordcount <in> [<in>...] <out>");
System.exit(2);
}
//2.获得job
Job job = Job.getInstance(conf, "word count");
//3.加载路径
job.setJarByClass(WordCount.class);
//4.mapper类指定
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
//5.reducer类指定
job.setReducerClass(IntSumReducer.class);
//6.设置输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//7.设置输入参数
for (int i = 0; i < otherArgs.length - 1; i++) {
FileInputFormat.addInputPath(job, new Path(otherArgs[i]));
}
//8.设置输出参数
FileOutputFormat.setOutputPath(job, new Path(otherArgs[otherArgs.length - 1]));
//9.提交job运行
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
3.自定义WordCount案例
实现跟官方一样的效果:
输入一个文本文件,输出该文件的所有单词出现的次数统计
3.1 新建maven工程
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
</dependencies>
<!-- 如果需要上传集群测试需要配置打包信息 -->
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.lizzy.mr.WordCountDriver</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
3.2 日志配置log4j2.xml
在项目的src/main/resources目录下,新建一个文件,命名为“log4j2.xml”,在文件中填入
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
<Appenders>
<!-- 类型名为Console,名称为必须属性 -->
<Appender type="Console" name="STDOUT">
<!-- 布局为PatternLayout的方式,
输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
<Layout type="PatternLayout"
pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
</Appender>
</Appenders>
<Loggers>
<!-- 可加性为false -->
<Logger name="test" level="info" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
<!-- root loggerConfig设置 -->
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
3.3 编写Mapper类
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 {
// 1 获取一行
String line = value.toString();
// 2 切割
String[] words = line.split(" ");
// 3 输出
for (String word : words) {
k.set(word);
context.write(k, v);
}
}
}
3.4 编写Reducer类
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
int sum;
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();
}
// 2 输出
v.set(sum);
context.write(key,v);
}
}
3.5 编写Driver驱动类
public class WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//可以通过启动参数传入,也可以像我一样在这里硬编码写死
args = new String[]{"写你的输入文件路径","输出路径(必须不存在)"};
// 1 获取配置信息以及封装任务
Configuration configuration = new Configuration();
//如果不是在虚拟机,集群部署在云主机上,最好配置这两项
//configuration.set("yarn.resourcemanager.hostname","hadoop103");
//configuration.set("dfs.client.use.datanode.hostname","true");
Job job = Job.getInstance(configuration);
// 2 设置jar加载路径
job.setJarByClass(WordcountDriver.class);
// 3 设置map和reduce类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 4 设置map输出
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
3.6 在开发工具IDEA上运行测试程序
- 配置好HADOOP_HOME以及hadoop的windows运行时依赖
- 运行驱动方法
新建测试数据 xxx.txt
lizzy lizzy
ss ss
cls cls
jiao
banzhang
xue
hadoop
期望输出结果:
banzhang 1
cls 2
hadoop 1
jiao 1
lizzy 2
ss 2
xue 1