MapReduce
- MapReduce编程深入
- 知识点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的练习作业1
- 附录一:MapReduce编程依赖
苟有恒,何必三更眠五更起;
最无益,莫过一日曝十日寒。
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进行处理
-
-
-
分区规则
-
本质:给数据打标签
-
规则:默认的分区器调用的是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:方案一:拼接字符串
-
目标:实现通过拼接字符串解决多列传输及处理的问题
-
实施
-
代码
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 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中的数据传输
-
实现
-
自定义数据类型
package bigdata.itcast.cn.hadoop.mr.userbean; 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); } } }
-
结果
-
-
-
-
小结
- 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方法
- 规则
-
实现
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; } }
-
小结
- 如何实现自定义数据类型并实现排序接口?
- 实现: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
-
-
练习2:基于二手房数据统计每个地区二手房的个数,平均单价,最高单价,最低单价
地区 个数 平均 最高 最低
-
练习3:基于搜狗数据统计每种搜索词出现的次数
-
演变的Wordcount程序
搜索词 次数
-
-
练习4:基于搜狗数据统计每个小时的UV
小时 UV
- UV:unique View:唯一访问用户数
- 通过用户id唯一标识,一个用户id标识一个用户
- 转换语义:统计每个小时用户id的个数
- UV:unique View:唯一访问用户数
MapReduce的练习作业1
本题第二题用到reduce
其他三题可只用三大阶段(input-map-output)
现有如下数据
字段为: 门店名,营业额,开支额,年份
劲松店,600,350,2019年
劲松店,800,250,2020年
王府井店,1900,600,2020年
王府井店,2000,900,2019年
回龙观店,6700,1800,2020年
西单店,3000,1000,2019年
西单店,5000,1000,2020年
,3500,1000,2020年
牡丹园店,3800,1400,2020年
牡丹园店,2800,1300,2019年
西直门店,1500,900,2019年
太阳宫店,9000,3600,2019年
三里屯店,,1000,2020年
西直门店,3500,1000,2020年
太阳宫店,6000,4600,2020年
回龙观店,7500,2000,2019年
需求1:去除源文件中字段缺失的数据
本题只需要做一个判断是否为空白利用trim
String[] items = Value.toString().trim().split("’");
package exam1.exam1;
import exam2.FlowBean1;
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.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
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;
public class exam_drivers extends Configured implements Tool {
public int run(String[] args) throws Exception {
//构建
Job job = Job.getInstance(this.getConf(),"userwc");
job.setJarByClass(exam_drivers.class);
//配置
job.setInputFormatClass(TextInputFormat.class);
//使用程序的第一个参数作为输入
TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
//配置map
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
//配置shuffle
//job.setPartitionerClass(UserPartition.class);
//配置reducer
//job.setReducerClass(WCReducer.class);
//job.setOutputKeyClass(Text.class);
//job.setOutputValueClass(FlowBean1.class);
job.setNumReduceTasks(0);
job.setOutputFormatClass(TextOutputFormat.class);
//使用程序的第二个参数作为输出路径
Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\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 exam_drivers(), args);
System.exit(status);
}
static class WCMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
Text outputKey = new Text(); //K2
FlowBean1 outputValue = new FlowBean1(); //V2
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = value.toString().trim().split(",");
if (items.length != 4) {
return;
}
for (String item : items) {
if(item.trim().length() == 0){
return;
}
}
context.write(value, NullWritable.get());
}
}
}
需求2:按照不同年份将营业数据拆分到不同的文件中
这一问是利用Shuffle分区输出结果
设置 job.setPartitionerClass(UserPartition.class)
job.setNumReduceTasks(2);
exam_drivers类
package exam1.exam2;
import exam2.FlowBean1;
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.LongWritable;
import org.apache.hadoop.io.NullWritable;
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;
public class exam_drivers extends Configured implements Tool {
public int run(String[] args) throws Exception {
//构建
Job job = Job.getInstance(this.getConf(),"userwc");
job.setJarByClass(exam_drivers.class);
//配置
job.setInputFormatClass(TextInputFormat.class);
//使用程序的第一个参数作为输入
TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
//配置map
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
//配置shuffle
job.setPartitionerClass(UserPartition.class);
//配置reducer
job.setReducerClass(WCReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
job.setNumReduceTasks(2);
job.setOutputFormatClass(TextOutputFormat.class);
//使用程序的第二个参数作为输出路径
Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\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 exam_drivers(), args);
System.exit(status);
}
static class WCMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
NullWritable outputValue = NullWritable.get();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = value.toString().trim().split(",");
if (items.length != 4) {
return;
}
for (String item : items) {
if(item.trim().length() == 0){
return;
}
}
context.write(value,this.outputValue);
}
}
private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
}
UserPartition类
package exam1.exam2;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class UserPartition extends Partitioner<Text, NullWritable> {
public int getPartition(Text text, NullWritable nullWritable, int i) {
String year = text.toString().split(",")[3];
if(year.startsWith("2019")) {
return 0;
}else {
return 1;
}
}
}
输出的结果为两个目录
需求3:对每一年的营业数据按照净盈利排序(营业额-开支额)
封装JavaBean
每一列分别为门店名,营业额,开支额,年份,净盈利
多加一个净盈利
写出compareTo方法以及write和readFields方法
compareTo方法是和自己做对比
切记 write和readField的顺序要一致
UserBean类
package exam1.exam3;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class UserBean implements WritableComparable<UserBean> {
private String sname; //门店
private long totalInput;//总利润
private long totalOutput;//总支出
private String yearinfo;//年份
private long input;//净利润
public UserBean() {
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public long getTotalInput() {
return totalInput;
}
public void setTotalInput(long totalInput) {
this.totalInput = totalInput;
}
public long getTotalOutput() {
return totalOutput;
}
public void setTotalOutput(long totalOutput) {
this.totalOutput = totalOutput;
}
public String getYearinfo() {
return yearinfo;
}
public void setYearinfo(String yearinfo) {
this.yearinfo = yearinfo;
}
public long getInput() {
return input;
}
public void setInput(long input) {
this.input = input;
}
@Override
public String toString() {
return this.sname+"\t"+this.totalInput+"\t"+this.totalOutput+"\t"+this.yearinfo+"\t"+this.input;
}
public int compareTo(UserBean o) {
//按照净利润降序排序
return -Long.valueOf(this.getInput()).compareTo(Long.valueOf(o.getInput()));
}
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(this.sname);
dataOutput.writeLong(this.totalInput);
dataOutput.writeLong(this.totalOutput);
dataOutput.writeUTF(this.yearinfo);
dataOutput.writeLong(this.input);
}
public void readFields(DataInput dataInput) throws IOException {
this.sname = dataInput.readUTF();
this.totalInput = dataInput.readLong();
this.totalOutput = dataInput.readLong();
this.yearinfo = dataInput.readUTF();
this.input = dataInput.readLong();
}
}
exam_drivers类
package exam1.exam3;
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.LongWritable;
import org.apache.hadoop.io.NullWritable;
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;
public class exam_drivers extends Configured implements Tool {
public int run(String[] args) throws Exception {
//构建
Job job = Job.getInstance(this.getConf(),"userwc");
job.setJarByClass(exam_drivers.class);
//配置
job.setInputFormatClass(TextInputFormat.class);
//使用程序的第一个参数作为输入
TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
//配置map
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(UserBean.class);
job.setMapOutputValueClass(NullWritable.class);
//配置shuffle
//job.setPartitionerClass(UserPartition.class);
//配置reducer
//job.setReducerClass(WCReducer.class);
//job.setOutputKeyClass(Text.class);
//job.setOutputValueClass(NullWritable.class);
//job.setNumReduceTasks(2);
job.setOutputFormatClass(TextOutputFormat.class);
//使用程序的第二个参数作为输出路径
Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\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 exam_drivers(), args);
System.exit(status);
}
public static class WCMapper extends Mapper<LongWritable, Text, UserBean, NullWritable> {
UserBean outputKey = new UserBean();
NullWritable outputValue = NullWritable.get();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = value.toString().trim().split(",");
if (items.length != 4) {
return;
}
for (String item : items) {
if(item.trim().length() == 0){
return;
}
}
String sname = items[0];
long totalInput = Long.parseLong(items[1]);
long totalOutput = Long.parseLong(items[2]);
String Yearinfo = items[3];
long input = totalInput - totalOutput;
this.outputKey.setSname(sname);
this.outputKey.setTotalInput(totalInput);
this.outputKey.setTotalOutput(totalOutput);
this.outputKey.setYearinfo(Yearinfo);
this.outputKey.setInput(input);
//
context.write(this.outputKey,this.outputValue);
//
}
}
private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
}
输出结果
需求4:要求最后输出到文件的数据字段之间以‘\t’分割,后边加两个描述字段:净盈利额、盈利或者亏损标记
如:
王府井店 1900 600 2020年 1300 盈利
劲松店 800 950 2020年 -150 亏损
这个只需要在上题的Value中加上一个字段判断输出即可
package exam1.exam4;
import exam1.exam3.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.LongWritable;
import org.apache.hadoop.io.NullWritable;
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;
public class exam_drivers extends Configured implements Tool {
public int run(String[] args) throws Exception {
//构建
Job job = Job.getInstance(this.getConf(),"userwc");
job.setJarByClass(exam_drivers.class);
//配置
job.setInputFormatClass(TextInputFormat.class);
//使用程序的第一个参数作为输入
TextInputFormat.setInputPaths(job,new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\exam_26.txt"));
//配置map
job.setMapperClass(WCMapper.class);
job.setMapOutputKeyClass(UserBean.class);
job.setMapOutputValueClass(Text.class);
//配置shuffle
//job.setPartitionerClass(UserPartition.class);
//配置reducer
//job.setReducerClass(WCReducer.class);
//job.setOutputKeyClass(Text.class);
//job.setOutputValueClass(NullWritable.class);
//job.setNumReduceTasks(2);
job.setOutputFormatClass(TextOutputFormat.class);
//使用程序的第二个参数作为输出路径
Path outputPath = new Path("E:\\itheima_software\\exam\\exam_26\\src\\datas\\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 exam_drivers(), args);
System.exit(status);
}
public static class WCMapper extends Mapper<LongWritable, Text, UserBean, Text> {
UserBean outputKey = new UserBean();
Text outputValue = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = value.toString().trim().split(",");
if (items.length != 4) {
return;
}
for (String item : items) {
if(item.trim().length() == 0){
return;
}
}
String sname = items[0];
long totalInput = Long.parseLong(items[1]);
long totalOutput = Long.parseLong(items[2]);
String Yearinfo = items[3];
long input = totalInput - totalOutput;
this.outputKey.setSname(sname);
this.outputKey.setTotalInput(totalInput);
this.outputKey.setTotalOutput(totalOutput);
this.outputKey.setYearinfo(Yearinfo);
this.outputKey.setInput(input);
if(input > 0 ){
this.outputValue.set("盈利");
context.write(this.outputKey,this.outputValue);
}else if (input < 0 ){
this.outputValue.set("亏损");
context.write(this.outputKey,this.outputValue);
}
}
}
private static class WCReducer extends Reducer<Text, NullWritable, Text, NullWritable> {
@Override
protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}
}
输出结果:
附录一: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>