文章目录
一、前言
可以看这几个视频,挺棒的,可以快速上手MR开发。
先看这个链接的视频,非常短,会大题有一个了解:
1、深入浅出讲解MapReduece
再看这两个,以经典的wordcount为例,讲解原理与编程:
2、wordcount程序原理以及代码实现
3、mapreduce编程模型和具体实现框架之间的概念关系
有时间还可以看看3所在这个视频集合里的其他案例。
如果嫌视频慢的话,那就看我下面的代码以及文字吧。
我先以wordcount为例,来学习。据前辈说,好多的业务逻辑本质上都是wordcount的逻辑。
可以大概看看下面的第二部分,直接看代码,我在代码里面加了很多注释,对整个逻辑写的非常详细啦。
二、wordcount原理及分析
1、如何统计单词数?
2、MapReduce数据处理逻辑
要实现一个业务逻辑,关键在于:看我map这边产生写什么key,value。相同key的会通过reduce把对应的那些value聚合起来。
3、MapReduce架构(可略过)
我早年博客,可以加深理解:
https://blog.csdn.net/u013317445/article/details/51769986
在写代码之前,一定要把wordcount的业务逻辑搞清楚。知道mapper的输入输出是啥,知道reduce的输入输出是啥。知道map()实现啥,reduce()实现啥。
三、wordcount code
code主要分为三部分:
Mapper部分:核心是重写里面的map()方法,把map阶段的业务逻辑写到map()方法里面。
Reducer部分:核心是重写里面的reduce()方法,把reduce阶段的业务逻辑写到reduce()方法里面。
Driver部分:在此封装我们的MR程序相关运行参数。整个程序需要一个Drvier来进行提交。
WordcountMapper.java
package wc;/**
* Created by cc on 2019/11/26.
*/
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;
/**
* @program: WordC
* @description: map
* @author: chen
* @create: 2019-11-26 16:41
*
* Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> 输出输入类型:
* KEYIN:默认下,是mr框架所读到的一行文本的起始偏移量,Long。
* 但在hadoop里面有更加精简的接口,不直接用Long,而用LongWritable
* VALUEIN:默认下,是mr框架所读到的一行文本内容,String
* 同上,用Text(org.apache.hadoop.io里面的)
* KEYOUT:是用户自定义逻辑处理完成后输出数据中的key,在此处是单词,String。同上,用Text
* VALUEOUT:用户自定义逻辑完成之后输出数据中的value,在此处是单词词频,Integer
* 同上,用IntWritable
**/
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
/**
* 把map阶段的业务逻辑写到map()方法里面
* map task会对每一行输入数据调用一次我们自定义的map()方法。原来是每一行数据调一次map!!!
* 之后,每调一次map()会有一个输出(一行数据处理完了有一个输出)。map task会把这些输出收集收集到一起。然后根据单词的不同给不同的区域,再发给不同的reduce task
* 实际上,有多个map tasks,每个去处理一部分文本
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//super.map(key, value, context);
/**
* map task会对每一行输入数据调用一次map()
* 那么map()内要对这一行读入的文本作何处理呢?
* 1、将文本内容转为字符串
* 2、按空格分割字符串,获得每一个单词
* 3、每一个单词,构建一个<word,1>,输出<word,1>,写到context里
**/
String line= value.toString();
String[] words= line.split(" ");
for(String word:words){
//context.write(word,1); 错误,Mapper的输出类型是Text,IntWritable
context.write(new Text(word), new IntWritable(1));
}
}
}
WordcountReducer.java
package wc;/**
* Created by cc on 2019/11/27.
*/
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* @program: WordC
* @description: reduce
* @author: chen
* @create: 2019-11-27 10:33
*
* Reducer<KEYIN, VALUEIN ,KEYOUT, VALUEOUT >输入输出类型:
* KEYIN, VALUEIN对应Mapper输出类型Text,IntWritable
* KEYOUT, VALUEOUT是自定义Reducer的输出数据类型
* KEYOUT是单词,类型Text
* VALUEOUT是词频,类型IntWritable
**/
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
/**
* 把reduce阶段的业务逻辑写到reduce()方法里面
* reduce task拿到一组一组的数据,对每一组数据调一次reduce():
* 如对<sun,1><sun,1><sun,1><sun,1><sun,1><sun,1>处理一次,调一次reduce();
* 再下一个对<moon,1><moon,1><moon,1><moon,1>处理一次,调一次reduce(),......。
*
* reduce task数量可以指定,默认是1个
*
* 具体每一次reduce():
* 对数据块,key为同一个,对应的value们串成了一个迭代器
* 对value叠加,出count
* 输出<key,count>到context,整合出了次数
* */
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//super.reduce(key, values, context);
int count=0;
for(IntWritable value: values){
count += value.get();//.get()转为int
}
// Iterator<IntWritable> it= values.iterator();
// while(it.hasNext()){
// count += it.next().get();
// }
context.write(key, new IntWritable(count));//输出
}
}
WordcountDriver.java
刚开始学,Driver这个就当成一个固定模板照着写吧,只需要改一下参数和路径。
主要在上面Mapper里面的map()方法重写,Reduecer里面的reduce()方法重写,把咱们的业务逻辑算法逻辑梳理清,就可以用代码表达啦。
package wc;/**
* Created by cc on 2019/11/27.
*/
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.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
/**
* @program: WordC
* @description: 写main()
* @author: chen
* @create: 2019-11-27 12:04
*
* 相当于yarn集群的客户端
* 需要在此封装我们的MR程序相关运行参数,这顶jar包
* 最后提交给yarn
**/
/**
* map:本质是拆解,汽车拆成零件
* reduce:本质是组合,汽车零件+其它各种机械零件组合成变形金刚
* input(取数据)+split(切分)+map()+ shuffle(归类数据)+reduce(组装)+finalize(交付)
* */
public class WordcountDriver {
public static void main(String[] args) throws Exception {
Configuration conf= new Configuration();
Job job= Job.getInstance(conf); //创建一个job
//指定本程序的jar包所在的本地路径(告诉yarn jar包在哪里)
//job.setJar("path");//写死了路径
job.setJarByClass(WordcountDriver.class);//不写死,就在jar包所在路径
//指定本业务job要使用的mapper/reduce业务类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
//指定mapper输出数据类型,kv的
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//指定最终输出数据类型,kv的
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//如果以后我们要自定义输入输出格式的话需要加上:
//job.setInputFormatClass(MyInputFormat.class);
//job.setOutputFormatClass(MyOutputFormat.class);
//指定job的输入原始文件目录
FileInputFormat.setInputPaths(job, new Path("F:\\ideaWorkspace\\WordC\\input"));//我把路径写死了
//指定job的输出结果所在目录
FileOutputFormat.setOutputPath(job, new Path("F:\\ideaWorkspace\\WordC\\output2"));
//提交:将job中配置的相关参数,以及job所用的java类所在的jar包,提交给yarn去运行
//job.submit();
boolean res= job.waitForCompletion(true);
System.exit(res? 0:1);//退出个0:job跑成功了
}
}
四、梳理一下MR编程规范
- 1、用户编写的程序分为三个部分:Mapper、Reducer、Driver(提交运行mr程序的客户端)
- 2、Mapper的输入数据是KV对的形式(KV类型可自定义)
- 3、Reducer的输出数据是KV对的形式(KV类型可自定义)
- 4、Mapper中的业务逻辑写在map()方法中
- 5、map()方法(maptask进程)对每一个<K,V>调用一次。(处理一次<K,V>调一次map)
- 6、Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
- 7、Reducer的业务逻辑写在reduce()方法中
- 8、Reduce()方法对每一组相同K的<K,V>组调用一次
- 9、用户的Mapper和Reducer都要继承各自的类
- 10、整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象
map task们全部输出完了,reduce task们才开始去map的机器上去拿数据。
map task们输出数据时候,有outputCollector在收集<k1,v>,<k1,v><k1,v><k1,v>…
reduce task拿到一组一组的数据(相同key的为一组)
map task可能和reduce task在不同的机器上。
五、补充一下hadoop mapreduce内置数据类型
在考虑KV的输入输出类型的时候需要到这个知识点。
- BooleanWritable 标准布尔型数值
- ByteWritable 单字节数值
- DoubleWritable 双字节数
- FloatWritable 浮点数
- IntWritable 整型数
- LongWritable 长整型数
- Text 使用UTF-8格式存储的文本
- NullWritable 当<key, value>中的key或value为空时使用
这些内置数据类型,都实现了WritableComparable接口,以便用这些类型定义的数据可以被序列化进行网络传输和文件存储,以及进行大小比较。
我现在是能用内置类型就用内置类型,不用自己写了。
我们也可以自定义数据类型,不过要自己写一些接口。具体的话暂时没用到。
六、进一步,写Partitioner,写Combiner
https://www.cnblogs.com/frankdeng/p/9256254.html 可以看一下这个博客后面的部分。写了Partitioner把单词按照ASCII码奇偶分区,写了Combiner对每一个maptask的输出局部汇总。
好啦,就分享到这里吧。the end~