思想
MapReduce思想在生活中处处可见。或多或少都曾接触过这种思想。MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景)。
- Map负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。
- Reduce负责“合”,即对map阶段的结果进行全局汇总。
这两个阶段合起来正是MapReduce思想的体现。
还有一个比较形象的语言解释MapReduce:
我们要数图书馆中的所有书。你数1号书架,我数2号书架。这就是“Map”。我们人越多,数书就更快。
现在我们到一起,把所有人的统计数加在一起。这就是“Reduce”。
图示
图解
Map阶段是一个整体,包括我们自定义的Mapper类完成的MapTask阶段和Hadoop框架为我们提供的Shuffle阶段的分区,排序,规约的默认处理(Shuffle阶段我们也可以自定义)
Reduce阶段也是一个整体,包括我们自定义的Reducer类完成的ReduceTask阶段和Hadoop框架为我们提供的Shuffle阶段的分组的默认处理(相同键的值放在同一个集合)
ReduceTask阶段输入的K2-V2是经过Shuffle分组(相同键的值放在同一个集合)之后的键值对, 由Hadoop框架默认提供
MapReduce也可以只有Map阶段没有Reduce阶段,这样的输出结果就是完成MapTask阶段和Shuffle阶段的分区,排序,规约的默认处理后的结果
一个DataNode数据块对应一个MapTask
多个DataNode数据块对应的MapTask并行处理
一个分区对应一个ReduceTask
多个分区对应的ReduceTask并行处理
案例代码-单词统计
WordCountMapper
package com.sz.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* 自定义map类
*将k1 v1 转为k2 v2
*继承Mapper类 重写map方法
*
*/
public class WordCountMapper extends Mapper<LongWritable,Text,Text,LongWritable> {
/**
* 将k1 v1 转为k2 v2
* k1 v1
* 0 hello,world,hadoop
* 24 hive,sqoop,flume,hello
* -------------------------------
* k2 v2
* hello 1
* world 1
* --------------------------------
* 思路:
* 将v1以','分隔符进行分割,产生字符串数组
*遍历字符串数组 统计个数
* 字符串作为k2 个数作为v2
*
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String s = value.toString();
//将v1以','分隔符进行分割,产生字符串数组
String[] split = s.split(",");
//遍历字符串数组 统计个数
for (String k2:split) {
context.write(new Text(k2),new LongWritable(1));
}
}
}
WordCountReducer
package com.sz.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* 自定义Reducer类
* 将k2-v2 转为k3-v3
* 继承Reduce类 重写reduce方法
*/
public class WordCountReducer extends Reducer<Text,LongWritable,Text,LongWritable>{
/**
* 自定义Reduce逻辑
* 统计单词个数
* 将shuffle之后的k2-v2 转为k3-v3
* k2-v2
* hello <1,1,1>
* world <1,1>
* ------------------------
* k3-v3
* hello 3
* world 2
* 转换思路
* 键不变k2-k3
* 遍历v2值 统计相加 结果作为新值v3
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
//遍历v2值 统计相加 结果作为新值v3
long v3 = 0;
for (LongWritable v:values) {
v3 +=v.get();
}
context.write(key,new LongWritable(v3));
}
}
jobMain
package com.sz.mapreduce;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.File;
public class jobMain extends Configured implements Tool{
public static void main(String args[]) throws Exception {
//这里创建了一个configuration对象,好像并没有给他赋值初始化,空对象好像并没有什么用
//其实Configuration有一个静态代码块(682行)在创建对象时会执行,可以加载很多配置文件文件完成初始化
Configuration configuration = new Configuration();
//启动job任务
//set the configuration back, so that Tool can configure itself
//这个方法会使用configuration对象参数配置tool对象的实现类 tool.setConf(conf);
//这个方法会调用tool 对象的run()方法 这里的话就是tool对象的实现类jobMain重写的run()方法 return tool.run(toolArgs);
//public static int run(Configuration conf, Tool tool, String[] args)
int run = ToolRunner.run(configuration, new jobMain(), args);
System.exit(run);
}
//该方法用于创建一个job任务
@Override
public int run(String[] args) throws Exception {
//创建job任务对象
Job job = Job.getInstance(super.getConf(), "WordCount");
//配置job任务(八个步骤)
//输入
//指定文件的读取方式和路径
job.setInputFormatClass(TextInputFormat.class);
// Path input = new Path("hdfs://node01:8020/wordcount");
Path input = new Path("file:///D:\\hadoop_test\\wordcount_input");
TextInputFormat.addInputPath(job,input);
//Map阶段
//指定Map阶段的处理方式和数据类型
job.setMapperClass(WordCountMapper.class);
//设置Map阶段k2类型
job.setMapOutputKeyClass(Text.class);
//设置Map阶段v2类型
job.setMapOutputValueClass(LongWritable.class);
//shuffle阶段 采用默认方式
//分区:
//设置我们的分区类,以及我们的reducetask的个数,注意reduceTask的个数一定要与我们的分区数保持一致
// job.setPartitionerClass(WordPartition.class);
// job.setNumReduceTasks(3);
//Reduce阶段
//指定Reduce阶段的处理方式和数据类型
job.setReducerClass(WordCountReducer.class);
//设置Reduce阶段k3类型
job.setOutputKeyClass(Text.class);
//设置Reduce阶段v3类型
job.setOutputValueClass(LongWritable.class);
//输出
//设置输出类型和路径
job.setOutputFormatClass(TextOutputFormat.class);
// Path path = new Path("hdfs://node01:8020/wordcount_out");
Path path = new Path("file:///D:\\hadoop_test\\wordcount_output");
TextOutputFormat.setOutputPath(job,path);
//等待执行
boolean b = job.waitForCompletion(true);
//执行成功返回0
return b?0:1;
}
}
WordPartition
package com.sz.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
/**
* 分区类
* 这里的输入类型就是Map阶段结束后的输出类型
* k2 v2
* hello 1
* world 1
*/
public class WordPartition extends Partitioner<Text,LongWritable>{
@Override
public int getPartition(Text key, LongWritable value, int i) {
if (key!=null) {
int length = key.toString().length();
if(length>5){
return 0;
}else if(length==5){
return 1;
}else{
return 2;
}
}
return 0;
}
}
运行结果
源文件数据
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
K1(行偏移量) V1(Text)
0 hello,world,hadoop
19 hive,sqoop,flume,hello
42 kitty,tom,jerry,world
64 hadoop
MapTask(自定义)
K2 V2
hello 1
world 1
hadoop 1
hive 1
sqoop 1
flume 1
hello 1
kitty 1
tom 1
jerry 1
world 1
hadoop 1
排序
K2 V2
flume 1
hadoop 1
hadoop 1
hello 1
hello 1
hive 1
jerry 1
kitty 1
sqoop 1
tom 1
world 1
world 1
分组
K2 V2
flume 1
hadoop <1,1>
hello <1,1>
hive 1
jerry 1
kitty 1
sqoop 1
tom 1
world <1,1>
ReduceTask(自定义)
不分区结果
K3 V3
flume 1
hadoop 2
hello 2
hive 1
jerry 1
kitty 1
sqoop 1
tom 1
world 2
按单词长度三分区分区结果
0分区
hadoop 2
1分区
flume 1
hello 2
jerry 1
kitty 1
sqoop 1
world 2
2分区
hive 1
tom 1