优秀是一种习惯
- 知识点01:回顾
- 知识点02:目标
- 知识点03:Task个数问题
- 知识点04:MapReduce分区机制
- 知识点05:Shuffle自定义分区实现
- 知识点06:MapReduce中的数据类型
- 知识点07:数据处理的问题及解决方案
- 知识点08:方案一:拼接字符串
- 知识点09:方案二:自定义数据类型封装JavaBean
- 知识点10:MapReduce程序的分类
- 知识点11:三大阶段的MapReduce程序
- 知识点12:了解MapReduce中的排序问题
- 知识点13:自定义数据类型实现比较器接口
- 知识点14:MapReduce中的排序设计及规则
- 知识点15:自定义排序方案一:自定义数据类型
- 知识点16:自定义排序方案二:自定义排序器
- 练习
- 附录一:MapReduce编程依赖
知识点01:回顾
-
MapReduce中的Input的功能是什么?
- 功能:负责程序的输出
- 实现
- 数据分片:将读取到的数据拆分为多个分片Split
- 转换KV:将任何一种数据源转换为KV结构
-
MapReduce的Map的功能是什么?
- 功能:实现分布式中分的过程
- 实现
- 根据分片的个数启动MapTask,每个MapTask进程处理一个分片的数据
- 每个MapTask会实例化一个Mapper类的对象,调用map方法实现处理
-
MapReduce的Shuffle的功能是什么?
- 功能:分区、排序、分组
- 实现
- 排序:按照K2,对Map输出的KV进行排序
- 分组:按照K2,对Map输出的KV进行分组,相同K2对应的所有V2放在一起
-
MapReduce中的Reduce的功能是什么?
- 功能:实现分布式中合的功能
- 实现
- 默认启动1个ReduceTask进程来处理Shuffle输出的数据
- 每个ReduceTask会实例化一个Reducer类的对象,调用reduce方法实现聚合
-
MapReduce中的Output的功能是什么?
- 功能:负责整个程序的输出
- 实现:将上一步的KV保存到外部系统
-
MapReduce开发过程中涉及的类及对应的方法?
- Driver:作为程序入口
- 规则:继承Configured 实现 Tool
- 方法
- run:构建、配置、提交MapReduce Job
- main:程序入口,调用run方法
- Input:实现数据读取
- 规则:InputFormat
- 默认:TextInputFormat
- 读文件
- K:行的偏移量
- V:行的内容
- Mapper:实现Map阶段的处理
- 规则:继承Mapper
- 重写:map
- Reducer:实现Reduce阶段的处理
- 规则:继承Reducer
- 重写:reduce
- Output:负责实现结果的保存
- 规则:OutputFormat
- 默认:TextOutputFormat
- Driver:作为程序入口
-
MapReduce中的数据结构与数据类型是什么?
- 结构:KV键值对
- 类型:支持Hadoop序列化的类型
-
实现MapReduce模板的开发
-
反馈问题
- MapReduce是纯cpu运算吗?有木有办法像cuda那样调用gpu并行运算实现显卡加速?
- 每一个MapTask和ReduceTask进程中,为什么不是使用多线程的方式调用的map方法和reduce方法。
- 这个很简单?内心狂叫你确定很简单?
- 本地测试的时候,另外两个Mapper和Reducer类为什么还用public修饰,不是说public修饰的类名必须和文件名一致的嘛?
知识点02:目标
- MapReduce中使用问题
- Reduce负载问题
- reduce只有1个,如果负载过高,性能就会比较差
- 可以启动多个Reduce
- Map的所有数据如何分配给多个Reduce
- |
- 分区
- 数据类型问题
- MapReduce的数据结构是KV,数据类型只能用序列化类型
- |
- 问题:数据只能有两列,如果要处理的数据有多列怎么办?
- |
- 自定义数据类型:自己写JavaBean 实现序列化
- Reduce负载问题
- Shuffle过程:排序
- 为什么要做排序?
- 排序的规则?
- 自定义排序?
知识点03:Task个数问题
-
引入:Task的个数如何决定?Reduce个数什么时候会有多个?
-
目标:掌握MapReduce中Task个数的规则及配置
-
路径
- step1:Task个数的默认规则
- step2:多个Reduce场景
- step3:多个Reduce的配置
-
实施
-
Task个数的默认规则
- MapTask:由Split的分片个数决定
- ReduceTask:默认只有1个,可以自己配置多个
-
多个Reduce场景
- 场景:单个ReduceTask处理的数据量过多,导致性能比较差,或者单机资源不足,程序失败
-
- 解决:配置多个ReduceTask
-
多个Reduce的配置
-
方式一:动态指定
job.setNumReduceTasks(2);//设置Reduce个数
-
方式二:手动指定配置
conf.set("mapreduce.job.reduces","2");
-
-
小结
-
Task的个数如何决定?
- MapTask:分片个数
- ReduceTask:默认只有1个,可以指定多个
-
Reduce个数什么时候会有多个?
- 当Reduce处理的数据量过多导致性能比较差,单机资源不足
-
如何配置多个Reduce?
job.setNumReduceTasks(N)
-
知识点04:MapReduce分区机制
-
引入:多个Reduce的场景下,如何MapTask的数据会给哪个Reduce处理?
-
目标:掌握MapReduce中的分区机制
-
路径
- step1:分区机制
- step2:分区规则
-
实施
-
分区机制
-
每个Reduce就是一个分区
-
功能:多个Reduce时,按照一定的规则将所有Map的数据分配给不同的Reduce
-
阶段:Map输出后进入Shuffle阶段
- 每一条Map阶段输出的数据都会调用分区器来计算自己属于哪个分区,归哪个Reduce进行处理
- 每一条Map阶段输出的数据都会调用分区器来计算自己属于哪个分区,归哪个Reduce进行处理
-
-
分区规则
-
本质:给数据打标签
-
规则:默认的分区器调用的是HashPartitioner
-
Key的Hash值取余分区个数
public class HashPartitioner<K2, V2> implements Partitioner<K2, V2> { public void configure(JobConf job) {} /** Use {@link Object#hashCode()} to partition. */ //返回值为分区的编号 public int getPartition(K2 key, V2 value,int numReduceTasks) { return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks; } }
-
-
优缺点
- 优点:相同Key的数据在同一个Reduce中
- 缺点:数据分配不均衡
- 如果所有Key的hash值取余的结果都是一个,所有的数据都由一个reduce处理,其他reduce就是空闲的,资源浪费了
-
-
-
小结
- 什么是分区机制?
- 多个Reduce的情况下,根据分区机制的规则计算当前的Map输出的每条数据最终会被哪个Reduce进行处理
- 分区的规则是什么?
- Hash分区:根据K2的hash值取余分区的个数
- 什么是分区机制?
知识点05:Shuffle自定义分区实现
-
引入:如果工作中希望根据自己的规则实现分区怎么办?
-
目标:掌握MapReduce中自定义分区的开发规则及实现
-
路径
- step1:自定义分区的规则
- step2:自定义分区的实现
-
实施
- 自定义分区的规则
-
规则:继承Partitioner类,重写getPartition方法
-
配置
job.setPartitionerClass(HashPartitioner.class); //设置分区器,默认是Hash分区器
-
- 自定义分区的实现
-
需求:将浦东的二手房,单独由一个Reduce计算,其他地区进入另外一个reduce计算
-
实现:两个Reduce,分区规则:浦东一个分区,其他在另一个分区
-
自定义一个分区器
package bigdata.itcast.cn.hadoop.mr.partition; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Partitioner; /** * @ClassName UserPartition * @Description TODO 自定义分区器 * extends Partitioner<Text, IntWritable> * 必须指定K2和V2的类型,重写getPartition方法必须指定类型 * @Date 2021/4/27 11:06 * @Create By Frank */ public class UserPartition extends Partitioner<Text, IntWritable> { /** * Map输出的每一条数据会调用一次 * @param k2:key * @param v2:value * @param numPartitions:reduce个数 * @return:分区的编号,0,1 */ public int getPartition(Text k2, IntWritable v2, int numPartitions) { //获取当前的地区 String region = k2.toString(); //判断 这个地区是否是浦东 if("浦东".equals(region)){ return 0; }else return 1; } }
-
代码中指定分区器
//Shuffle:配置Shuffle job.setPartitionerClass(UserPartition.class); //设置分区器 //Reduce:配置Reduce job.setNumReduceTasks(2); //设置ReduceTask的个数,默认为1
-
结果
-
-
- 自定义分区的规则
-
小结
- 自定义分区的规则是什么?
- 继承Partitioner,重写getPartition(K2,V2,reduce个数)
- 自定义分区的规则是什么?
知识点06:MapReduce中的数据类型
-
引入:MapReduce中的数据类型与Java中数据类型的区别?
-
目标:掌握MapReduce中数据类型的特点
- 为什么不能使用Java中的类型?
- MapReduce中自带了哪些类型?
- MapReduce的类型是如何实现序列化和反序列化的?
-
路径
- step1:序列化与反序列化
- step2:Hadoop中的自带数据类型
-
实施
- 序列化与反序列化
- 定义:在Java实现对象的传递过程中需要使用序列化与反序列化
- 数据传递:A -> 1 -> B
- 对象传递:A ->Int a = 1 -> B
- 序列化:将对象转换为字节
- 反序列化:将字节解析为对象
- 定义:在Java实现对象的传递过程中需要使用序列化与反序列化
- Hadoop中的自带数据类型
- 非String:xxxWritable
- IntWritable
- LongWritable
- BooleanWritable
- ……
- String:Text
- 怎么实现序列化的?
- 都实现Writable接口
- 重写方法
- write:序列化方法
- readFields:反序列化方法
- 非String:xxxWritable
- 序列化与反序列化
-
小结
- MapReduce中的数据类型有哪些?
- 非String:xxxxWritable
- String:Text
- 本质:将Java的类型封装成JavaBean,实现了序列化接口
- 如何实现实现序列化与反序列化的?
- 实现Writable
- 重写write和readFields
- MapReduce中的数据类型有哪些?
知识点07:数据处理的问题及解决方案
-
引入:MapReduce中只能传递KV,KV类型只能存储一列,如果要实现多列数据处理传输怎么办?
-
目标:了解MapReduce中数据处理的问题及解决方案
- 怎么实现多列传输?
-
路径
- step1:数据处理的问题
- step2:解决方案
-
实施
-
数据处理的问题
-
MapReduce中的数据传输只能以KV形式传输
-
MapReduce中的数据类型只能是Hadoop自带的类型
-
MapReduce中只能传递两列
-
如果数据处理过程中需要传递多列怎么办?
-
例如:MapReduce实现Wordcount时,最后结果中希望加上一列,单词的长度,结果如下:
hadoop 6 4 hbase 5 2 hive 4 2 spark 5 3
-
如何实现?
-
-
解决方案
- 方案一:拼接字符串
- 方案二:封装JavaBean
-
-
小结
- 怎么实现多列传输?
- 拼接字符串:将多列通过Text拼接为1列,实现传输
- 封装JavaBean:自定义一个JavaBean,在JavaBean中定义多个属性,按照Hadoop规则,实现Writable接口,就可以在Hadoop中使用
- 怎么实现多列传输?
知识点08:方案一:拼接字符串
-
目标:实现通过拼接字符串解决多列传输及处理的问题
-
实施
-
代码
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 WordCountDriver * @Description TODO 自定义开发程序实现Wordcount词频统计 * @Date 2021/4/26 15:33 * @Create By Frank */ public class WordCountConcatString extends Configured implements Tool { public int run(String[] args) throws Exception { //构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountConcatString.class); //配置 job.setInputFormatClass(TextInputFormat.class); //使用程序的第一个参数作为输入 TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt")); job.setMapperClass(WCMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(WCReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); job.setOutputFormatClass(TextOutputFormat.class); //使用程序的第二个参数作为输出路径 Path outputPath = new Path("datas/output/wc/output4"); FileSystem fs = FileSystem.get(this.getConf()); if(fs.exists(outputPath)){ fs.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 WordCountConcatString(), args); System.exit(status); } public static class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> { //输出的K2 Text outputKey = new Text(); //输出的V2 IntWritable outputValue = new IntWritable(1); /** * 每条KV调用一次map * @param key:行的偏移量 * @param value:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每行的内容分割得到每个单词 String[] words = value.toString().split("\\s+"); //迭代取出每个单词作为K2 for (String word : words) { //将当前的单词作为K2 this.outputKey.set(word); //将K2和V2传递到下一步 context.write(outputKey,outputValue); } } } public static class WCReducer extends Reducer<Text, IntWritable,Text, IntWritable> { //输出K3:拼接单词和单词的长度 Text outputKey = new Text(); //输出V3 IntWritable outputValue = new IntWritable(); /** * 每一组调用一次 * @param key:单词 * @param values:所有相同单词对应的1 * @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) { //取出当前单词所有 1,进行累加 sum += value.get(); } //给K3赋值:单词和单词的长度 this.outputKey.set(key.toString()+"\t"+key.toString().length()); //给V3赋值 this.outputValue.set(sum); //传递到下一步 context.write(this.outputKey,this.outputValue); } } }
-
结果
-
-
小结
- 基本与Java拼接字符串方案一致
- 优点:简单
- 缺点:如果字符串过多,数据类型不一致,维护顺序麻烦,处理容易出错
知识点09:方案二:自定义数据类型封装JavaBean
-
引入:如果涉及到多列数据处理或者数据传输,MapReduce自带的类型不能满足需求,拼接字符串也不方便,如何解决?
-
目标:掌握MapReduce中自定义数据类型的实现
-
路径
- step1:规则
- step2:实现
-
实施
-
规则
- 实现Writable接口:实现序列化与反序列化
- 重写方法
- write:序列化
- readFields:反序列化
-
实现
-
需求:定义一个类型,存储一个String属性和一个Int属性,用于实现MapReduce中的数据传输
-
实现
-
自定义数据类型
import org.apache.hadoop.io.Writable; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; /** * @ClassName UserBean1 * @Description TODO 自定义封装JavaBean,作为MapReduce数据传输的类型 * @Date 2021/4/27 11:35 * @Create By Frank */ public class UserBean1 implements Writable { //给定属性 private String firstKey; private int secondKey; //构造 public UserBean1(){} //get and set public String getFirstKey() { return firstKey; } public void setFirstKey(String firstKey) { this.firstKey = firstKey; } public int getSecondKey() { return secondKey; } public void setSecondKey(int secondKey) { this.secondKey = secondKey; } //toString @Override public String toString() { return this.firstKey+"\t"+this.secondKey; } //序列化 public void write(DataOutput out) throws IOException { out.writeUTF(this.firstKey);//类型必须一致 out.writeInt(this.secondKey); } //反序列化 public void readFields(DataInput in) throws IOException { this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致 this.secondKey = in.readInt(); } }
-
代码
package bigdata.itcast.cn.hadoop.mr.userbean; 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 WordCountDriver * @Description TODO 自定义开发程序实现Wordcount词频统计 * @Date 2021/4/26 15:33 * @Create By Frank */ public class WordCountUserBean1 extends Configured implements Tool { public int run(String[] args) throws Exception { //构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountUserBean1.class); //配置 job.setInputFormatClass(TextInputFormat.class); //使用程序的第一个参数作为输入 TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt")); job.setMapperClass(WCMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); job.setReducerClass(WCReducer.class); job.setOutputKeyClass(UserBean1.class); job.setOutputValueClass(IntWritable.class); job.setOutputFormatClass(TextOutputFormat.class); //使用程序的第二个参数作为输出路径 Path outputPath = new Path("datas/output/wc/output5"); FileSystem fs = FileSystem.get(this.getConf()); if(fs.exists(outputPath)){ fs.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 WordCountUserBean1(), args); System.exit(status); } public static class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> { //输出的K2 Text outputKey = new Text(); //输出的V2 IntWritable outputValue = new IntWritable(1); /** * 每条KV调用一次map * @param key:行的偏移量 * @param value:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每行的内容分割得到每个单词 String[] words = value.toString().split("\\s+"); //迭代取出每个单词作为K2 for (String word : words) { //将当前的单词作为K2 this.outputKey.set(word); //将K2和V2传递到下一步 context.write(outputKey,outputValue); } } } public static class WCReducer extends Reducer<Text, IntWritable,UserBean1, IntWritable> { //输出K3:拼接单词和单词的长度 UserBean1 outputKey = new UserBean1(); //输出V3 IntWritable outputValue = new IntWritable(); /** * 每一组调用一次 * @param key:单词 * @param values:所有相同单词对应的1 * @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) { //取出当前单词所有 1,进行累加 sum += value.get(); } //给K3赋值:单词和单词的长度 this.outputKey.setFirstKey(key.toString()); this.outputKey.setSecondKey(key.toString().length()); //给V3赋值 this.outputValue.set(sum); //传递到下一步 context.write(this.outputKey,this.outputValue); } } }
-
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3FHtlaM-1619600266581)(Day08_MapReduce深入.assets/image-20210427114437906.png)]
-
-
-
-
小结
- MapReduce如何实现自定义数据类型?
- 构建一个JavaBean,将每一列作为JavaBean中的属性
- 必须实现Writable接口,重写write和readFields
- 序列化:类型必须指定
- 反序列化:顺序必须与序列化顺序一致
- MapReduce如何实现自定义数据类型?
知识点10:MapReduce程序的分类
-
引入:MapReduce如果实现没有分组聚合的场景是否需要Shuffle和Reduce呢?
-
目标:掌握MapReduce的程序的分类及应用场景
-
路径
- step1:五大阶段程序
- step2:三大阶段程序
-
实施
-
五大阶段程序
- 阶段:Input、Map、Shuffle、Reduce、Output
- 性能:相对较慢
- 应用:需要分组聚合和全局排序
- 分组排序:Shuffle
- 聚合:Reduce
- 统计分析类的程序一般都是五大阶段的:多对一
- 统计单词的个数
- 统计二手房的个数
- 统计二手房的平均单价
- 统计二手房的最高单价
- 统计最低单价
-
三大阶段程序
-
阶段:Input、Map、Output
-
性能:相对较快
-
应用:不需要分组聚合或者全局排序
-
一般做ETL程序会用三大阶段:一对一
18/Aug/2020:18:30:20 userid1 url1 19/Aug/2020:18:30:20 userid2 url1 10/Aug/2020:18:30:20 userid3 url1
|
2020-08-18 18:30:20 userid1 url1 2020-08-19 18:30:20 userid2 url1 2020-08-10 18:30:20 userid3 url1
-
-
-
-
小结
- MapReduce程序分为几类,各自的应用场景是什么?
- 五大阶段:分组聚合或者全局排序
- 三大阶段:ETL场景
- MapReduce程序分为几类,各自的应用场景是什么?
知识点11:三大阶段的MapReduce程序
-
目标:实现MapReduce三大阶段的程序开发
-
路径
- step1:需求
- step2:实现
-
实施
-
需求
- 将WordCount程序中Map的结果进行输出,并增加一列单词的长度
-
配置
//如果没有Reduce,必须指定下面这句代码 job.setNumReduceTasks(0);//不走shuffle和reduce过程
-
实现
package bigdata.itcast.cn.hadoop.mr.mapout; import bigdata.itcast.cn.hadoop.mr.userbean.UserBean1; 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 WordCountDriver * @Description TODO 自定义开发程序实现Wordcount词频统计 * @Date 2021/4/26 15:33 * @Create By Frank */ public class WordCountMapOutput extends Configured implements Tool { public int run(String[] args) throws Exception { //构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountMapOutput.class); //配置 job.setInputFormatClass(TextInputFormat.class); //使用程序的第一个参数作为输入 TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt")); job.setMapperClass(WCMapper.class); job.setMapOutputKeyClass(UserBean1.class); job.setMapOutputValueClass(IntWritable.class); // job.setReducerClass(WCReducer.class); // job.setOutputKeyClass(UserBean1.class); // job.setOutputValueClass(IntWritable.class); //如果没有Reduce,必须指定下面这句代码 job.setNumReduceTasks(0);//不走shuffle和reduce过程 job.setOutputFormatClass(TextOutputFormat.class); //使用程序的第二个参数作为输出路径 Path outputPath = new Path("datas/output/wc/output6"); FileSystem fs = FileSystem.get(this.getConf()); if(fs.exists(outputPath)){ fs.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 WordCountMapOutput(), args); System.exit(status); } public static class WCMapper extends Mapper<LongWritable, Text,UserBean1, IntWritable> { //输出的K2 UserBean1 outputKey = new UserBean1(); //输出的V2 IntWritable outputValue = new IntWritable(1); /** * 每条KV调用一次map * @param key:行的偏移量 * @param value:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每行的内容分割得到每个单词 String[] words = value.toString().split("\\s+"); //迭代取出每个单词作为K2 for (String word : words) { //将当前的单词作为K2 this.outputKey.setFirstKey(word); this.outputKey.setSecondKey(word.length()); //将K2和V2传递到下一步 context.write(outputKey,outputValue); } } } public static class WCReducer extends Reducer<Text, IntWritable,UserBean1, IntWritable> { //输出K3:拼接单词和单词的长度 UserBean1 outputKey = new UserBean1(); //输出V3 IntWritable outputValue = new IntWritable(); /** * 每一组调用一次 * @param key:单词 * @param values:所有相同单词对应的1 * @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) { //取出当前单词所有 1,进行累加 sum += value.get(); } //给K3赋值:单词和单词的长度 this.outputKey.setFirstKey(key.toString()); this.outputKey.setSecondKey(key.toString().length()); //给V3赋值 this.outputValue.set(sum); //传递到下一步 context.write(this.outputKey,this.outputValue); } } }
-
结果
-
-
小结
- 如何实现三大阶段的程序?
- 将Shuffle和Reduce的定义注释掉
- 设置ReduceTask个数为0
- 如何实现三大阶段的程序?
知识点12:了解MapReduce中的排序问题
-
目标:了解MapReduce中的排序问题
-
路径
- step1:需求
- step2:实现
-
实施
-
需求
-
将WordCount程序中Reduce的结果进行输出,并增加一列单词的长度
hadoop 6 4 hbase 5 2 hive 4 2 spark 5 3
-
-
实现
-
报错
-
- 现象:类转换异常
- 原因:Shuffle中需要做排序和分组,本质是做比较,底层会按照K2进行比较,将K2强转为比较器对象
- 类必须实现比较器的方法才能转成功
-
小结
- 为什么会出现这个问题?
- Shuffle需要做排序和分组:做比较
- 要想做比较:必须实现比较器的接口实现比较器的类
- 我们自定义的K2,没有实现比较器接口,所以无法强转为比较类型对象,无法实现排序或者分组
- 为什么会出现这个问题?
知识点13:自定义数据类型实现比较器接口
-
目标:实现自定义数据类型并实现排序接口
-
路径
- step1:规则
- step2:实现
-
实施
- 规则
- 实现WritableComparable接口
- 重写compareTo方法
- 规则
-
实现
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 自定义封装JavaBean,作为MapReduce数据传输的类型 * @Date 2021/4/27 11:35 * @Create By Frank */ public class UserBean2 implements WritableComparable<UserBean2> { //给定属性 private String firstKey; private int secondKey; //构造 public UserBean2(){} //get and set public String getFirstKey() { return firstKey; } public void setFirstKey(String firstKey) { this.firstKey = firstKey; } public int getSecondKey() { return secondKey; } public void setSecondKey(int secondKey) { this.secondKey = secondKey; } //toString @Override public String toString() { return this.firstKey+"\t"+this.secondKey; } //序列化 public void write(DataOutput out) throws IOException { out.writeUTF(this.firstKey);//类型必须一致 out.writeInt(this.secondKey); } //反序列化 public void readFields(DataInput in) throws IOException { this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致 this.secondKey = in.readInt(); } /** * 当这个类型作为K2经过shuffle时,需要调用该方法进行比较判断:排序和分组 * @param o * @return */ public int compareTo(UserBean2 o) { //先比较第一个属性 int comp = this.getFirstKey().compareTo(o.getFirstKey()); //如果第一个值,比较第二个值,用第二个属性的比较的结果作为最后的结果 if(comp == 0){ return Integer.valueOf(this.getSecondKey()).compareTo(Integer.valueOf(o.getSecondKey())); } return comp; } }
-
小结
- 如何实现自定义数据类型并实现排序接口?
- 实现:WritableComparable
- 重写:compareTo
- 一般建议直接实现WritableComparable
- 实现Writable:这个类型不能作为五大阶段中的K2
- 如何实现自定义数据类型并实现排序接口?
知识点14:MapReduce中的排序设计及规则
-
引入:为什么在Driver类中要定义K2,V2,K3,V3的类型?
-
目标:掌握MapReduce中排序的设计及规则
-
路径
- step1:排序的设计目的
- step2:排序的实现规则
-
实施
-
排序的设计目的
-
排序的设计是为了加快Shuffle中分组的性能
-
不排序
hadoop 1 hive 1 spark 1 hive 1 hadoop 1 spark 1
-
排序
hadoop 1 hadoop 1 hive 1 hive 1 spark 1 spark 1
-
-
-
排序的实现规则
- step1:先调用排序器来实现排序
- step2:如果没有排序器,会调用K2自带的compareTo方法来实现排序
-
-
小结
- 为什么要做排序?
- 提高分组的性能
- 如何做排序的?
- step1:先调用排序器
- step2:如果没有排序器,调用K2的compareTo方法
- 为什么要做排序?
知识点15:自定义排序方案一:自定义数据类型
-
目标:实现自定义数据类型的排序
-
路径
- step1:需求分析
- step2:代码实现
-
实施
-
需求分析
-
对wordcount.txt进行词频统计,并输出结果如下
spark 5 3 hive 4 2 hbase 5 2 hadoop 6 4
-
-
代码实现
package bigdata.itcast.cn.hadoop.mr.sort; 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 自定义封装JavaBean,作为MapReduce数据传输的类型 * @Date 2021/4/27 11:35 * @Create By Frank */ public class UserBean2 implements WritableComparable<UserBean2> { //给定属性 private String firstKey; private int secondKey; //构造 public UserBean2(){} //get and set public String getFirstKey() { return firstKey; } public void setFirstKey(String firstKey) { this.firstKey = firstKey; } public int getSecondKey() { return secondKey; } public void setSecondKey(int secondKey) { this.secondKey = secondKey; } //toString @Override public String toString() { return this.firstKey+"\t"+this.secondKey; } //序列化 public void write(DataOutput out) throws IOException { out.writeUTF(this.firstKey);//类型必须一致 out.writeInt(this.secondKey); } //反序列化 public void readFields(DataInput in) throws IOException { this.firstKey = in.readUTF(); //必须与序列化的顺序保持一致 this.secondKey = in.readInt(); } /** * 当这个类型作为K2经过shuffle时,需要调用该方法进行比较判断:排序和分组 * @param o * @return */ public int compareTo(UserBean2 o) { //先比较第一个属性 int comp = this.getFirstKey().compareTo(o.getFirstKey()); //如果第一个值,比较第二个值,用第二个属性的比较的结果作为最后的结果 if(comp == 0){ return Integer.valueOf(this.getSecondKey()).compareTo(Integer.valueOf(o.getSecondKey())); } //默认升序,如果要降序,添加负号即可 return -comp; } }
-
-
小结
- 如何在自定义数据类型时进行排序?
- 调用compareTo方法
- 默认是升序,加负号就可以降序
- 如何在自定义数据类型时进行排序?
知识点16:自定义排序方案二:自定义排序器
-
目标:实现自定义排序器排序
-
路径
- step1:自定义排序器的规则
- step2:自定义排序器的实现
-
实施
-
自定义排序器的规则
- 继承WritableComparator类
- 重写compare方法
-
自定义排序器的实现
-
需求:实现词频统计,按照单词降序排序
-
结果
spark 3 hive 2 hbase 2 hadoop 4
-
实现自定义排序比较器
package bigdata.itcast.cn.hadoop.mr.sort; import org.apache.hadoop.io.Text; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * @ClassName UserSort * @Description TODO 用户自定义比较器 * @Date 2021/4/27 15:03 * @Create By Frank */ public class UserSort extends WritableComparator { //step1:注册 public UserSort(){ super(Text.class,true); } //step2:实现比较 @Override public int compare(WritableComparable a, WritableComparable b) { //将两个比较器对象强转为要比较的Text类型 Text t1 = (Text) a; Text t2 = (Text) b; //实现两个Text类型的比较,降序排序 return -t1.toString().compareTo(t2.toString()); } }
-
代码
package bigdata.itcast.cn.hadoop.mr.sort; 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 WordCountDriver * @Description TODO 自定义开发程序实现Wordcount词频统计 * @Date 2021/4/26 15:33 * @Create By Frank */ public class WordCountUserSort extends Configured implements Tool { public int run(String[] args) throws Exception { //构建 Job job = Job.getInstance(this.getConf(),"userwc"); job.setJarByClass(WordCountUserSort.class); //配置 job.setInputFormatClass(TextInputFormat.class); //使用程序的第一个参数作为输入 TextInputFormat.setInputPaths(job,new Path("datas/wordcount/wordcount.txt")); job.setMapperClass(WCMapper.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class); //配置Shuffle job.setSortComparatorClass(UserSort.class);//指定排序器 job.setReducerClass(WCReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); job.setOutputFormatClass(TextOutputFormat.class); //使用程序的第二个参数作为输出路径 Path outputPath = new Path("datas/output/wc/output10"); FileSystem fs = FileSystem.get(this.getConf()); if(fs.exists(outputPath)){ fs.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 WordCountUserSort(), args); System.exit(status); } public static class WCMapper extends Mapper<LongWritable, Text,Text, IntWritable> { //输出的K2 Text outputKey = new Text(); //输出的V2 IntWritable outputValue = new IntWritable(1); /** * 每条KV调用一次map * @param key:行的偏移量 * @param value:行的内容 * @param context * @throws IOException * @throws InterruptedException */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //将每行的内容分割得到每个单词 String[] words = value.toString().split("\\s+"); //迭代取出每个单词作为K2 for (String word : words) { //将当前的单词作为K2 this.outputKey.set(word); //将K2和V2传递到下一步 context.write(outputKey,outputValue); } } } public static class WCReducer extends Reducer<Text, IntWritable,Text, IntWritable> { //输出K3 // Text outputKey = new Text(); //输出V3 IntWritable outputValue = new IntWritable(); /** * 每一组调用一次 * @param key:单词 * @param values:所有相同单词对应的1 * @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) { //取出当前单词所有 1,进行累加 sum += value.get(); } //给V3赋值 this.outputValue.set(sum); //传递到下一步 context.write(key,this.outputValue); } } }
-
-
-
小结
- 如何实现自定义排序比较器?
- 继承WritableComparator
- 重写compare,代替compareTo
- 如何实现自定义排序比较器?
练习
-
练习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
-
方案一:封装一个JavaBean作为K2,V2为NullWritable
package com.itheima.mr.wordcount;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class JavaBean implements WritableComparable<JavaBean> {
private String first;
private String second;
public void setAll(String first, String second) {
this.setFirst(first);
this.setSecond(second);
}
@Override
public String toString() {
return first + "\t" + second;
}
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
public int compareTo(JavaBean o) {
int comp=this.first.compareTo(o.first);
if (comp==0){
return -this.second.compareTo(o.second);
}
return comp;
}
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(first);
dataOutput.writeUTF(second);
}
public void readFields(DataInput dataInput) throws IOException {
this.first=dataInput.readUTF();
this.second=dataInput.readUTF();
}
}
package com.itheima.mr.wordcount;
import org.apache.hadoop.io.IntWritable;
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;
public class MapWordCount extends Mapper<LongWritable, Text, JavaBean, NullWritable> {
JavaBean javaBean = new JavaBean();
NullWritable nullWritable = NullWritable.get();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String first = value.toString().split("\\s+")[0];
String second = value.toString().split("\\s+")[1];
javaBean.setAll(first, second);
context.write(javaBean, nullWritable);
}
}
package com.itheima.mr.wordcount;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class ReduceWordCount extends Reducer<JavaBean, NullWritable,JavaBean, NullWritable> {
@Override
protected void reduce(JavaBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
package com.itheima.mr.wordcount;
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.NullWritable;
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;
public class WordCount extends Configured implements Tool {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
int status = ToolRunner.run(conf, new WordCount(), args);
System.exit(status);
}
public int run(String[] args) throws Exception {
Job job = Job.getInstance(this.getConf(), "WorkCount");
job.setJarByClass(WordCount.class);
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.setInputPaths(job,new Path("C:\\Users\\User\\Desktop\\input\\aa.txt"));
job.setMapperClass(MapWordCount.class);
job.setMapOutputKeyClass(JavaBean.class);
job.setMapOutputValueClass(NullWritable.class);
// job.setPartitionerClass(HashPartitioner.class);
// job.setSortComparatorClass(null);
// job.setGroupingComparatorClass(null);
// job.setCombinerClass(null);
job.setReducerClass(ReduceWordCount.class);
job.setOutputKeyClass(JavaBean.class);
job.setOutputValueClass(NullWritable.class);
job.setOutputFormatClass(TextOutputFormat.class);
Path path = new Path("C:\\Users\\User\\Desktop\\output\\day09");
FileSystem fs = FileSystem.get(this.getConf());
if (fs.exists(path)){
fs.delete(path,true);
}
TextOutputFormat.setOutputPath(job,path);
//job.setNumReduceTasks(1);
return job.waitForCompletion(true)?0:-1;
}
}
方案二:用Text作为K2【字母】,Int作为V2【数字】
package com.itheima.mr.wordcount2;
import org.apache.hadoop.io.IntWritable;
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;
public class MapWordCount2 extends Mapper<LongWritable, Text, Text, IntWritable> {
Text outputKey = new Text();
IntWritable outputValue = new IntWritable();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String first=value.toString().split("\\s+")[0];
String second= value.toString().split("\\s+")[1];
this.outputKey.set(first);
this.outputValue.set(Integer.parseInt(second));
context.write(this.outputKey,this.outputValue);
}
}
package com.itheima.mr.wordcount2;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ReduceWordCount2 extends Reducer<Text, IntWritable,Text, IntWritable> {
IntWritable outputValue = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
List<Integer> list=new ArrayList<Integer>();
for (IntWritable value : values) {
list.add(value.get());
}
Collections.sort(list, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (Integer integer : list) {
outputValue.set(integer);
context.write(key,outputValue);
}
}
}
package com.itheima.mr.wordcount2;
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.NullWritable;
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;
public class WordCount2 extends Configured implements Tool {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
int status = ToolRunner.run(conf, new WordCount2(), args);
System.exit(status);
}
public int run(String[] args) throws Exception {
Job job = Job.getInstance(this.getConf(), "WorkCount2");
job.setJarByClass(WordCount2.class);
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.setInputPaths(job,new Path("C:\\Users\\User\\Desktop\\input\\aa.txt"));
job.setMapperClass(MapWordCount2.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// job.setPartitionerClass(HashPartitioner.class);
// job.setSortComparatorClass(null);
// job.setGroupingComparatorClass(null);
// job.setCombinerClass(null);
job.setReducerClass(ReduceWordCount2.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setOutputFormatClass(TextOutputFormat.class);
Path path = new Path("C:\\Users\\User\\Desktop\\output\\day091");
FileSystem fs = FileSystem.get(this.getConf());
if (fs.exists(path)){
fs.delete(path,true);
}
TextOutputFormat.setOutputPath(job,path);
//job.setNumReduceTasks(1);
return job.waitForCompletion(true)?0:-1;
}
}
-
练习2:基于二手房数据统计每个地区二手房的个数,平均单价,最高单价,最低单价
地区 个数 平均 最高 最低
-
练习3:基于搜狗数据统计每种搜索词出现的次数
-
演变的Wordcount程序
搜索词 次数
-
-
练习4:基于搜狗数据统计每个小时的UV
小时 UV
- UV:unique View:唯一访问用户数
- 通过用户id唯一标识,一个用户id标识一个用户
- 转换语义:统计每个小时用户id的个数
- UV:unique View:唯一访问用户数
附录一:MapReduce编程依赖
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
</dependencies>