自定义分组 与

自定义分组

job.setGroupingComparatorClass(MyGroupingComparator.class); //按照第一列进行分组,然后找出每个分组中的第二列中的最小值

为什么要自定义分组?

业务要求分组是按照第一列分组,但是NewK2的比较规则决定了不能按照第一列分。只能自定义分组比较器。

复制代码
1 package group;
2
3 import java.io.DataInput;
4 import java.io.DataOutput;
5 import java.io.IOException;
6 import java.net.URI;
7 import java.util.Comparator;
8 import java.util.function.Function;
9 import java.util.function.ToDoubleFunction;
10 import java.util.function.ToIntFunction;
11 import java.util.function.ToLongFunction;
12
13 import org.apache.hadoop.conf.Configuration;
14 import org.apache.hadoop.fs.FileSystem;
15 import org.apache.hadoop.fs.Path;
16 import org.apache.hadoop.io.LongWritable;
17 import org.apache.hadoop.io.RawComparator;
18 import org.apache.hadoop.io.Text;
19 import org.apache.hadoop.io.WritableComparable;
20 import org.apache.hadoop.io.WritableComparator;
21 import org.apache.hadoop.io.file.tfile.RawComparable;
22 import org.apache.hadoop.mapreduce.Job;
23 import org.apache.hadoop.mapreduce.Mapper;
24 import org.apache.hadoop.mapreduce.Reducer;
25 import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
26 import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
27 import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
28 import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
29 import org.apache.hadoop.mapreduce.lib.partition.HashPartitioner;
30
31 public class GroupApp {
32 static final String INPUT_PATH = “hdfs://chaoren:9000/input”;
33 static final String OUT_PATH = “hdfs://chaoren:9000/out”;
34
35 public static void main(String[] args) throws Exception {
36 final Configuration configuration = new Configuration();
37
38 final FileSystem fileSystem = FileSystem.get(new URI(INPUT_PATH),
39 configuration);
40 if (fileSystem.exists(new Path(OUT_PATH))) {
41 fileSystem.delete(new Path(OUT_PATH), true);
42 }
43
44 final Job job = new Job(configuration, GroupApp.class.getSimpleName());
45
46 // 1.1 指定输入文件路径
47 FileInputFormat.setInputPaths(job, INPUT_PATH);
48 // 指定哪个类用来格式化输入文件
49 job.setInputFormatClass(TextInputFormat.class);
50
51 // 1.2指定自定义的Mapper类
52 job.setMapperClass(MyMapper.class);
53 // 指定输出<k2,v2>的类型
54 job.setMapOutputKeyClass(NewK2.class);
55 job.setMapOutputValueClass(LongWritable.class);
56
57 // 1.3 指定分区类
58 job.setPartitionerClass(HashPartitioner.class);
59 job.setNumReduceTasks(1);
60
61 // 1.4 TODO 排序、分区
62 /**
63 * 分组:按照第一列分区
64 /
65 job.setGroupingComparatorClass(MyGroupingComparator.class);
66
67 // 1.5 TODO (可选)合并
68
69 // 2.2 指定自定义的reduce类
70 job.setReducerClass(MyReducer.class);
71 // 指定输出<k3,v3>的类型
72 job.setOutputKeyClass(LongWritable.class);
73 job.setOutputValueClass(LongWritable.class);
74
75 // 2.3 指定输出到哪里
76 FileOutputFormat.setOutputPath(job, new Path(OUT_PATH));
77 // 设定输出文件的格式化类
78 job.setOutputFormatClass(TextOutputFormat.class);
79
80 // 把代码提交给JobTracker执行
81 job.waitForCompletion(true);
82 }
83
84 static class MyMapper extends
85 Mapper<LongWritable, Text, NewK2, LongWritable> {
86 protected void map(
87 LongWritable key,
88 Text value,
89 org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, NewK2, LongWritable>.Context context)
90 throws java.io.IOException, InterruptedException {
91 final String[] splited = value.toString().split("\t");
92 final NewK2 k2 = new NewK2(Long.parseLong(splited[0]),
93 Long.parseLong(splited[1]));
94 final LongWritable v2 = new LongWritable(Long.parseLong(splited[1]));
95 context.write(k2, v2);
96 };
97 }
98
99 static class MyReducer extends
100 Reducer<NewK2, LongWritable, LongWritable, LongWritable> {
101 protected void reduce(
102 NewK2 k2,
103 java.lang.Iterable v2s,
104 org.apache.hadoop.mapreduce.Reducer<NewK2, LongWritable, LongWritable, LongWritable>.Context context)
105 throws java.io.IOException, InterruptedException {
106 long min = Long.MAX_VALUE;
107 for (LongWritable v2 : v2s) {
108 if (v2.get() < min) {
109 min = v2.get();
110 }
111 }
112 context.write(new LongWritable(k2.first), new LongWritable(min));
113 };
114 }
115
116 /
*
117 * 问:为什么实现该类? 答:因为原来的v2不能参与排序,把原来的k2和v2封装到一个类中,作为新的k2
118 *
119 /
120 // WritableComparable:Hadoop的序列化
121 static class NewK2 implements WritableComparable {
122 Long first;
123 Long second;
124
125 public NewK2() {
126 }
127
128 public NewK2(long first, long second) {
129 this.first = first;
130 this.second = second;
131 }
132
133 public void readFields(DataInput in) throws IOException {
134 this.first = in.readLong();
135 this.second = in.readLong();
136 }
137
138 public void write(DataOutput out) throws IOException {
139 out.writeLong(first);
140 out.writeLong(second);
141 }
142
143 /
*
144 * 当k2进行排序时,会调用该方法. 当第一列不同时,升序;当第一列相同时,第二列升序
145 /
146 public int compareTo(NewK2 o) {
147 final long minus = this.first - o.first;
148 if (minus != 0) {
149 return (int) minus;
150 }
151 return (int) (this.second - o.second);
152 }
153
154 @Override
155 public int hashCode() {
156 return this.first.hashCode() + this.second.hashCode();
157 }
158
159 @Override
160 public boolean equals(Object obj) {
161 if (!(obj instanceof NewK2)) {
162 return false;
163 }
164 NewK2 oK2 = (NewK2) obj;
165 return (this.first == oK2.first) && (this.second == oK2.second);
166 }
167 }
168
169 static class MyGroupingComparator implements RawComparator {
170
171 public int compare(NewK2 o1, NewK2 o2) {
172 return (int) (o1.first - o2.first);
173 }
174
175 /
*
176 * @param arg0
177 * 表示第一个参与分组的字节数组
178 * @param arg1
179 * 表示第一个参与分组的字节数组的起始位置
180 * @param arg2
181 * 表示第一个参与分组的字节数组的偏移量
182 *
183 * @param arg0
184 * 表示第二个参与分组的字节数组
185 * @param arg1
186 * 表示第二个参与分组的字节数组的起始位置
187 * @param arg2
188 * 表示第二个参与分组的字节数组的偏移量
189 */
190 public int compare(byte[] arg0, int arg1, int arg2, byte[] arg3,
191 int arg4, int arg5) {
192 return WritableComparator
193 .compareBytes(arg0, arg1, 8, arg3, arg4, 8);
194 }
195
196 }
197
198 }

