MapReduce编程深入
一、回顾
-
MapReduce的功能以及应用场景
- Hadoop1.x
- HDFS
- MapReduce v1:分布式计算框架
- 分布式程序
- 分布式资源
- 主从架构:JobTracker,TaskTacker
- Hadoop2.x
- HDFS
- MapReduce v2:分布式编程模型
- 一套API
- 从逻辑上定义数据处理的过程
- YARN:分布式资源管理
- ResourceManager
- NodeManager
- 负责根据逻辑上代码定义的处理过程而使用资源去运行实现这个过程
- 应用
- 适合于离线大数据业务场景
- 不适合于实时或者对时效性要求较高的场景、
- 不适合处理小量数据
- 目前大数据行业发展:偏向于实时
- 认为所有数据会随着时间的流逝,数据的价值会逐渐降低
- 离线:以时间为单位进行数据处理
- T+1:今天处理昨天的数据
- 时效性一般都是分钟内以上级别:小时、天、周、月、季度、年
- 实时:以数据为单位的
- Hadoop1.x
-
MapReduce编程规则
-
基本原理:执行五大阶段过程
-
Input:负责整个程序的输入,读取数据
-
功能
- 将读取到的数据按照分的规则,将数据变成分片:Split
- getSplits
- 将每个分片的每一条数据转换为KV
- K1
- V1
- RecordReader:nextKeyValue(源码就是这个类的这个方法进行读数据的)
- 将读取到的数据按照分的规则,将数据变成分片:Split
-
从哪读取,怎么读,都由输入类控制:InputFormat
-
默认输入类:TextInputFormat:用于读取文件
hadoop hive spark hbase spark hadoop hive spark hbase spark hadoop hive spark hbase spark hive hbase hadoop hadoop
-
构建分片
-
Split1
hadoop hive spark hbase spark hadoop hive spark
-
Split2
hbase spark hadoop hive spark hbase spark hive hbase hadoop hadoop
-
-
转换K1V1
- K1:行的偏移量
- V1:行的内容
-
-
输出
-
Split1
0 hadoop hive spark 20 hbase spark 40 hadoop hive spark
-
Split2
0 hbase spark 100 hadoop hive spark 200 hbase spark hive hbase hadoop hadoop
-
-
-
Map:任务的划分
-
根据Input阶段有几个Split,在Map阶段会启动几个MapTask进程来处理每个分片的数据
-
MapTask1:Split1
0 hadoop hive spark 20 hbase spark 40 hadoop hive spark
-
MapTask2:Split2
0 hbase spark 100 hadoop hive spark 200 hbase spark hive hbase hadoop hadoop
-
处理逻辑:每个MapTask会构建一个Mapper类的对象实例,对自己处理的每一条数据调用一次map方法
- map方法逻辑:由我们自定义
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8hfD6BMu-1615184077759)(Day07_MapReduce深入.assets/image-20201031093951757.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UeMPhmf-1615184077762)(Day07_MapReduce深入.assets/image-20201031094114945.png)]
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6iiDyUYp-1615184077764)(Day07_MapReduce深入.assets/image-20201031094306478.png)]
-
输出K2,V2
hadoop 1 hive 1 spark 1 hbase spark hadoop hive spark 1
-
-
Shuffle:排序,分组
-
排序:K2
hadoop 1 hadoop hbase hive 1 hive spark 1 spark spark 1
-
分组:K2
hadoop <1,1> hbase <1> hive <1,1> spark <1,1,1>
-
-
Reduce:设计是为了做聚合,默认只会启动一个ReduceTask
-
构建一个Reducer类的实例,对每一组调用一次reduce方法
-
处理逻辑:reduce方法
-
输出:K3,V3
hadoop 2 hbase 1 hive 2 spark 3
-
-
Output:负责整个程序的输出
- 输出类:outputFormat
- 默认:TextOutputFormat:将结果保存到文件中
- K3与V3之间使用制表符分隔
-
-
类和方法
-
Driver:驱动类
-
推荐:继承Configured 实现 Tool
-
main:作为程序运行的入口,需要调用run方法
int status = Toolrunner.run(conf,new Class ,args)
-
run:构建、配置、提交
Job job = Job.getInstance(this.getConf,jobName) job.setJarByClass(this.class) //input Path inputPath = new Path(args[0]) TextInputFOrmat.setInputPaths(job,inputpath1……) //map job.setMapperClass(Mapper.class) job.setMapperOutputKeyClass(K2) job.setMapperOutputValueClass(V2) //reduce job.setReduceClass(Reducer.class) job.setOutputKeyClass(K3) job.setOutputValueClass(V3) //output Path outputPath = new Path(args[1]) TextOutputFormat.setOutputPath(job,outputPath) return job.waitForCompletion(true) ? 0 : -1
-
-
Input:TextInputFormat
-
Map
public class Mapper extend Mapper<K1,V1,K2,V2>{ map方法 }
-
Shuffle
-
Reduce
public class Reducer extends Reducer<K2,V2,K3,V3>{ reduce方法 }
-
Output:TextOutputFormat
-
-
数据结构:KV
-
数据类型:必须使用支持序列化的类型
- 不能用常用的Java类型
- 使用Hadoop中的序列化类型
- Text
- IntWritable
- DoubleWritable
- ……
- 原理:封装了Java中常用类型在一个JavaBean中,让这个JavaBean实现了序列化和反序列化
-
-
反馈问题
- 在执行Hadoop namenode —format的时候为什么会生成格式化的fsimage文件,生成的fsimage是存在磁盘里的镜像文件吗?
- 是的
- 元数据
- 磁盘:格式化、SecondaryNameNode =》fsimage
- 内存:每次NameNode启动时,会加载fsimage和edits将元数据加载到内存
- mapreduce处理的计算是分布式,在用yarn 调用jar包时,输入的文件只能是分布式系统内的文件,linux本地文件(比如/export/data/wc.txt)不能作为输入文件处理是吗?
- 不是
- Input负责读取数据:由输入类决定
- TextInputFormat:读文件,根据fs.defaultFS配置的文件系统来读取的
- 在YARN上运行时,从原理上是可以读Linux文件系统
- 但是当前YARN环境只能读HDFS
- YARN在启动时,加载了配置文件
- core-site.xml
- 在执行Hadoop namenode —format的时候为什么会生成格式化的fsimage文件,生成的fsimage是存在磁盘里的镜像文件吗?
二、课程目标
- 二手房统计:回顾
- shuffle过程【重要】
- 功能:分区、排序、分组
- 分区:干预
- 排序:干预
- 自定义数据类型【重要】
- 问题:MapReduce中处理数据需要处理多列,怎么解决
- 自定义JavaBean
三、MapReduce实现二手房统计
1、需求
- 统计每个地区的二手房个数
2、分析
-
step1:先观察数据
梅园六街坊,2室0厅,47.72,浦东,低区/6层,朝南,500,104777,1992年建
- 分隔符:逗号
- 每个字段的含义
- 小区
- 户型
- 面积
- 地区
- 楼层
- 朝向
- 总价
- 单价
- 建造年份
-
step2:结果长什么样?
地区 二手房个数
- 地区:下标为3
-
step3:判断有没有分组或者排序
- 分析完这一步:决定K2
- 分组:需求中出现每个、每、不同、各个,后面的字段就是肯定是分组字段
- 排序:最高、最低。前几,后几、升序、降序
- 有分组:地区
- K2:地区
-
step4:分析一下,除了K2以外,结果中还需要哪些字段
- 决定V2
- V2:1
-
step5:验证
-
Input
- 读取这个文件
- K1:行的偏移量
- V1:行的内容
-
Map
-
map
- 对V1分割,取第4个字段是地区
- 用地区作为Key
- 用1作为Value
-
K2:地区
-
V2:1
-
-
Shuffle
-
分组:按照K2
-
每个地区分为一组,相同地区的所有Value放入同一个迭代器中
浦东 《1,1,1,1,1,1,1》 ……
-
-
Reduce
- reduce
- 对迭代器累加求和即可
- reduce
-
3、实现
package bigdata.itcast.cn.hadoop.mapreduce.secondhouse;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
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.IOException;
/**
* @ClassName SecondHouseMr
* @Description TODO 实现二手房的个数统计
* @Date 2020/10/31 10:55
* @Create By Frank
*/
public class SecondHouseMr extends Configured implements Tool {
public int run(String[] args) throws Exception {
//构建
Job job = Job.getInstance(this.getConf(),"second");
job.setJarByClass(SecondHouseMr.class);
//配置
//输入路径可以指定目录,会将目录下的所有文件作为输入,注意:输入目录中不能再包含目录
Path inputPath = new Path("datas/lianjia");
TextInputFormat.setInputPaths(job,inputPath);
job.setMapperClass(SecondMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(SecondReduce.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
Path outputPath = new Path("datas/output/second");
//判断输出目录是否存在
FileSystem hdfs = FileSystem.get(this.getConf());
if(hdfs.exists(outputPath)){
hdfs.delete(outputPath,true);
}
TextOutputFormat.setOutputPath(job,outputPath);
return job.waitForCompletion(true) ? 0 : -1;
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
int status = ToolRunner.run(conf,new SecondHouseMr(),args);
System.exit(status);
}
public static class SecondMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
Text outputKey = new Text();
IntWritable outputValue = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//对每行内容分割,取出地区
String region = value.toString().split(",")[3];
//将地区赋值给K2
this.outputKey.set(region);
//输出
context.write(this.outputKey,this.outputValue);
}
}
public static class SecondReduce extends Reducer<Text, IntWritable,Text, IntWritable>{
IntWritable outputValue = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable value : values) {
sum += value.get();
}
this.outputValue.set(sum);
context.write(key,this.outputValue);
}
}
}
四、分区与自定义分区
1、问题
-
shuffle功能
- 排序:按照K2排序
- 分组:按照K2分组
- 分区
-
MapReduce怎么解决大数据计算的问题?
-
通过分布式的解决方案来实现
-
分布式体现在什么地方?
- Map:负责任务的拆分
- 根据Input分片的个数来划分的
- Input阶段将数据进行划分为Split
- Map阶段将每个Split对应一个MapTask,实现计算任务的划分
- 例如:1000万
- Input
- Split1:300万
- Split2:300万
- Split3:400万
- Map
- MapTask1:300万:Mapper:map:300万
- MapTask2:300万:Mapper:map:300万
- MapTask3:400万:Mapper:map:400万
- ||
- K2V2:1000万
- Shuffle:本质上:还是将这1000万个K2V2排序以后放在一个迭代器中
- Reduce:默认只有1个
- ReduceTask:1000万
- Input
- Map:负责任务的拆分
-
问题:如果只有一个Reduce,一个Reduce复杂处理所有数据,如果数据量过大,ReduceTask只能在一台机器启动一个进程来运行,会导致处理非常缓慢,甚至程序资源不足
-
解决:可以启动多个Reduce来分摊数据处理的负载
-
实现
-
Reduce只有1个的情况下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7vNDSxkL-1615184077765)(Day07_MapReduce深入.assets/image-20201031113436606.png)]
-
多个Reduce
job.setNumReduceTasks(2);//指定ReduceTask的个数为2
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eaG07Wir-1615184077769)(Day07_MapReduce深入.assets/image-20201031113607389.png)]
-
-
问题:所有Map输出的数据会被两个Reduce分摊处理,每一条K2V2如何决定会被哪个Reduce进行处理?
- 将数据分给不同的Reduce,分配的规则?
-
2、分区
-
分区:指定的是MapReduce中有多个Reduce,一个Reduce就是一个分区
- 分区的编号从0开始
- 如果有两个reduce
- 第一个分区:0
- 第二个分区:1
-
分区的规则:所有K2V2是如何分配给不同的Reduce
-
默认规则:默认会调用一个分区器的类来实现,默认类:HashPartition
public int getPartition(K2 key, V2 value, int numReduceTasks) { return (key.hashCode() & 2147483647) % numReduceTasks; }
-
根据K2的Hash值取余Reduce的【分区】个数,得到的结果就是分区的编号
-
hadoop 1 => K2的hash值:4 => 4 % 2 = 0 =》 reduce0
-
hive 1=> K2的hash值: 1 => 1 % 2 =1 =》 reduce1
-
优点
- 只要K2相同,就会进入同一reduce
-
缺点
- 数据分配不均衡
-
-
3、自定义分区
-
需求:默认的Hash取余的方式会导致数据分配不均衡,工作中会根据需求修改分区方式
-
实现
- step1:自定义分区器
- 继承Partitioner类
- 实现getPartition方法
- step2:设置定义的分区器
- step1:自定义分区器
-
需求:统计每个地区二手房的个数,用两个Reduce,浦东的数据单独用一个reduce处理,其他地区的数据给另外一个reduce处理
-
自定义分区
package bigdata.itcast.cn.hadoop.mapreduce.partition; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; /** * @ClassName UserPartition * @Description TODO 用户自定义分区器 * @Date 2020/10/31 11:55 * @Create By Frank */ public class UserPartition extends Partitioner<Text, IntWritable> { /** * 将浦东作为一个分区的数据,将其他地区放到另外一个分区中 * @param key:就是当前这条K2 * @param value:就是当前这条V2 * @param numReduceTasks:Reduce个数 * @return */ public int getPartition(Text key, IntWritable value, int numReduceTasks) { //根据地区做判断 String region = key.toString(); if("浦东".equals(region)){ return 0; }else{ return 1; } } }
-
设置分区
job.setNumReduceTasks(2);//设置Reduce以及分区个数为2 job.setPartitionerClass(UserPartition.class);//设定分区的规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OckIqHNk-1615184077769)(Day07_MapReduce深入.assets/image-20201031120220423.png)]
- 自定义分区经常用于分布式存储和分布式计算
- 常用规则
- Hash分区
- 随机分区
- 轮询分区
- 槽位分区
- ……
- 常用规则
总结
- 什么是分区?
- 一个分区就是一个Reduce,有多个Reduce就有多个分区
- 为什么要有多个reduce?
- 为了提高Reduce的性能,加快处理的性能
- 分区规则
- 默认:Hash分区
- 自定义
- 分区发生阶段
- Shuffle
五、MR中自定义数据类型
1、Hadoop中的数据类型
- 问题:为什么在Mapreduce开发过程中,不能使用Java中常用的类型作为KV?
- 原因:分布式的计算中,数据必须支持序列化以及反序列化
- 需求:1+……+9
- MapTask1:1+2+3
- node1:6
- MapTask2:4+5+6
- node2:15
- MapTask3:7+8+9
- node3:24
- ReduceTask:将三个MapTask的结果进行最后的累加
- node4
- 问题:数据的跨机器传输,选择哪一种传输方式?
- 方式一:不做序列化,直接传递数据
- A -》1 《- B
- 导致数据混乱不一致的问题,解析失败
- 方式二:做序列化,传递对象
- A String age = “1”-》String “1” 《- B
- A发送对象:序列化
- B解析对象:反序列化
- 方式一:不做序列化,直接传递数据
- MapTask1:1+2+3
- Hadoop中提供了常用的序列化类型:Text,IntWritable……
2、问题
-
问题:MapReduce中所有数据的都以KV形式存在,整个数据传输只能有两列
- 如果我们需要多列怎么解决?
-
需求:希望wordcount的结果有3列,中间加上Key的长度
-
之前的wordcount结果
K3· V3 hadoop 5 hbase 4 hive 4 spark 6
-
希望的结果
hadoop 6 5 hbase 5 4 hive 4 4 spark 5 6
-
-
解决
-
方案一:拼接字符串,将多列封装在一个Text里面
- 不适合于多次拼接,以及字段比较多的情况
-
方案二:自定义JavaBean,必须实现序列化以及反序列化
-
3、自定义数据类型
-
规则:自己构建一个类,实现Writable接口【实现序列化和反序列化】
- 定义属性
- get and set
- 构造
- toString
- write:序列化
- readFields:反序列化
-
实现:在Reduce的方法中,输出类型使用自定义数据类型,将单词和单词长度封装为一个JavaBean
package bigdata.itcast.cn.hadoop.mapreduce.bean; import org.apache.hadoop.io.Writable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @ClassName UserBean1 * @Description TODO 自定义数据类型,封装单词和单词长度 * @Date 2020/10/31 14:57 * @Create By Frank */ public class UserBean1 implements Writable { //定义属性 private String word; private int len; //构造 public UserBean1(){} //get and set public void setAll(String word,int len){ this.setWord(word); this.setLen(len); } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public long getLen() { return len; } public void setLen(int len) { this.len = len; } @Override public String toString() { return this.word+"\t"+this.len; } //序列化 public void write(DataOutput out) throws IOException { out.writeUTF(this.word); out.writeInt(this.len); } //反序列化:注意:一定要保证顺序和类型是一致的 public void readFields(DataInput in) throws IOException { this.word = in.readUTF(); this.len = in.readInt(); } }
-
代码实现
package bigdata.itcast.cn.hadoop.mapreduce.bean; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; 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.Mapper; import org.apache.hadoop.mapreduce.Reducer; 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.IOException; /** * @ClassName WordCountMr * @Description TODO 自定义开发实现wordcount程序 * @Date 2020/10/30 16:36 * @Create By Frank */ public class WordCountLocalBean1 extends Configured implements Tool { //构建,配置,提交 public int run(String[] args) throws Exception { //todo:1-构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountLocalBean1.class); //todo:2-配置 //input Path inputPath = new Path("D:\\IDEAProject\\SHBigdata\\datas\\wordcount\\wordcount.txt"); TextInputFormat.setInputPaths(job,inputPath); //map job.setMapperClass(WcMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //reduce job.setReducerClass(WcReducer.class); job.setOutputKeyClass(UserBean1.class); job.setOutputValueClass(IntWritable.class); // job.setNumReduceTasks(2);//指定ReduceTask的个数为2 //output Path outputPath = new Path("datas/output/wc/output4"); TextOutputFormat.setOutputPath(job,outputPath); //todo:3-提交 return job.waitForCompletion(true) ? 0:-1; } //作为程序入口,负责调用run方法 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // conf.set("fs.defaultFS","hdfs://node1:8020"); int status = ToolRunner.run(conf, new WordCountLocalBean1(), args); System.exit(status); } public static class WcMapper extends Mapper<LongWritable, Text,Text, IntWritable>{ //定义输出的Key:单词 Text outputKey = new Text(); //定义输出的Value:恒为1 IntWritable outputValue = new IntWritable(1); /** * 每一条数据会调用一次map方法 * @param key:K1,行的偏移量 * @param value:V1:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每一行的内容进行分割,得到每个单词 String[] words = value.toString().split(" "); //遍历取出每个单词 for (String word : words) { //将单词作为Key this.outputKey.set(word); //输出:write方法用于将新的KV输出到下一步 context.write(this.outputKey,this.outputValue); } } } public static class WcReducer extends Reducer<Text, IntWritable,UserBean1, IntWritable>{ //定义输出的Key UserBean1 outputKey = new UserBean1(); //定义输出的Value IntWritable outputValue = new IntWritable(); /** * 每一组调用一次reduce方法 * @param key:单词 * @param values:同一个单词对应的所有value * @param context * @throws IOException * @throws InterruptedException */ @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } //将单词出现的次数赋值给value this.outputValue.set(sum); //给key赋值 this.outputKey.setAll(key.toString(),key.toString().length()); //输出 context.write(this.outputKey,this.outputValue); } } }
六、MapReduce分类与排序
1、MapReduce程序分类
-
五大阶段:Input、Map、Shuffle、Reduce、Output
- 应用:需要做分组聚合,适合于多对一
-
三大阶段:Input、Map、Output
-
应用:不需要做分组聚合,适合于一对一场景
-
ETL:数据清洗
id name age ip 1 laoda 18 192.168.134.111 laoer 20 192.168.134.112 3 laosan 22 192.168.134.113
- 过滤:将不需要【非法,脏】的数据过滤掉
- 没有id的数据过滤掉
- 补全:解析用户的ip,得到用户所在的国家、省份、城市
- 转换:解密、格式转换
- 过滤:将不需要【非法,脏】的数据过滤掉
-
-
需求:直接打印wordcount程序中Map输出的结果,并且在Map输出的结果中添加单词的长度这一列
-
预期结果
hadoop 6 1 hive 4 1 spark 5 1 ……
-
-
实现
-
step1:设置ReduceTask的个数为0
job.setNumReduceTasks(0);//指定ReduceTask的个数为0
-
step2:测试代码
package bigdata.itcast.cn.hadoop.mapreduce.mapoutput; import bigdata.itcast.cn.hadoop.mapreduce.bean.UserBean1; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; 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.Mapper; import org.apache.hadoop.mapreduce.Reducer; 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.IOException; /** * @ClassName WordCountMr * @Description TODO 自定义开发实现wordcount程序 * @Date 2020/10/30 16:36 * @Create By Frank */ public class WordCountMapOutput extends Configured implements Tool { //构建,配置,提交 public int run(String[] args) throws Exception { //todo:1-构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountMapOutput.class); //todo:2-配置 //input Path inputPath = new Path("D:\\IDEAProject\\SHBigdata\\datas\\wordcount\\wordcount.txt"); TextInputFormat.setInputPaths(job,inputPath); //map job.setMapperClass(WcMapper.class); job.setMapOutputKeyClass(UserBean1.class); job.setMapOutputValueClass(IntWritable.class); //reduce // job.setReducerClass(WcReducer.class); // job.setOutputKeyClass(Text.class); // job.setOutputValueClass(IntWritable.class); job.setNumReduceTasks(0);//指定ReduceTask的个数为0 //output Path outputPath = new Path("datas/output/wc/output6"); TextOutputFormat.setOutputPath(job,outputPath); //todo:3-提交 return job.waitForCompletion(true) ? 0:-1; } //作为程序入口,负责调用run方法 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // conf.set("fs.defaultFS","hdfs://node1:8020"); int status = ToolRunner.run(conf, new WordCountMapOutput(), args); System.exit(status); } public static class WcMapper extends Mapper<LongWritable, Text, UserBean1, IntWritable>{ //定义输出的Key:单词 UserBean1 outputKey = new UserBean1(); //定义输出的Value:恒为1 IntWritable outputValue = new IntWritable(1); /** * 每一条数据会调用一次map方法 * @param key:K1,行的偏移量 * @param value:V1:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每一行的内容进行分割,得到每个单词 String[] words = value.toString().split(" "); //遍历取出每个单词 for (String word : words) { //将单词作为Key this.outputKey.setAll(word,word.length()); //输出:write方法用于将新的KV输出到下一步 context.write(this.outputKey,this.outputValue); } } } public static class WcReducer extends Reducer<Text, IntWritable,Text, IntWritable>{ //定义输出的Value IntWritable outputValue = new IntWritable(); /** * 每一组调用一次reduce方法 * @param key:单词 * @param values:同一个单词对应的所有value * @param context * @throws IOException * @throws InterruptedException */ @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } //将单词出现的次数赋值给value this.outputValue.set(sum); //输出 context.write(key,this.outputValue); } } }
-
step3:观察结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qqrrAGZ-1615184077771)(Day07_MapReduce深入.assets/image-20201031153851219.png)]
-
-
现在通过自定义的数据类型作为K2,报错了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uz32LGKT-1615184077771)(Day07_MapReduce深入.assets/image-20201031154332298.png)]
-
原因
-
Map
- K2:UserBean1:单词+长度
- V2:IntWritable:1
-
Shuffle
- 基于在Driver类中的类型定义,构建了一个collector的集合
- 分区:只有1个reduce
- 排序:对K2排序
- 将K2强转为一个比较器类型的子类,如果想强转成功,K2,必须包含一个比较器的方法
- 要求K2必须实现比较器接口或者继承一个比较器的类,才能有这个比较器方法
- 将K2强转为一个比较器类型的子类,如果想强转成功,K2,必须包含一个比较器的方法
-
根本原因:自己定义的类型不能被转换为比较器类型,不能实现排序
-
2、MapReduce中的排序方式一
-
Shuffle阶段会对K2进行排序
- 方式一:调用K2自带的compareTo方法
-
问题:如何实现让自定义数据类型有compareTo方法
-
解决:自定义数据类型实现比较器接口:WritableCompareble
- 除了正常的方法外
- 多实现了一个compareTo
-
实现自定义数据类型,实现比较器接口
package bigdata.itcast.cn.hadoop.mapreduce.bean; import org.apache.hadoop.io.Writable; import org.apache.hadoop.io.WritableComparable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @ClassName UserBean1 * @Description TODO 自定义数据类型,封装单词和单词长度 * @Date 2020/10/31 14:57 * @Create By Frank */ public class UserBean2 implements WritableComparable<UserBean2> { //定义属性 private String word; private int len; //构造 public UserBean2(){} //get and set public void setAll(String word,int len){ this.setWord(word); this.setLen(len); } public String getWord() { return word; } public void setWord(String word) { this.word = word; } public int getLen() { return len; } public void setLen(int len) { this.len = len; } @Override public String toString() { return this.word+"\t"+this.len; } //序列化 public void write(DataOutput out) throws IOException { out.writeUTF(this.word); out.writeInt(this.len); } //反序列化:注意:一定要保证顺序和类型是一致的 public void readFields(DataInput in) throws IOException { this.word = in.readUTF(); this.len = in.readInt(); } /** * 当数据类型实现比较器接口以后,重写比较器方法 * 在Shuffle阶段,对当前类型排序会被调用 * 常见的排序:冒泡、快排 * 核心的思想:比较 * 升序:如果你比我小,你放到我的前面去 * Java中根据比较的返回值:来决定大于、小于、等于 * 返回值大于0:大于 * 返回值小于0:小于 * 返回值等于0:相等 * 默认排序:升序 * @param o * @return */ public int compareTo(UserBean2 o) { //先比较第一个属性,如果第一个属性相等,再比较第二个 //如果第一个属性不相等 int comp = this.getWord().compareTo(o.getWord()); if(comp == 0){ //因为第一个值相等,以第二个值比较的结果作为最后的结果 return Integer.valueOf(this.getLen()).compareTo(Integer.valueOf(o.getLen())); } //默认是升序,加上负号,可以实现降序 return comp; } }
-
实现wordcount
package bigdata.itcast.cn.hadoop.mapreduce.bean; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; 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.Mapper; import org.apache.hadoop.mapreduce.Reducer; 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.IOException; /** * @ClassName WordCountMr * @Description TODO 自定义开发实现wordcount程序 * @Date 2020/10/30 16:36 * @Create By Frank */ public class WordCountUserBean2 extends Configured implements Tool { //构建,配置,提交 public int run(String[] args) throws Exception { //todo:1-构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountUserBean2.class); //todo:2-配置 //input Path inputPath = new Path("D:\\IDEAProject\\SHBigdata\\datas\\wordcount\\wordcount.txt"); TextInputFormat.setInputPaths(job,inputPath); //map job.setMapperClass(WcMapper.class); job.setMapOutputKeyClass(UserBean2.class); job.setMapOutputValueClass(IntWritable.class); //reduce job.setReducerClass(WcReducer.class); job.setOutputKeyClass(UserBean2.class); job.setOutputValueClass(IntWritable.class); // job.setNumReduceTasks(0);//指定ReduceTask的个数为0 //output Path outputPath = new Path("datas/output/wc/output9"); TextOutputFormat.setOutputPath(job,outputPath); //todo:3-提交 return job.waitForCompletion(true) ? 0:-1; } //作为程序入口,负责调用run方法 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // conf.set("fs.defaultFS","hdfs://node1:8020"); int status = ToolRunner.run(conf, new WordCountUserBean2(), args); System.exit(status); } public static class WcMapper extends Mapper<LongWritable, Text, UserBean2, IntWritable>{ //定义输出的Key:单词 UserBean2 outputKey = new UserBean2(); //定义输出的Value:恒为1 IntWritable outputValue = new IntWritable(1); /** * 每一条数据会调用一次map方法 * @param key:K1,行的偏移量 * @param value:V1:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每一行的内容进行分割,得到每个单词 String[] words = value.toString().split(" "); //遍历取出每个单词 for (String word : words) { //将单词作为Key this.outputKey.setAll(word,word.length()); //输出:write方法用于将新的KV输出到下一步 context.write(this.outputKey,this.outputValue); } } } public static class WcReducer extends Reducer<UserBean2, IntWritable,UserBean2, IntWritable>{ //定义输出的Value IntWritable outputValue = new IntWritable(); /** * 每一组调用一次reduce方法 * @param key:单词 * @param values:同一个单词对应的所有value * @param context * @throws IOException * @throws InterruptedException */ @Override protected void reduce(UserBean2 key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } //将单词出现的次数赋值给value this.outputValue.set(sum); //输出 context.write(key,this.outputValue); } } }
-
降序怎么解决?
public int compareTo(UserBean2 o) { //先比较第一个属性,如果第一个属性相等,再比较第二个 //如果第一个属性不相等 int comp = this.getWord().compareTo(o.getWord()); if(comp == 0){ //因为第一个值相等,以第二个值比较的结果作为最后的结果 return Integer.valueOf(this.getLen()).compareTo(Integer.valueOf(o.getLen())); } //默认是升序,加上负号,可以实现降序 return -comp; }
-
总结:自定义数据类型
-
方式一:实现Writable
- 这种类型不能作为五大阶段的K2
-
方式二:实现WritableCompareble
- 可以作为任何一个位置的KV类型
-
3、MapReduce中的排序方式二
-
问题:如果K2,不是自定义数据类型,怎么实现干预排序呢?
-
需求:基于Text按照单词降序排序
-
解决:MapReduce中允许自定义排序比较器
-
规则:自己开发一个类,继承WritableComparator
- 重写compare方法
-
实现
-
step1:自定义排序比较器
package bigdata.itcast.cn.hadoop.mapreduce.sort; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * @ClassName UserSort * @Description TODO 用于实现用户自定义排序 * @Date 2020/10/31 16:34 * @Create By Frank */ public class UserSort extends WritableComparator { //注册比较的类型 public UserSort(){ super(Text.class,true); } @Override public int compare(WritableComparable a, WritableComparable b) { Text o1 = (Text) a; Text o2 = (Text) b; //默认升序 return -o1.compareTo(o2); } }
-
step2:代码实现
package bigdata.itcast.cn.hadoop.mapreduce.sort; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configured; 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.Mapper; import org.apache.hadoop.mapreduce.Reducer; 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.IOException; /** * @ClassName WordCountMr * @Description TODO 自定义开发实现wordcount程序 * @Date 2020/10/30 16:36 * @Create By Frank */ public class WordCountSort extends Configured implements Tool { //构建,配置,提交 public int run(String[] args) throws Exception { //todo:1-构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountSort.class); //todo:2-配置 //input Path inputPath = new Path("D:\\IDEAProject\\SHBigdata\\datas\\wordcount\\wordcount.txt"); TextInputFormat.setInputPaths(job,inputPath); //map job.setMapperClass(WcMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //shuffle job.setSortComparatorClass(UserSort.class);//使用自定义的排序比较器来实现排序 //reduce job.setReducerClass(WcReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // job.setNumReduceTasks(2);//指定ReduceTask的个数为2 //output Path outputPath = new Path("datas/output/wc/output11"); TextOutputFormat.setOutputPath(job,outputPath); //todo:3-提交 return job.waitForCompletion(true) ? 0:-1; } //作为程序入口,负责调用run方法 public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); // conf.set("fs.defaultFS","hdfs://node1:8020"); int status = ToolRunner.run(conf, new WordCountSort(), args); System.exit(status); } public static class WcMapper extends Mapper<LongWritable, Text,Text, IntWritable>{ //定义输出的Key:单词 Text outputKey = new Text(); //定义输出的Value:恒为1 IntWritable outputValue = new IntWritable(1); /** * 每一条数据会调用一次map方法 * @param key:K1,行的偏移量 * @param value:V1:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每一行的内容进行分割,得到每个单词 String[] words = value.toString().split(" "); //遍历取出每个单词 for (String word : words) { //将单词作为Key this.outputKey.set(word); //输出:write方法用于将新的KV输出到下一步 context.write(this.outputKey,this.outputValue); } } } public static class WcReducer extends Reducer<Text, IntWritable,Text, IntWritable>{ //定义输出的Value IntWritable outputValue = new IntWritable(); /** * 每一组调用一次reduce方法 * @param key:单词 * @param values:同一个单词对应的所有value * @param context * @throws IOException * @throws InterruptedException */ @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int sum = 0; for (IntWritable value : values) { sum += value.get(); } //将单词出现的次数赋值给value this.outputValue.set(sum); //输出 context.write(key,this.outputValue); } } }
-
-
总结
-
排序发生在哪个阶段?
- Shuffle阶段
-
为什么要做排序?
- 为了做分组,加快分组的效率
-
排序规则
- 方式一:默认规则:调用K2自带的compareTo方法
- 官方类型:自带了
- 自定义类型:实现WritableCompareble
- 方式二:自定义:自定义排序比较器
- 优先级高于方式一
- 方式一:默认规则:调用K2自带的compareTo方法
-
一般在工作中,只有数据量非常大,才利用MapReduce自带的排序来是实现
-
如果数据量比较小:一般排序放在Reduce里面做
-
练习
-
练习1:实现二次排序
-
数据
a 4 b 0 a 3 c 6 a 8 b 7 b 5 c 3
-
结果
a 8 a 4 a 3 b 7 b 5 b 0 c 6 c 3
-
实现
- 方案一:通过自定义数据类型,将两列作为一个整体,通过排序比较器来实现二次排序
- K2:两列
- V2:NullWritable
- 方案二:用第一列作为K2,用第二列作为V2
- 在reduce方法中,将这个字母对应的所有数字放入list集合
- Collection.sort(list)
- 迭代排序好的集合输出即可
- 方案一:通过自定义数据类型,将两列作为一个整体,通过排序比较器来实现二次排序
-
-
练习2:统计每个地区二手房的个数,平均单价,最高单价,最低单价
-
练习3:基于搜狗数据统计每种搜索词出现的次数
-
练习4:基于搜狗数据统计每个小时的UV
- UV:唯一用户访问的数
- |
- 统计每个小时用户的个数
- |
- 去重
-
练习3和练习4放到YARN上运行,观察分片的个数