weather案例, 简单分析每年的前三个月的最高温即可, 使用自定义的分组和排序
设计分析
- 设定多个reduce
- 每年的数据都很多,如果按照默认情况处理,统计性能是非常慢(因为默认只有一个reduce),所以我们需要重新分配reduceTask,将一年的数据交给一个reduceTask处理,
- 分区
- 那个数据交给哪个reduceTask处理是有Patitioner决定(patition对每个map输出的数据分配置一个分区号这个分区号决定map输出数据送到那个reudeTask),
- 自定义分区
- 由于我们是将一年的数据交给一个reduce处理,但是默认分区是按照key.hashCode()的值 模 reduceTask数量得到分区号,所以我们需要重写分区,
-
自定义排序
- 由于我们是要每月最该的三个温度,所以需要对温度进行排序,所以在洗牌(shuffler)过程中自定义sort,
-
自定义分组
- 分组的目的:是按照实际的需求,将数据分成一个个组, 传给reduceTask,我们的需求是统计每年每月温度最高的三个,如果一组数据就是这一年的数据,我们对着一年的数据进行统计,是很复杂的,如果我们将每月的数据分成一个组,这样就会方便多了, 默认的分组是按照key是否相同进行分组,所以我们要自定义分组
- 自定义key
- 默认的partition是根据key的hashcode模reduceTask数量,得到分区号
- 默认的排序是根据key的字典排序
- 默认的分组是根据key相同,进行比较进行分组
- 这几个都与key与联系, 所以我们需要影响这些步骤的因素添加到key中,
- 根据上面分析,partition与年有关,sort与温度有关,分组和月份有关
- 总结:所以key中需要包含year, month, T
1, MyKey,
因为对温度进行分组, 排序, pardition操作, 所以默认的字典顺序不能满足需求
*** 自定义的key中的数据, 必须在构造中进行初始化, 否则报 NullpointException
package com.wenbronk.weather; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.WritableComparable; /** * 自定义key, 对key进行分组 * 实现writableComparble方法, 可序列化并比较是否同一个对象 * @author root * */ public class MyKey implements WritableComparable<MyKey> { private int year; private int month; private double hot; public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public double getHot() { return hot; } public void setHot(double hot) { this.hot = hot; } /** * 反序列化 */ @Override public void readFields(DataInput arg0) throws IOException { this.year = arg0.readInt(); this.month = arg0.readInt(); this.hot = arg0.readDouble(); } /** * 序列化 */ @Override public void write(DataOutput arg0) throws IOException { arg0.writeInt(year); arg0.writeInt(month); arg0.writeDouble(hot); } /** * 比较, 判断是否同一个对象, 当对象作为key时 */ @Override public int compareTo(MyKey o) { int c1 = Integer.compare(this.year, o.getYear()); if (c1 == 0) { int c2 = Integer.compare(this.month, o.getMonth()); if (c2 == 0) { return Double.compare(this.hot, o.getHot()); } } return 1; } }
2, sort
package com.wenbronk.weather; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * 自定义排序 * @author root */ public class MySort extends WritableComparator { /** * 在构造方法中, 通过调用父类构造创建MyKey * MyKey.class : 比较的对象 * true : 创建这个对象 */ public MySort() { super(MyKey.class, true); } /** * 自定义排序方法 * 传入的比较对象为 map 输出的key * * 年相同比较月, 月相同, 温度降序 */ @Override public int compare(WritableComparable a, WritableComparable b) { MyKey key1 = (MyKey) a; MyKey key2 = (MyKey) b; int r1 = Integer.compare(key1.getYear(), key2.getYear()); if (r1 == 0) { int r2 = Integer.compare(key1.getMonth(), key2.getMonth()); if (r2 == 0) { // 温度降序 return - Double.compare(key1.getHot(), key2.getHot()); }else { return r2; } } return r1; } }
3, group
package com.wenbronk.weather; import org.apache.hadoop.io.WritableComparable; import org.apache.hadoop.io.WritableComparator; /** * 自定义分组 * @author root * */ public class MyGroup extends WritableComparator { public MyGroup() { super(MyKey.class, true); } /** * 年, 月相同, 则为一组 */ @Override public int compare(WritableComparable a, WritableComparable b) { MyKey key1 = (MyKey) a; MyKey key2 = (MyKey) b; int r1 = Integer.compare(key1.getYear(), key2.getYear()); if (r1 == 0) { return Integer.compare(key1.getMonth(), key2.getMonth()); } return r1; } }
4, parditon
package com.wenbronk.weather; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner; /** * 自定义partition, 保证一年一个reducer进行处理 * 从map接收值 * @author root * */ public class MyPartition extends HashPartitioner<MyKey, DoubleWritable> { /** * maptask每输出一个数据, 调用一次此方法 * 执行时间越短越好 * 年的数量是确定的, 可以传递reduceTask数量, 在配置文件可设置, 在程序执行时也可设置 * */ @Override public int getPartition(MyKey key, DoubleWritable value, int numReduceTasks) { // 减去最小的, 更精确 return (key.getYear() - 1949) % numReduceTasks; } }
5, 执行类
package com.wenbronk.weather; import java.io.IOException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; 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.KeyValueTextInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; /** * 执行mapreduce 统计每年温度的前三个 * * @author wenbronk * */ public class RunMapReduce { public static void main(String[] args) throws Exception { // 初始化时加载src或classpath下所有的配置文件 Configuration configuration = new Configuration(); // 本地执行 configuration.set("fs.default", "hdfs://wenbronk.hdfs.com:8020 "); configuration.set("yarn.resourcemanager", "hdfs://192.168.208.106"); // 服务器执行 // configuration.set("mapred.jar", "C:/Users/wenbr/Desktop/weather.jar"); // configuration.set("mapred.jar", "E:\sxt\target\weather.jar"); // configuration.set("mapreduce.app-submission.cross-platform", "true"); // // configuration.set("mapreduce.framework.name", "yarn"); // configuration.set("yarn.resourcemanager.address", "192.168.208.106:"+8030); // configuration.set("yarn.resourcemanager.scheduler.address", "192.168.208.106:"+8032); // 得到执行的任务 Job job = Job.getInstance(); // 入口类 job.setJarByClass(RunMapReduce.class); // job名字 job.setJobName("weather"); // job执行是map执行的类 job.setMapperClass(WeatherMapper.class); job.setReducerClass(WeatherReduce.class); job.setMapOutputKeyClass(MyKey.class); job.setMapOutputValueClass(DoubleWritable.class); // 使用自定义的排序, 分组 job.setPartitionerClass(MyPartition.class); job.setSortComparatorClass(MySort.class); job.setGroupingComparatorClass(MyGroup.class); // job.setJar("E:\sxt\target\weather.jar"); //设置 分区数量 job.setNumReduceTasks(3); // **** 使用插件上传data.txt到hdfs/root/usr/data.txt //****使得左边为key, 右边为value, 此类默认为 " " 可以自定义 // 或者 config.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator", " "); job.setInputFormatClass(KeyValueTextInputFormat.class); // 使用文件 FileInputFormat.addInputPath(job, new Path("E:\sxt\1-MapReduce\data\weather.txt")); // FileInputFormat.addInputPath(job, new Path("/root/usr/weather.txt")); // 使用一个不存在的目录进行 Path path = new Path("/root/usr/weather"); // 如果存在删除 FileSystem fs = FileSystem.get(configuration); if (fs.exists(path)) { fs.delete(path, true); } // 输出 FileOutputFormat.setOutputPath(job, path); boolean forCompletion = job.waitForCompletion(true); if (forCompletion) { System.out.println("success"); } } /** * key: 将 LongWritalbe 改成 Text类型的 * * 将输入更改为需要的 key, value, mapper所做的事情 * * @author wenbronk */ static class WeatherMapper extends Mapper<Text, Text, MyKey, DoubleWritable> { /** * 转换字符串为日期对象 */ DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /** * 将键值取出来, 封装为key 每行第一个分隔符" "左侧为key, 右侧有value, 传递过来的数据已经切割好了 */ @Override protected void map(Text key, Text value, Mapper<Text, Text, MyKey, DoubleWritable>.Context context) throws IOException, InterruptedException { try { Date date = formatter.parse(key.toString()); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH); double hot = Double.parseDouble(value.toString().substring(0, value.toString().lastIndexOf("c"))); MyKey mykey = new MyKey(); mykey.setYear(year); mykey.setMonth(month); mykey.setHot(hot); context.write(mykey, new DoubleWritable(hot)); } catch (ParseException e) { e.printStackTrace(); } } } /** * 经过partition, 分组, 排序, 传递数据给reducer 需要自定义partition, 保证一年一个reduce 自定义排序, * 保证按照年, 月, 温度 自定义分组, 年月相同, 一个组 * 传进来的温度, 为已经排好序的 * @author root */ static class WeatherReduce extends Reducer<MyKey, DoubleWritable, Text, NullWritable> { NullWritable nullWritable = NullWritable.get(); @Override protected void reduce(MyKey arg0, Iterable<DoubleWritable> arg1, Reducer<MyKey, DoubleWritable, Text, NullWritable>.Context arg2) throws IOException, InterruptedException { int i = 0; for (DoubleWritable doubleWritable : arg1) { i++; String msg = arg0.getYear() + " " + arg0.getMonth() + " " + doubleWritable.get(); // key中已经包含需要的结果了 arg2.write(new Text(msg), NullWritable.get()); // 每个月的前三个 if (i == 3) { break; } } } } }
初始文档
1949-10-01 14:21:02 34c 1949-10-02 14:01:02 36c 1950-01-01 11:21:02 32c 1950-10-01 12:21:02 37c 1951-12-01 12:21:02 23c 1950-10-02 12:21:02 41c 1950-10-03 12:21:02 27c 1951-07-01 12:21:02 45c 1951-07-02 12:21:02 46c 1951-07-03 12:21:03 47c
系列来自尚学堂视频
https://blog.csdn.net/wuxintdrh/article/details/54917232