复制代码

在这里插入图片描述

shuffle的过程分析

shuffle阶段其实就是之前《MapReduce的原理及执行过程》中的步骤2.1。多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点上。
在这里插入图片描述

在这里插入图片描述

Map端:

1、在map端首先接触的是InputSplit,在InputSplit中含有DataNode中的数据,每一个InputSplit都会分配一个Mapper任务,Mapper任务结束后产生<K2,V2>的输出,这些输出先存放在缓存中,每个map有一个环形内存缓冲区,用于存储任务的输出。默认大小100MB(io.sort.mb属性),一旦达到阀值0.8(io.sort.spil l.percent),一个后台线程就把内容写到(spill)Linux本地磁盘中的指定目录(mapred.local.dir)下的新建的一个溢出写文件。(注意:map过程的输出是写入本地磁盘而不是HDFS,但是一开始数据并不是直接写入磁盘而是缓冲在内存中,缓存的好处就是减少磁盘I/O的开销,提高合并和排序的速度。又因为默认的内存缓冲大小是100M(当然这个是可以配置的),所以在编写map函数的时候要尽量减少内存的使用,为shuffle过程预留更多的内存,因为该过程是最耗时的过程。)

2、写磁盘前,要进行partition、sort和combine等操作。通过分区,将不同类型的数据分开处理,之后对不同分区的数据进行排序,如果有Combiner,还要对排序后的数据进行combine。等最后记录写完,将全部溢出文件合并为一个分区且排序的文件。(注意:在写磁盘的时候采用压缩的方式将map的输出结果进行压缩是一个减少网络开销很有效的方法!)

3、最后将磁盘中的数据送到Reduce中,从图中可以看出Map输出有三个分区,有一个分区数据被送到图示的Reduce任务中,剩下的两个分区被送到其他Reducer任务中。而图示的Reducer任务的其他的三个输入则来自其他节点的Map输出。

Reduce端:

1、Copy阶段:Reducer通过Http方式得到输出文件的分区。

reduce端可能从n个map的结果中获取数据,而这些map的执行速度不尽相同,当其中一个map运行结束时,reduce就会从JobTracker中获取该信息。map运行结束后TaskTracker会得到消息,进而将消息汇报给  JobTracker,reduce定时从JobTracker获取该信息,reduce端默认有5个数据复制线程从map端复制数据。

2、Merge阶段:如果形成多个磁盘文件会进行合并

从map端复制来的数据首先写到reduce端的缓存中,同样缓存占用到达一定阈值后会将数据写到磁盘中,同样会进行partition、combine、排序等过程。如果形成了多个磁盘文件还会进行合并,最后一次合并的结果作为reduce的输入而不是写入到磁盘中。

3、Reducer的参数:最后将合并后的结果作为输入传入Reduce任务中。(注意:当Reducer的输入文件确定后,整个Shuffle操作才最终结束。之后就是Reducer的执行了,最后Reducer会把结果存到HDFS上。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值