实训笔记7.24
- 7.24笔记
- 一、Hadoop中MapReduce框架的使用原理和流程
- 1.1 涉及到一些框架核心组件
- 1.1.1 InputFotmat
- 1.1.2 MapTask
- 1.1. 3Partitioner
- 1.1.4 WritableComparable
- 1.1.5 Combiner(可选)
- 1.1.6 WritableComparator(GroupingComparator)
- 1.1.7 ReduceTask
- 1.1.8 OutputFormat
- 二、Hadoop中对于SequenceFile文件的支持和处理
- 2.1 SequenceFile文件中内容是由三部分组成的
- 2.1.1 Header
- 2.1.2 Record区域|block区域
- 2.1.3 同步标记sync-mark
- 2.2 SequenceFile文件的三种压缩方式
- 2.3 MapReduce中,可以处理SequenceFile文件,也可以将结果输出为SequenceFile文件,之所以MR可以处理这个文件,是因为MR提供了两个类
- 2.3.1 SequenceFileInputFormat
- 2.3.2 SequenceFileOutputFormat
- 三、MapReduce中的OutputFormat的自定义操作
- 四、MapReduce的特殊应用场景
- 4.1 使用MapReduce进行join操作
- 4.1.1 MR的join根据join数据的位置分为两种情况
- 4.1.2 第一种Join使用:Reduce端的Join操作
- 4.1.3 第二种join使用:map端的join操作
- 4.2 使用MapReduce的计数器
- 4.2.1 计数器是由三部分组成的
- 4.2.2 计数器的使用有两种方式
- 4.3 MapReduce做数据清洗
- 五、代码示例
7.24笔记
一、Hadoop中MapReduce框架的使用原理和流程
1.1 涉及到一些框架核心组件
1.1.1 InputFotmat
1.1.2 MapTask
1.1. 3Partitioner
1.1.4 WritableComparable
1.1.5 Combiner(可选)
1.1.6 WritableComparator(GroupingComparator)
1.1.7 ReduceTask
1.1.8 OutputFormat
二、Hadoop中对于SequenceFile文件的支持和处理
SequenceFile文件是Hadoop提供的一种比较的特殊的文件,文件中存储的是key-value的二进制数据,而且SequenceFile文件支持对存储的二进制key-value数据进行压缩,是大数据中比较常用的一种数据文件,在Spark和Flink、Hive中有很多的情况下都是使用SequenceFile文件格式进行数据的保存等操作。
SequenceFile文件因为存储的是key-value数据的二进制类型数据,因此文件支持value或者key为图片、视频、音频数据
2.1 SequenceFile文件中内容是由三部分组成的
2.1.1 Header
Header区域存放了文件中key-value的类型、以及key、value采用的压缩方式、压缩使用的算法规则,以及同步标识
2.1.2 Record区域|block区域
存放的就是key和value的二进制数据,如果指定了压缩,存储的就是key-value的二进制压缩数据
2.1.3 同步标记sync-mark
2.2 SequenceFile文件的三种压缩方式
- none:对key-value数据不进行压缩
- record:只对每一个key-value数据中的value数据进行压缩,key值不进行压缩
- block:对多个key-value数据进行压缩,key和value都会压缩
2.3 MapReduce中,可以处理SequenceFile文件,也可以将结果输出为SequenceFile文件,之所以MR可以处理这个文件,是因为MR提供了两个类
2.3.1 SequenceFileInputFormat
- 是InputFormat的一个实现类,专门用来读取SequenceFile文件格式的一个InputFormat类
- 读取数据的时候,文件中一个一个keyvalue依次读取,而且这个类可以根据sequenceFile文件中的Header头部信息,自动识别数据是否被压缩以及采集的压缩方法和压缩算法,如果数据是被压缩过的,使用header中提供的压缩算法进行解压缩操作
- 同时在SequneceFile文件的Header中,还指定了key和value的类型(类型是序列化类型的),那InputFormat还会根据序列化类型自动把key value解压缩之后的二进制数据自动转换成为对应的key-value数据类型
【注意】如果使用SequenceFileInputFormat,那么map阶段输入的key-value类型就是不确定的。
2.3.2 SequenceFileOutputFormat
- 是OutputFormat的一个实现类,支持可以把Reduce最终数据的结果以我们指定的压缩方法把数据输出成为SequenceFile文件格式
- 如果我们想要这个类帮助我们输出SequenceFile文件格式的数据,我们必须满足,MR输出的key-value必须实现了Hadoop的序列化机制
job.setOutputFormatClass(SequenceFileOutputFormat.class);
SequenceFileOutputFormat.setCompressOutput(job,true);
SequenceFileOutputFormat.setOutputCompressionType(job, SequenceFile.CompressionType.RECORD);
SequenceFileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);
三、MapReduce中的OutputFormat的自定义操作
默认情况下,mapreduce是掉用TextOutputFormat类将MR程序的输出数据写出到了文件当中,文件的格式默认是将key-value以\t分割,并且输出的文件名是part-m/r-xxxxx
除了TextOutputFormat之外,还有一个实现类SequenceFileOutputFormat,这个类是将文件以key-value的二进制形式进行输出,并且输出的二进制数据支持压缩,同时输出的文件名也是part-m/r-xxxxx
这两个实现类默认一个reduceTask只输出一个文件
在有些情况下,这两个实现类满足不了我们的输出需求,因此我们就得自定义InputFormat实现输出效果
- 自定义一个类继承FileOutputFormat类
- 重写getRecordWriter方法,方法需要返回一个RecordWriter的子类对象
四、MapReduce的特殊应用场景
4.1 使用MapReduce进行join操作
MapReduce可以对海量数据进行计算,但是有些情况下,计算的结果可能来自于多个文件,每个文件的数据格式是不一致,但是多个文件存在某种关联关系,类似于MySQL中外键关系,如果想计算这样的结果,MR程序也是支持的。这种计算我们称之为join计算。
4.1.1 MR的join根据join数据的位置分为两种情况
- Map端的Join操作
- Reduce端的join操作
4.1.2 第一种Join使用:Reduce端的Join操作
- 思维就是在map端将多个不同格式的文件全部读取到,然后根据不同文件的格式对数据进行切割,切割完成以后,将数据进行封装,然后以多个文件的共同字段当作key,剩余字段当作value发送给reduce
- reduce端根据共同的key值,把value数据进行聚合,聚合完成以后,进行多文件的join操作
- Reduce端的join存在的问题:非常容易出现数据倾斜问题
- 如果多个进行join的文件数据量相差过大,就非常容易出现数据倾斜问题 大文件join小文件容易出现这个问题
- 加入order,txt文件300M,product,txt 10m 如果采用的默认切片机制,那么这两个文件切成4片 order.txt 128M 128M 44M product.txt 10m
- Reduce阶段也能会出现数据倾斜问题,不同key值对应的数据量相差过大
4.1.3 第二种join使用:map端的join操作
-
map端的join适用于如果两个需要做join操作文件数据量相差过大的情况下,map端的join操作可以尽最大可能避免map端的数据倾斜问题的出现,如果使用map端的join的话,我们就不需要reduce阶段
-
map的join操作的核心逻辑是:将小文件缓存起来,大文件正常使用MR程序做切片做读取
在驱动程序中通过
job.addCacheFile(new URI("XXXXX"))
方法缓存小文件,小文件可以缓存无数个(小于100M)在mapper阶段的setup方法中通过
context.getCacheFiles
方法获取到缓存的文件,然后通过IO流读取小文件数据,在MapTask中使用Map集合把小文件缓存起来,缓存的时候以小文件和大文件的关联字段当作map集合的key值。
4.2 使用MapReduce的计数器
计数器是MR程序运行过程中提供的一种的特殊的计数机制,计数器可以帮助我们查看MR程序运行过程中的数据量的变化趋势或者是我们感兴趣的一些数据量的变化。
计数器在MR程序中自带了很多计数器,计数器只能累加整数类型的值,最后把计数器输出到我们的日志当中
4.2.1 计数器是由三部分组成的
-
计数器组:一个计数器组当中可以包含多个计数器
-
计数器:真正用来记录记录数的东西,计数器一般都是一个字符串的名字
-
计数器的值:计数器的值都是整数类型
计数器在map阶段和reduce阶段都有的,如果在map阶段写的计数器,是在map任务结束之后会输出,如果在reduce阶段使用的计数器,reduce阶段执行完成输出
4.2.2 计数器的使用有两种方式
- 直接使用字符串的形式进行操作
context,getCounter(String groupName,String counterName).increment(long num)
- 使用Java的枚举类的形式操作计数器
context.getCounter(enumObject).increment(long num)
计数器组的名字就是枚举类的类名 计数器的名字就是枚举类的对象名
计数器使用的时候,每一个MapTask或者ReduceTask单独输出它这个任务计数器的结果,等MR程序全部运行完成,计数器会把所有MapTask或者ReduceTask中相同的计数器结果累加起来,得到整个MR程序中计数器的结果。
合理利用计数器和查看计数器可以检测MR程序运行有没有数据倾斜问题的出现
4.3 MapReduce做数据清洗
有时候需要把一些数据中不合法,非法的数据通过MapReduce程序清洗过滤掉,因此数据只需要清洗掉即可,不需要做任何的聚合操作,所以一般涉及到数据清洗操作只需要mapper阶段即可,reduce阶段我们不需要。
如果需要过滤数据,只需要在mapepr阶段将读取到的数据按照指定的规则进行筛选,筛选符合条件的数据通过context.write写出,不符合要求的数据,只要不调用context,write方法自然而言就过滤掉了
五、代码示例
package com.sxuek.sequence.input;import com.sxuek.sequence.output.FlowBean;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.SequenceFileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;
import java.net.URI;/***将一个SequenceFile文件转换成为正常的文本文件输出*/
public class DemoDriver {public static void main(String[] args) throws Exception {Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(DemoDriver.class);job.setInputFormatClass(SequenceFileInputFormat.class);FileInputFormat.setInputPaths(job,new Path("/demoOutput/part-m-00000"));job.setMapperClass(DemoMapper.class);job.setOutputKeyClass(Text.class);job.setOutputValueClass(FlowBean.class);job.setNumReduceTasks(0);Path path = new Path("/output3");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}class DemoMapper extends Mapper<Text,FlowBean,Text,FlowBean>{@Overrideprotected void map(Text key, FlowBean value, Mapper<Text, FlowBean, Text, FlowBean>.Context context) throws IOException, InterruptedException {context.write(key,value);}
}
package com.sxuek.sequence.output;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.*;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.URI;/*** 13502468823 7335 110349 117684* 13925057413 11058 48243 59301* 13560436666 3597 25635 29232* 通过MR程序实现将上述数据中转换成为sequenceFile格式* 其中SequenceFile文件中,以手机号为key,以上行流量\t下行流量\t总流量为value进行保存* 而且要求只对value进行压缩*/
public class DemoDriver {public static void main(String[] args) throws Exception {Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(DemoDriver.class);FileInputFormat.setInputPaths(job,new Path("/output1/part-r-00000"));job.setMapperClass(DemoMapper.class);job.setOutputKeyClass(Text.class);job.setOutputValueClass(FlowBean.class);job.setNumReduceTasks(0);Path path = new Path("/demoOutput");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}job.setOutputFormatClass(SequenceFileOutputFormat.class);SequenceFileOutputFormat.setCompressOutput(job,true);SequenceFileOutputFormat.setOutputCompressionType(job, SequenceFile.CompressionType.RECORD);SequenceFileOutputFormat.setOutputCompressorClass(job, GzipCodec.class);FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}class DemoMapper extends Mapper<LongWritable, Text,Text,FlowBean>{@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, FlowBean>.Context context) throws IOException, InterruptedException {String line = value.toString();String[] array = line.split("\t");String phoneNumber = array[0];long upFlow = Long.parseLong(array[1]);long downFlow = Long.parseLong(array[2]);long sumFlow = Long.parseLong(array[3]);FlowBean flowBean = new FlowBean(upFlow,downFlow,sumFlow);context.write(new Text(phoneNumber),flowBean);}
}
package com.sxuek.sequence.output;import org.apache.hadoop.io.Writable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;public class FlowBean implements Writable {private Long upFlow;private Long downFlow;private Long sumFlow;public FlowBean() {}public FlowBean(Long upFlow, Long downFlow, Long sumFlow) {this.upFlow = upFlow;this.downFlow = downFlow;this.sumFlow = sumFlow;}@Overridepublic String toString() {return upFlow + "\t" + downFlow + "\t" + sumFlow;}public Long getUpFlow() {return upFlow;}public void setUpFlow(Long upFlow) {this.upFlow = upFlow;}public Long getDownFlow() {return downFlow;}public void setDownFlow(Long downFlow) {this.downFlow = downFlow;}public Long getSumFlow() {return sumFlow;}public void setSumFlow(Long sumFlow) {this.sumFlow = sumFlow;}@Overridepublic void write(DataOutput out) throws IOException {out.writeLong(upFlow);out.writeLong(downFlow);out.writeLong(sumFlow);}@Overridepublic void readFields(DataInput in) throws IOException {upFlow = in.readLong();downFlow = in.readLong();sumFlow = in.readLong();}
}
package com.sxuek.customoutput;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.*;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.PathOutputCommitterFactory;import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;/*** 实现单词计数案例,并且要求整个MR程序中只能有一个分区,一个reduceTask* 但是我们要求你要将统计的单词计数结果,首字母大写的单词输出到upper.txt文件* 首字母小写的单词输出到lower.txt文件中** 案例:一个reduceTask任务输出两个文件,两个文件名不是part-r/m-xxxxx*/
public class WCDriver {public static void main(String[] args) throws Exception{Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(WCDriver.class);FileInputFormat.setInputPaths(job,new Path("/wordcount.txt"));job.setMapperClass(WCMapper.class);job.setMapOutputKeyClass(Text.class);job.setMapOutputValueClass(LongWritable.class);job.setReducerClass(WCReducer.class);job.setOutputKeyClass(Text.class);job.setOutputValueClass(LongWritable.class);job.setNumReduceTasks(1);Path path = new Path("/output4");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}job.setOutputFormatClass(WCOutputFormat.class);FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}class WCMapper extends Mapper<LongWritable, Text,Text,LongWritable>{@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, LongWritable>.Context context) throws IOException, InterruptedException {String line = value.toString();String[] words = line.split(" ");for (String word : words) {context.write(new Text(word),new LongWritable(1L));}}
}
class WCReducer extends Reducer<Text,LongWritable,Text,LongWritable>{@Overrideprotected void reduce(Text key, Iterable<LongWritable> values, Reducer<Text, LongWritable, Text, LongWritable>.Context context) throws IOException, InterruptedException {long sum = 0L;for (LongWritable value : values) {sum += value.get();}context.write(key,new LongWritable(sum));}
}/*** 自定义一个OutputFormat类实现数据的输出规则:* 首字母大写的单词 输出到upper.txt文件* 首字母小写的单词 输出到lower.txt文件*/
class WCOutputFormat extends FileOutputFormat<Text,LongWritable>{/*** 如何输出数据的核心代码逻辑* @param context context是一个MR程序运行的上下文对象 可以通过上下文对象获取job的所有Configuration配置* @return* @throws IOException* @throws InterruptedException*/@Overridepublic RecordWriter<Text, LongWritable> getRecordWriter(TaskAttemptContext context) throws IOException, InterruptedException {/*** 内部类的方式创建一个RecordWriter的实现类 当作返回值返回*/class WCRecordWriter extends RecordWriter<Text,LongWritable>{private FSDataOutputStream stream1; //这个输出IO流用来连接HDFS的上upper.txt文件private FSDataOutputStream stream2;//这个输出IO流用来连接HDFS上的lower.txt文件public WCRecordWriter() throws URISyntaxException, IOException, InterruptedException {this(context);}public WCRecordWriter(TaskAttemptContext context) throws URISyntaxException, IOException, InterruptedException {//这一行代码代表获取MR程序的所有配置对象Configuration configuration = context.getConfiguration();String hdfsAddress = configuration.get("fs.defaultFS");FileSystem fileSystem = FileSystem.get(new URI(hdfsAddress), configuration, "root");/*** 创建和HDFS的Io流的时候,两个文件输出到Driver驱动程序中指定的输出目录下。输出目录按道理来说不能自己手动写* 应该获取Driver设置的输出目录*/String outputDir = configuration.get(FileOutputFormat.OUTDIR);stream1 = fileSystem.create(new Path(outputDir+"/upper.txt"));stream2 = fileSystem.create(new Path(outputDir+"/lower.txt"));}/*** 如何写出数据,写出数据必须是两个文件* @param key the key to write.* @param value the value to write.* @throws IOException* @throws InterruptedException*/@Overridepublic void write(Text key, LongWritable value) throws IOException, InterruptedException {String word = key.toString();char c = word.charAt(0);if (c >=65 && c <=90){String line = key.toString()+"="+value.get()+"\n";stream1.write(line.getBytes());stream1.flush();}else{String line = key.toString()+"="+value.get()+"\n";stream2.write(line.getBytes());stream2.flush();}}@Overridepublic void close(TaskAttemptContext context) throws IOException, InterruptedException {stream1.close();stream2.close();}}try {return new WCRecordWriter(context);} catch (URISyntaxException e) {throw new RuntimeException(e);}}
}
package com.sxuek.join.map;import com.sxuek.join.reduce.OrderProductBean;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;/*** Map端的join操作:* 核心逻辑:在MR执行的时候,将小文件在内存中缓存起来,然后map阶段从缓存当中把缓存的小文件读取到,将小文件数据* 在内存保存起来,然后大文件正常使用MR程序进行切片读取,map方法每读取到一个大文件中一行数据,将这一行数据* 的关联字段获取到,然后根据关联字段从map缓存的小文件数据中获取对应的数据添加上。*/
public class SecondDriver {public static void main(String[] args) throws Exception{Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(SecondDriver.class);/*** 输入文件只输入大文件 order.txt 小文件不这样输入,因为小文件这样输入会产生小切片,小切片产生了会导致数据倾斜问题*/FileInputFormat.setInputPaths(job,new Path("/join/order.txt"));//将小文件缓存起来,传递小文件的路径job.addCacheFile(new URI("hdfs://192.168.68.101:9000/join/product.txt"));job.setMapperClass(SecondMapper.class);job.setOutputKeyClass(NullWritable.class);job.setOutputValueClass(OrderProductBean.class);job.setNumReduceTasks(0);Path path = new Path("/mapOutput");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}/*** 做map端的join 最核心的逻辑就是 在map方法读取大文件数据之前,先从缓存中把小文件获取到,然后把小文件中数据先保存起来* 保存的时候以key-value的形式保存 key是大小文件的关联字段,value是剩余的数据** Mapper中除了map方法以外 还有一个方法setup方法 setup方法会在map方法执行之前执行,而且只会执行依次*/
class SecondMapper extends Mapper<LongWritable, Text, NullWritable, OrderProductBean>{private Map<String,String> products = new HashMap<>();//缓存的产品信息的属性/*** setup方法每一个maptask只会执行依次,在map方法执行之前执行的* @param context* @throws IOException* @throws InterruptedException*/@Overrideprotected void setup(Mapper<LongWritable, Text, NullWritable, OrderProductBean>.Context context) throws IOException, InterruptedException {//缓存的小文件可以有多个URI[] cacheFiles = context.getCacheFiles();URI uri = cacheFiles[0];String path = uri.getPath();BufferedReader br = null;try {FileSystem fileSystem = FileSystem.get(new URI(context.getConfiguration().get("fs.defaultFS")), context.getConfiguration(), "root");FSDataInputStream inputStream = fileSystem.open(new Path(path));br = new BufferedReader(new InputStreamReader(inputStream));String line = null;while ((line = br.readLine()) != null){String[] array = line.split(" ");String pid = array[0];String pName = array[1];products.put(pid,pName);}} catch (URISyntaxException e) {throw new RuntimeException(e);}finally {if (br != null){br.close();}}}@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, NullWritable, OrderProductBean>.Context context) throws IOException, InterruptedException {String line = value.toString();String[] array = line.split("\t");String orderId = array[0];String pId = array[1];int account = Integer.parseInt(array[2]);//根据订单数据中的产品id去缓存中获取产品名 然后将得到的产品名和订单的其余信息封装输出 join完成String pName = products.get(pId);OrderProductBean productBean = new OrderProductBean(orderId,pId,pName,account);context.write(NullWritable.get(),productBean);}
}
package com.sxuek.join.reduce;import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.conf.Configuration;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;/*** MR的第一种join方式:reduce端的join* 思维:* 1、通过map阶段读取两个文件的数据* 2、map阶段先获取当前行kv到切片数据对应的文件,然后根据文件进行不同方式的切割* 3、然后对切割的数据进行封装(将数据传输到reduce进行聚合的),如果要在reduce端做join操作* 需要在map端输出数据时,以两个文件的关联字段当作key值进行传输,以两个文件的剩余字段当作value传输** 自定义JavaBean,JavaBean包含两个文件的所有字段,同时还需要包含一个标识字段(数据来自于哪个文件的),* 然后使用JavaBean封装两个文件的不同数据。*/
public class FirstDriver {public static void main(String[] args) throws Exception{Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(FirstDriver.class);FileInputFormat.setInputPaths(job,new Path("/join"));job.setMapperClass(FirstMapper.class);job.setMapOutputKeyClass(Text.class);job.setMapOutputValueClass(OrderProductBean.class);job.setReducerClass(FirstReducer.class);job.setOutputKeyClass(OrderProductBean.class);job.setOutputValueClass(NullWritable.class);job.setNumReduceTasks(1);Path path = new Path("/joinOutput");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}class FirstMapper extends Mapper<LongWritable, Text,Text,OrderProductBean>{/*** map方法读取的每一行的kv数据,kv数据可能是订单文件的数据,也可能是商品文件的数据* @param key* @param value* @param context 上下文对象 context也可以获取每一个kv对应的切片中文件名* @throws IOException* @throws InterruptedException*/@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, OrderProductBean>.Context context) throws IOException, InterruptedException {//代表获取当前kv数据的切片FileSplit fileSplit = (FileSplit) context.getInputSplit();//获取kv数据在切片中属于哪个文件的Path path = fileSplit.getPath();//拿到文件的名字String name = path.getName();String line = value.toString();//if如果属于订单文件数据,如何切割 如何封装if (name.equals("order.txt")){String[] array = line.split("\t");System.out.println(Arrays.toString(array));String orderId = array[0];String pId = array[1];int account = Integer.parseInt(array[2]);OrderProductBean bean = new OrderProductBean(orderId,pId,account,"order");context.write(new Text(pId),bean);}else{//else代表是如果是商品文件,如何切割 如何封装String[] array = line.split(" ");System.out.println(Arrays.toString(array));String pId = array[0];String pName = array[1];OrderProductBean bean = new OrderProductBean(pId,pName,"product");context.write(new Text(pId),bean);}}
}/*** reduce端就是根据pid把订单表和商品表对应的信息聚合起来,聚合起来的结果肯定某一件商品的订单信息和商品信息* key values* p001 o001,p001,10,order p001,小米,product* p002 o001,poo2,5,order o002,p002,1,order p002,自行车,product*/
class FirstReducer extends Reducer<Text,OrderProductBean,OrderProductBean, NullWritable>{@Overrideprotected void reduce(Text key, Iterable<OrderProductBean> values, Reducer<Text, OrderProductBean, OrderProductBean, NullWritable>.Context context) throws IOException, InterruptedException {//放当前商品id对应的所有的订单信息List<OrderProductBean> orders = new ArrayList<>();//当前商品的商品信息OrderProductBean productBean = new OrderProductBean();/*** MapReduce当中,values集合中的bean都是同一个bean,* 如果要把values的bean加到一个集合中,我们需要创建一个全新的bean,把values中bean的数据* 复制到全新的bean当中 然后全新的bean加到集合中 这样的话不会出现数据错乱*/for (OrderProductBean bean : values) {if (bean.getFlag().equals("order")){OrderProductBean orderProductBean = new OrderProductBean();try {//BeanUtils是apache提供的一个工具类,工具类实现把一个Java对象的属性复制到另外一个Java对象中BeanUtils.copyProperties(orderProductBean,bean);orders.add(orderProductBean);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}}else{try {BeanUtils.copyProperties(productBean,bean);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);}}}for (OrderProductBean order : orders) {order.setpName(productBean.getpName());context.write(order,NullWritable.get());}}
}
package com.sxuek.join.reduce;import org.apache.hadoop.io.Writable;import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;/*** JavaBean是用来封装两个不同文件的数据的* JavaBean包含两个文件的所有字段*/
public class OrderProductBean implements Writable {private String orderId ="";private String pId ="";private String pName ="";private Integer account= 0;private String flag; //代表的是一个标识,标识是用来标识JavaBean封装的是订单数据还是商品数据public OrderProductBean() {}public OrderProductBean(String orderId, String pId, String pName, Integer account) {this.orderId = orderId;this.pId = pId;this.pName = pName;this.account = account;}/*** 专门是用来封装订单数据文件信息的* @param orderId* @param pId* @param account* @param flag*/public OrderProductBean(String orderId, String pId, Integer account, String flag) {this.orderId = orderId;this.pId = pId;this.account = account;this.flag = flag;}/*** 专门用来封装商品信息数据的* @param pId* @param pName* @param flag*/public OrderProductBean(String pId, String pName, String flag) {this.pId = pId;this.pName = pName;this.flag = flag;}public String getOrderId() {return orderId;}public void setOrderId(String orderId) {this.orderId = orderId;}public String getpId() {return pId;}public void setpId(String pId) {this.pId = pId;}public String getpName() {return pName;}public void setpName(String pName) {this.pName = pName;}public Integer getAccount() {return account;}public void setAccount(Integer account) {this.account = account;}public String getFlag() {return flag;}public void setFlag(String flag) {this.flag = flag;}@Overridepublic String toString() {return orderId + "\t" + pId + "\t" + pName + "\t" + account;}@Overridepublic void write(DataOutput out) throws IOException {out.writeUTF(orderId);out.writeUTF(pId);out.writeUTF(pName);out.writeInt(account);out.writeUTF(flag);}@Overridepublic void readFields(DataInput in) throws IOException {orderId = in.readUTF();pId = in.readUTF();pName = in.readUTF();account = in.readInt();flag = in.readUTF();}
}
package com.sxuek.join;/*** 现在有两个文件,第一个文件代表商品销售数据,另外一个文件代表商品的详细信息* 两个文件的内容分别如下:* 1、order.txt 订单文件---每一行数据的多个字段以\t分割* order_id-订单编号 pid--商品id account--商品的数量* o001 p001 10* o001 p002 5* o002 p003 11* o002 p002 1* 2、product.txt 商品文件---每一行数据的多个字段是以空格进行分割的* pid--商品id pname-商品的名字* p001 小米* p002 自行车* p003 电视机** 使用MR程序实现如下的效果展示 最终的结果每一行以\t分割的* order_id pid pname account* o001 p001 小米 10* o001 p002 自行车 5** 核心逻辑:借助MapReduce实现一种类似于MySQL的多表连接查询功能。* MR实现有两种方式:map端的join reduce端join*/
public class Introduction {
}
package com.sxuek.filter;import org.apache.hadoop.conf.Configuration;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;
import java.net.URI;/*** 单词文件中中包含大写字母H的单词全部过滤调用,只保留不包含大写字母H的单词* 输出的时候一个单词输出一行*/
public class FilterDriver {public static void main(String[] args) throws Exception{Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(FilterDriver.class);FileInputFormat.setInputPaths(job,new Path("/wordcount.txt"));job.setMapperClass(FileterMapper.class);job.setOutputKeyClass(NullWritable.class);job.setOutputValueClass(Text.class);job.setNumReduceTasks(0);Path path = new Path("/filter");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}}
class FileterMapper extends Mapper<LongWritable, Text, NullWritable,Text>{@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, NullWritable, Text>.Context context) throws IOException, InterruptedException {String line = value.toString();String[] words = line.split(" ");for (String word : words) {if (word.contains("H")){continue;}else{context.write(NullWritable.get(),new Text(word));}}}
}
package com.sxuek.counters;import com.sxuek.sequence.output.DemoDriver;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;
import java.net.URI;/*** 计算单词计数,要求最后在控制台能给我输出大小和小写单词各有多少个*/
public class DemoDiver {public static void main(String[] args) throws Exception{Configuration configuration = new Configuration();configuration.set("fs.defaultFS","hdfs://192.168.68.101:9000");Job job = Job.getInstance(configuration);job.setJarByClass(DemoDriver.class);FileInputFormat.setInputPaths(job,new Path("/wordcount.txt"));job.setMapperClass(DemoMapper.class);job.setMapOutputKeyClass(Text.class);job.setMapOutputValueClass(LongWritable.class);job.setReducerClass(DemoReducer.class);job.setOutputKeyClass(Text.class);job.setOutputValueClass(LongWritable.class);job.setNumReduceTasks(2);Path path = new Path("/wcoutput");FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.68.101:9000"), configuration, "root");if (fileSystem.exists(path)){fileSystem.delete(path,true);}FileOutputFormat.setOutputPath(job,path);boolean b = job.waitForCompletion(true);System.exit(b?0:1);}
}class DemoMapper extends Mapper<LongWritable, Text,Text,LongWritable> {@Overrideprotected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, LongWritable>.Context context) throws IOException, InterruptedException {String line = value.toString();String[] words = line.split(" ");for (String word : words) {context.write(new Text(word),new LongWritable(1L));}}
}
class DemoReducer extends Reducer<Text,LongWritable,Text,LongWritable> {@Overrideprotected void reduce(Text key, Iterable<LongWritable> values, Reducer<Text, LongWritable, Text, LongWritable>.Context context) throws IOException, InterruptedException {/*** 使用计数器在reduce阶段判断大小和小写单词出现的次数* 毕竟reduce把同一个单词聚合起来 如果map阶段计数 计数数字肯定不准确*/String word = key.toString();char c = word.charAt(0);if (c >= 65 && c <= 90){//累加器的使用 就一行代码
// context.getCounter("MyCounters","upperCount").increment(1);context.getCounter(MyCounters.UPPERCOUNT).increment(1);}else{
// context.getCounter("MyCounters","lowerCount").increment(1);context.getCounter(MyCounters.LOWERCOUNT).increment(1);}long sum = 0L;for (LongWritable value : values) {sum += value.get();}context.write(key,new LongWritable(sum));}
}
enum MyCounters{UPPERCOUNT,LOWERCOUNT;
}