八斗30_day02_作业
作业1:理解前10个案例代码逻辑
01_WordCount:
1. Hadoop MapReduce 程序中的Driver部分,即将所有组件(如 Mapper、Reducer、Combiner等)整合起来的主要逻辑部分。
2.MapReduce 程序的 Mapper 会将输入数据中的每一行转换成一个键值对 (Text, LongWritable)。
3.这些键值对会被传递到 Reduce 阶段进行进一步处理。在Reduce阶段中,它会以单词为键,对应的值列表为值,合并相同单词出现次数,并输出对应的 (Text, LongWritable) 类型的键值对。
Context,表示 MapReduce 程序执行期间的上下文信息。它通过 context.write() 方法将 Mapper 的输出数据传递给 Reducer 阶段。具体来说,Context 包含了以下常用方法:
1.context.write(K key, V value):将 Mapper 的输出键值对以 (K, V) 的形式写入上下文中。
2.context.getConfiguration():获取 Hadoop 配置信息,如文件系统类型、副本数、存储路径等。
3.context.getInputSplit():获取当前输入数据所在的分片信息。
4.context.getCounter(Enum<?> counterName):用来获取计数器信息,可以用来监控程序的执行情况。
5.context.getCacheFiles():获取程序缓存的文件。
【代码】
WordMapper.java
1 //mapper进程,每一个split(block)会启动该类, 2 public class wordMapper extends Mapper<LongWritable,Text,Text,LongWritable>{ 3 // Mapper<LongWritable,Text,Text,LongWritable> 4 // map_in_key ,map_in_value: LongWritable,Text(String) 偏移量,Harry hung back for a last word with Ron and Hermione. 5 // map_out_key ,map_out_value:Text,LongWritable : Harry,1 hung,1 6 @Override 7 // map,是一条一条执行,只针对一条数据 8 protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { 9 // LongWritable key: 指的是偏移量。类型为 LongWritable,表示当前输入记录的偏移量,它对应于输入文件中每一行数据的行号或字节偏移量。 10 // Text value: 每一行的内容,类型为 Text,表示输入数据中的一行文本,它包含了行内所有的信息和字符。需要将这个 Text 类型的 value 转换为字符串进行后续处理。 11 // Context context:上下文 负责管理的读取hdfs 数据信息,计算上下文;类型为 Context,表示 MapReduce 程序执行期间的上下文信息。它通过 context.write() 方法将 Mapper 的输出数据传递给 Reducer 阶段。 12 13 // value = Harry hung back for a last word with Ron and Hermione. 14 // 1.每行读取文字,变成java的string 15 String line = value.toString(); 16 17 // 2.业务切分每个单词,切分为字符串数组 18 // [Harry,hung,back,for,a,last,word,with,Ron,and,Hermione] 19 String[] data = line.split(" "); 20 21 // 3.遍历字符串数组,然后一步一步输出(word,1) 22 for (String word: 23 data 24 ) { 25 //word,1 ----->key = word,value = 1 26 context.write(new Text(word),new LongWritable(1)); 27 } 28 }
WordReducer.java
1 public class wordReducer extends Reducer<Text,LongWritable,Text,LongWritable>{ 2 //// block1 word1:3,word2:91 3 //// block2 word1:153,word2:191 4 // 5 @Override 6 protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException { 7 // 今天,[1,1,1,1,1,1,1,1] values 为 Iterable 集合类型,它包含了该单词在 Mapper 输出中的所有出现次数。 8 long count = 0; 9 10 for (LongWritable v: values) { 11 count+=v.get(); 12 } 13 14 // 八斗,15 15 context.write(new Text(key),new LongWritable(count)); 16 /* 17 Reducer 阶段的输出(即 MapReduce 作业的最终结果)会被写入 HDFS 或其他存储系统中, 18 可以使用 Hadoop 提供的 FileSystem API 来从外部程序中访问和处理这些结果。 19 */ 20 } 21 }
WordCountDriver.java
1 public class WordCountDriver { 2 public static void main(String[] args) throws Exception { 3 // hadoop jar study_demo.jar hadoop_test.word_count_demo_01.WordCountDriver 4 System.setProperty("HADOOP_USER_NAME","root"); 5 Configuration conf = new Configuration(); 6 Job job = Job.getInstance(conf); 7 8 9 //程序入口,driver类 ,设置mr作业的jar包,以及对应的主类 10 job.setJarByClass(WordCountDriver.class); 11 //设置mapper的类(蓝图,人类,鸟类),实例(李冰冰,鹦鹉2号),对象 12 //指定了 Mapper 类和 Reducer 类的类别和实例。 13 job.setMapperClass(wordMapper.class); 14 //设置mapper的输出键和值的类型 15 job.setMapOutputKeyClass(Text.class); 16 job.setMapOutputValueClass(LongWritable.class); 17 18 job.setReducerClass(wordReducer.class); 19 job.setOutputKeyClass(Text.class); 20 job.setOutputValueClass(LongWritable.class); 21 22 // 需要指定combine的实现类,不用指定输出key和value类型 23 // job.setCombinerClass(WordCountCombine.class); 24 // 输入文件 25 FileInputFormat.setInputPaths(job, new Path("/badou30/day01/acticle.txt")); 26 /* 27 在函数内部,首先调用 FileSystem.get(conf) 方法获取一个指向 Hadoop 分布式文件系统的 FileSystem 实例, 28 然后使用 fs.exists(new Path(path)) 方法检查给定路径下的文件或目录是否存在,并返回其布尔值结果。 29 */ 30 if( Utils_hadoop.testExist(conf,"/badou30/day01/word_count/word_count_result")){ 31 Utils_hadoop.rmDir(conf,"/badou30/day01/word_count/word_count_result");} 32 FileOutputFormat.setOutputPath(job, new Path("/badou30/day01/word_count/word_count_result")); 33 job.waitForCompletion(true); 34 /* 35 这行代码的作用是提交 MapReduce 作业并等待其完成。 36 具体来说,它将作业提交到 Hadoop 集群进行执行,并阻塞当前线程,直到作业完成或发生错误。 37 如果作业成功执行完毕,则返回 true,否则返回 false。 38 39 注意,job.waitForCompletion(true) 方法会将 Hadoop 集群上下文中的所有信息传输到客户端并进行汇总, 40 因此它可能会消耗相当大的网络带宽和计算资源。 41 为了避免这种情况,我们可以将其设置为 false,然后使用 Hadoop 的 Counter 对象来获取作业执行过程中的计数器信息。 42 这样可以显著减少网络传输量和资源消耗。 43 */ 44 45 } 46 }
WordCountCombine.java
1 public class WordCountCombine extends Reducer<Text,LongWritable,Text,LongWritable>{ 2 //Reducer<key_in,value_in,key_out,value_out> 3 // key_in,value_in 为map端的输出 4 // key_out,value_out 为reduce端的输入 5 // 这个就是做了局部(本机器下的map汇总)的汇总 6 @Override 7 protected void reduce(Text key, Iterable<LongWritable> values, 8 Context context) throws IOException, InterruptedException { 9 long result=0; 10 for(LongWritable value:values){ 11 result=result+value.get(); 12 } 13 // block1 word1:3,word2:91 14 // block2 word1:153,word2:191 15 context.write(key, new LongWritable(result)); 16 } 17 }
02_对ip数据进行去重 : map阶段将IP值映射成key,reduce阶段将同一个key的值进行聚合。即达到对ip数据去重。
03_分组求平均值 : mapper阶段:将每一行的数据中的班级设置为key,分数作为value;reducer阶段:按key求和求平均。
04_求最大最小值 : map:key=year,value=temp,reduce:根据key找每一年的最大温度和最小温度。
05_序列化机制:集群工作过程中,需要用到RPC通信,所以MR处理的对象必须可以进行序列化/反序列操作。Hadoop利用的是avro实现的序列化和反序列,并且在其基础上提供了便捷的API要序列化的对象必要实现相关的接口。
1.序列化:数据做压缩,方便传输,json,proto,picke。 map到reduce的传输过程:就是序列化传输。
2.反序列化:读取序列化数据的过程。
3.在 MapReduce 程序中,如果需要在Map和Reduce阶段之间传递复杂类型的数据,如自定义类或数组等,就需要将它们序列化并转换成可读写的字节码数据。在Hadoop中,我们可以通过实现Writable接口,并重写write()和readFields()方法来实现序列化和反序列化过程。
实现序列化和反序列化代码:FlowBean.java
1 public class FlowBean implements Writable{ 2 3 private String phone; 4 private String add; 5 private String name; 6 private long consum; 7 8 // 序列化 9 @Override 10 public void write(DataOutput out) throws IOException { 11 12 out.writeUTF(phone); 13 out.writeUTF(add); 14 out.writeUTF(name); 15 out.writeLong(consum); 16 } 17 /* 18 在序列化过程中,首先调用 write() 方法,并使用 DataOutput 输出流对上述四个属性进行写操作。 19 注意,Hadoop的DataOutput流是类似于 Java 中的 DataOutputStream 的二进制输出流, 20 它可以将 Java 基本类型与 String 类型的数据转换为可读的二进制码。 21 在本例中,out.writeUTF()方法用于将字符串类型的 phone、add、name 写入输出流中, 22 out.writeLong()方法用于将长整型数据consum写入输出流中。 23 */ 24 25 // 反序列化,跟序列化的顺序不能改变 26 @Override 27 public void readFields(DataInput in) throws IOException { 28 this.phone=in.readUTF(); 29 this.add=in.readUTF(); 30 this.name=in.readUTF(); 31 this.consum=in.readLong(); 32 } 33 /* 34 在反序列化过程中,调用 readFields() 方法,并使用 DataInput 输入流对上述四个属性进行读取操作,以还原出原始的 Bean 对象。 35 注意,Hadoop 的 DataInput 流是类似于 Java 中的 DataInputStream 的二进制输入流,它可以从字节码数据中读取 Java 基本类型和 String 类型的数据。 36 */ 37 38 public String getPhone() { 39 return phone; 40 } 41 42 public String getAdd() { 43 return add; 44 } 45 46 public String getName() { 47 return name; 48 } 49 50 public long getConsum() { 51 return consum; 52 } 53 54 public void setPhone(String phone) { 55 this.phone = phone; 56 } 57 58 public void setAdd(String add) { 59 this.add = add; 60 } 61 62 public void setName(String name) { 63 this.name = name; 64 } 65 66 public void setConsum(long consum) { 67 this.consum = consum; 68 } 69 70 @Override 71 public String toString() { 72 return "FlowBean [phobe="+phone+",add="+add+",name="+name+",consum="+consum+"]"; 73 } 74 }
FlowMapper.java
1 public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> { 2 @Override 3 protected void map(LongWritable key, Text value, Context context) 4 throws IOException, InterruptedException { 5 // 13877779999 bj zs 2145 6 7 String line=value.toString(); 8 //#实例化 9 FlowBean flowBean=new FlowBean(); 10 11 //给一个实例的属性赋予初始值, 12 // 13877779999 13 flowBean.setPhone(line.split(" ")[0]); 14 // bj 15 flowBean.setAdd(line.split(" ")[1]); 16 // zs 17 flowBean.setName(line.split(" ")[2]); 18 flowBean.setConsum(Integer.parseInt(line.split(" ")[3])); 19 System.out.println(flowBean); 20 // zs 21 context.write(new Text(flowBean.getName()), flowBean); 22 } 23 }
FlowReducer.java
1 public class FlowReducer extends Reducer<Text, FlowBean,Text,FlowBean> { 2 3 @Override 4 protected void reduce(Text name, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException { 5 //重新实例化了一个类,他目的是计算用户消费总额 6 FlowBean tmp=new FlowBean(); 7 // 李四 [flowbean[phone=13766668888,add=sh,name=ls,consum=1000],flowbean[phobe=13766668888,add=BJ,name=ls,consum=21000]] 8 for(FlowBean flowbean:values){ 9 // flowbean FlowBean [phobe=13766668888,add=sh,name=ls,consum=9844] 10 // tmp.add=flowbean.getAdd() 11 // System.out.println("FLOABEAN"+flowbean); 12 tmp.setAdd(flowbean.getAdd()); 13 tmp.setPhone(flowbean.getPhone()); 14 tmp.setName(flowbean.getName()); 15 // tmp.getComsum(初始化是0)+flowbean.getConsum()[9844] 16 // 在第一轮tmp.getConsum()=[9844] 17 18 // 第二轮FlowBean [phobe=13766668888,add=sh,name=ls,consum=1000] 19 // tmp.Consum =tmp.getComsum(9844)+flowbean.getConsum()[100] 20 System.out.println(name+"金额"+tmp.getConsum()); 21 tmp.setConsum(tmp.getConsum()+flowbean.getConsum()); 22 23 } 24 context.write(name, tmp); 25 } 26 }
06_分区:
1.FlowBean.java:实现一个writable接口,并重写两个方法,write表示序列化,readFields表示反序列化
2.FlowMapper.java
3.FlowPartitioner.java:对addr进行分区:
1 //Text,FlowBean keymapput keyvalueout 2 public class FlowPartitioner extends Partitioner<Text,FlowBean> { 3 @Override 4 //<Text,FlowBean>指的是map的key,value 5 public int getPartition(Text key, FlowBean value, int numPartitions) { 6 //数据倾斜解决 7 // Random R = new Random(); 8 // 9 // String hash_key=key.toString()+String.valueOf(R.nextInt()); 10 // return (hash_key .hashCode() & Integer.MAX_VALUE) % numPartitions; 11 // if(value.getName().equals("wyf")){ 12 // Random R = new Random(); 13 //// 14 // String hash_key = key.toString()+String.valueOf(R.nextInt()); 15 // } 16 17 if(value.getAddr().equals("sh")){ 18 return 0; 19 } 20 if(value.getAddr().equals("bj")){ 21 return 1; 22 } 23 else{ 24 return 2; 25 } 26 } 27 28 }
4.FlowReducer.java
1 public class FlowReducer extends Reducer<Text, FlowBean,Text,FlowBean> { 2 3 @Override 4 protected void reduce(Text name, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException { 5 FlowBean tmp=new FlowBean(); 6 for(FlowBean flowbean:values){ 7 tmp.setAddr(flowbean.getAddr()); 8 tmp.setPhone(flowbean.getPhone()); 9 tmp.setName(flowbean.getName()); 10 tmp.setFlow(tmp.getFlow()+flowbean.getFlow()); //tmp.getFlow()初始值为0 11 } 12 context.write(name, tmp); 13 } 14 }
5.FlowDriver.java中增加:
1 //配置partition,只多不少 2 //job.setNumReduceTasks(4)指定了该任务的Reduce任务数为4个 3 job.setNumReduceTasks(4); 4 job.setPartitionerClass(FlowPartitioner.class); 5 //FlowPartitioner是一个自定义的Partitioner类,用于将Map阶段输出的key-value对进行分区操作, 6 //将相同key的数据发送到同一个Reduce任务中进行处理。
07_combiner:
合并的目的是减少Reduce端迭代的次数combiner是实现Mapper端进行key的归并,combiner具有类似本地的reduce功能。如果不用combiner,那么所有的结果都是reduce完成,效率会很低。使用combiner先做合并,然后发往reduce。
08_排序和全排序:
排序:
1.WritableComparable是Hadoop中的一个接口,用于定义可序列化和可比较的数据类型,以便在MapReduce任务中进行序列化和排序。
2.Movie.java:
/* Movie类还需要实现compareTo方法,实现在Reduce阶段对对象进行比较和排序的方式。 在该方法中,返回一个int型的值,表示该对象与o对象的大小关系,可以根据hot进行比较,将hot值大的对象排在前面。 */ @Override public int compareTo(Movie o) { return o.hot-this.hot; }
?由于Movie类实现了WritableComparable接口,并实现了compareTo()方法,因此在Reduce阶段会根据Movie对象的hot属性进行排序。
###Kmeans:
1.对于一些有明显划分逻辑的类不需要聚类,例如经纬度--->城市,年龄段---->少年,青年,中年,老年.
2.什么时候需要去聚类:我们没有先验信息,去对数据做划分.
3.map阶段:计算每个样本到聚类中心的聚类,然后进行比较距离大小(根据业务场景,需对距离进行归一化操作或者标准化),将样本划分到一个簇中.
4.reduce阶段:计算新的聚类中心.
5.Driver阶段:进行迭代.
[代码]
KmeansMap.java
1 public class KmeansMap extends Mapper<LongWritable, Text, Text, Text> { 2 List<ArrayList<String>> centers; 3 //setup()方法加载预置文件,提前把数据加载进来 4 /* 5 在setup()方法中,通过调用Util.getCenterFile()方法获取了预置的初始聚类中心, 6 并将其存储在List<ArrayList<String>>类型的centers变量中。打印出centers变量便于检查是否成功读取聚类中心数据。 7 */ 8 @Override 9 protected void setup(Context context) throws IOException, InterruptedException { 10 super.setup(context); 11 centers= Util.getCenterFile(DataSource.old_center+"/part-r-00000"); 12 //1,0.5483598064494786,0.5620634207706902,0.631409127357256 13 //2,0.3097815453182178,0.30933422990614073,0.2494839735711083 14 //3,0.10216864838289068,0.056547788616962635,0.0982705121497766 15 //4,0.8526661438398866,0.8877750526698496,0.8710574668862512 16 //5,0.9282123373771547,0.8518260893453832,0.8992078636901775 17 System.out.println(centers+"啊哈哈哈哈哈哈哈"); 18 } 19 //每个block块对应的map都有老聚类中心 20 public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { 21 // step 1。读取一行数据 22 String data = value.toString(); 23 // step2_similarity 数据切分获得元组 24 String[] tmpSplit = data.split(","); 25 // step3_ItemUser 申请一个字符列表 26 List<String> parameter = new ArrayList<>(); 27 // step4_predictScore 塞入一条数据 此时paramter第一条数据 第一个位置是用户id 28 for (int i = 0; i < tmpSplit.length; i++) { 29 parameter.add(tmpSplit[i]); 30 } 31 32 // step5_recommand_item 读取聚类中心点文件(其中路径参数是从命令中获取的) 33 34 // step6 计算目标对象到各个中心点的距离,找最大距离对应的中心点,则认为此对象归到该点中 35 String outKey="" ;// 默认聚类中心为0号中心点 36 double minDist = Double.MAX_VALUE; 37 // 遍历5个聚类中心 38 for (int i = 0; i < centers.size(); i++) { 39 double dist = 0; 40 // 计算一个样本跟一个聚类中心的距离 41 for(int j=1;j<centers.get(i).size();j++){ 42 double a=Double.parseDouble(parameter.get(j)); //parameter原始数据 43 double b=Double.parseDouble(centers.get(i).get(j)); 44 dist+=Math.pow(a-b,2); 45 46 } 47 if (dist < minDist) { 48 // outKey = centers.get(i).get(0);// 类编号 outKey = String.valueOf(i) 49 outKey = String.valueOf(i); 50 minDist = dist; 51 } 52 53 } 54 //value是每一行数据 55 System.out.println(value+"属于:"+outKey+"类"); 56 57 // 1,【0.5997964273824741,0.5381577086398976,0.630918833446788】 58 // 1,【0.5477960351179503,0.6806860650336256,0.671837028690025】 59 context.write(new Text(outKey), value); 60 } 61 }
KmeansReduce.java
1 public class KmeansReduce extends Reducer<Text, Text, Text,Text> { //计算新的聚类中心 2 public void reduce(Text key, Iterable<Text> value, Context context) throws IOException,InterruptedException{ 3 // 1 ,[[0.5997964273824741,0.5381577086398976,0.630918833446788],[0.6425046619670638,0.5793500048314731,0.6036807346762254],[0.6147865482689907,0.6099377890937322,0.5681208931012612]] 4 5 //key为类编号,value为类内所有样本,Iterable<Text> value,海量数据,类少的情况下 value是海量的 6 //combiner可以用到, 7 long num=0; 8 // 顶以一个k维度数,存储累加结果 9 double[] re=new double[DataSource.feat_num]; 10 11 // 遍历value计算累加值,并标记样本个数, 12 for(Text T:value){ 13 num++;//计算一个簇中样本个数 14 String onePoint=T.toString(); //T=[0.5997964273824741,0.5381577086398976,0.630918833446788]变成字符串 15 onePoint=onePoint.replace(",", " "); 16 String[] parameters=onePoint.split(" ");//0.5997964273824741 0.5381577086398976 0.630918833446788 17 //进行累加,此处仅有两个元素,如果多个元素该如何 18 for (int i = 1; i <parameters.length ; i++) { 19 re[i-1]+=Double.parseDouble(parameters[i]); //每列进行相加 20 } 21 22 } 23 String result=key.toString()+","; 24 for (int i = 0; i <re.length ; i++) { 25 result=result+re[i]/num+","; //算均值,然后类和值的字符串拼接 26 } 27 //将每个均值添加到result字符串中,并在其后添加逗号。最后, 28 // 使用字符串截取方法substring去掉最后一个多余的逗号,即 result.length()-1, 29 // 从而得到最终的字符串表示该簇的类和均值。 30 result.substring(0,result.length() -1); 31 context.write(key,new Text(result)); 32 } 33 }
KmeansDriver.java
1 package hadoop_test.kmeans_demo_13; 2 3 import org.apache.hadoop.conf.Configuration; 4 import org.apache.hadoop.fs.FileSystem; 5 import org.apache.hadoop.io.Text; 6 import org.apache.hadoop.mapreduce.Job; 7 import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; 8 import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; 9 10 public class KmeansDriver { 11 // 主函数 12 public static void main(String[] args) throws Exception { 13 int repeats = 0; 14 //进行迭代 15 do { 16 //每一轮都需要构造conf 17 Configuration conf = new Configuration(); 18 // 新建MapReduce作业并指定作业启动类 19 Job job = new Job(conf); 20 // 设置输入输出路径(输出路径需要额外加判断) 21 job.setJarByClass(KmeansDriver.class); 22 //1.输入数据路径 23 FileInputFormat.addInputPath(job, DataSource.inputpath);// 设置输入路径(指的是文件的输入路径) 24 // 25 FileSystem fs = DataSource.newCenter.getFileSystem(conf); 26 27 if (fs.exists(DataSource.newCenter)) {// 设置输出路径(指的是中心点的输出路径) 28 fs.delete(DataSource.newCenter, true); 29 } 30 31 FileOutputFormat.setOutputPath(job, DataSource.newCenter); 32 // 为作业设置map和reduce所在类 33 job.setMapperClass(KmeansMap.class); 34 job.setReducerClass(KmeansReduce.class); 35 // 设置输出键和值的类 36 job.setOutputKeyClass(Text.class); 37 job.setOutputValueClass(Text.class); 38 // 启动作业 39 job.waitForCompletion(true); 40 repeats++; 41 //迭代停止条件:1.迭代次数,2.聚类中心聚类阈值 42 } while (repeats < DataSource.REPEAT && (Util.isStop(DataSource.old_center+"/part-r-00000", 43 44 DataSource.new_center+"/part-r-00000", repeats, DataSource.threshold))); 45 46 // 进行最后的聚类工作(由map来完成)第二个map 47 Configuration c_conf = new Configuration(); 48 // 新建MapReduce作业并指定作业启动类 49 Job c_job = new Job(c_conf); 50 // 设置输入输出路径(输出路径需要额外加判断) 51 FileInputFormat.addInputPath(c_job, DataSource.inputpath);// 设置输入路径(指的是文件的输入路径) 52 FileSystem fs = DataSource.newCenter.getFileSystem(c_conf);// 设置输出路径(指的是中心点的输出路径) 53 if (fs.exists(DataSource.newCenter)) { 54 fs.delete(DataSource.newCenter, true); 55 } 56 FileOutputFormat.setOutputPath(c_job, DataSource.newCenter); 57 // 为作业设置map(没有reducer,则看到的输出结果为mapper的输出) 58 c_job.setMapperClass(KmeansMap.class); 59 // 设置输出键和值的类 60 c_job.setOutputKeyClass(Text.class); 61 c_job.setOutputValueClass(Text.class); 62 // 启动作业 63 c_job.waitForCompletion(true); 64 } 65 }
浙公网安备 33010602011771号