"数据去重"主要是为了掌握和利用并行化思想来对数据进行有意义的筛选。统计大数据集上的数据种类个数、从网站日志中计算访问地等这些看似庞杂的任务都会涉及数据去重。
MaprReduce去重流程如下图所示:
数据去重的最终目标是让原始数据中出现次数超过一次的数据在输出文件中只出现一次。在MapReduce流程中,map的输出<key,value>经过shuffle过程聚集成<key,value-list>后交给reduce。我们自然而然会想到将同一个数据的所有记录都交给一台reduce机器,无论这个数据出现多少次,只要在最终结果中输出一次就可以了。具体就是reduce的输入应该以数据作为key,而对value-list则没有要求(可以设置为空)。当reduce接收到一个<key,value-list>时就直接将输入的key复制到输出的key中,并将value设置成空值,然后输出<key,value>。
实验环境
Linux Ubuntu 14.04
jdk-7u75-linux-x64
hadoop-2.6.0-cdh5.4.5
hadoop-2.6.0-eclipse-cdh5.4.5.jar
eclipse-java-juno-SR2-linux-gtk-x86_64
实验内容
现有一个某电商网站的数据文件,名为buyer_favorite1,记录了用户收藏的商品以及收藏的日期,文件buyer_favorite1中包含(用户id,商品id,收藏日期)三个字段,数据内容以" "分割,由于数据很大,所以为了方便统计我们只截取它的一部分数据,内容如下:
-
用户id 商品id 收藏日期
-
10181 1000481 2010-04-04 16:54:31
-
20001 1001597 2010-04-07 15:07:52
-
20001 1001560 2010-04-07 15:08:27
-
20042 1001368 2010-04-08 08:20:30
-
20067 1002061 2010-04-08 16:45:33
-
20056 1003289 2010-04-12 10:50:55
-
20056 1003290 2010-04-12 11:57:35
-
20056 1003292 2010-04-12 12:05:29
-
20054 1002420 2010-04-14 15:24:12
-
20055 1001679 2010-04-14 19:46:04
-
20054 1010675 2010-04-14 15:23:53
-
20054 1002429 2010-04-14 17:52:45
-
20076 1002427 2010-04-14 19:35:39
-
20054 1003326 2010-04-20 12:54:44
-
20056 1002420 2010-04-15 11:24:49
-
20064 1002422 2010-04-15 11:35:54
-
20056 1003066 2010-04-15 11:43:01
-
20056 1003055 2010-04-15 11:43:06
-
20056 1010183 2010-04-15 11:45:24
-
20056 1002422 2010-04-15 11:45:49
-
20056 1003100 2010-04-15 11:45:54
-
20056 1003094 2010-04-15 11:45:57
-
20056 1003064 2010-04-15 11:46:04
-
20056 1010178 2010-04-15 16:15:20
-
20076 1003101 2010-04-15 16:37:27
-
20076 1003103 2010-04-15 16:37:05
-
20076 1003100 2010-04-15 16:37:18
-
20076 1003066 2010-04-15 16:37:31
-
20054 1003103 2010-04-15 16:40:14
-
20054 1003100 2010-04-15 16:40:16
要求用Java编写MapReduce程序,根据商品id进行去重,统计用户收藏商品中都有哪些商品被收藏。结果数据如下:
-
商品id
-
1000481
-
1001368
-
1001560
-
1001597
-
1001679
-
1002061
-
1002420
-
1002422
-
1002427
-
1002429
-
1003055
-
1003064
-
1003066
-
1003094
-
1003100
-
1003101
-
1003103
-
1003289
-
1003290
-
1003292
-
1003326
-
1010178
-
1010183
-
1010675
实验步骤
1.切换到/apps/hadoop/sbin目录下,开启Hadoop。
-
cd /apps/hadoop/sbin
-
./start-all.sh
2.在Linux本地新建/data/mapreduce2目录。
-
mkdir -p /data/mapreduce2
3.切换到/data/mapreduce1目录下,自行建立文本文件buyer_favorite1。
依然在/data/mapreduce1目录下,使用wget命令,从
网络下载hadoop2lib.tar.gz,下载项目用到的依赖包。
将hadoop2lib.tar.gz解压到当前目录下。
-
tar -xzvf hadoop2lib.tar.gz
4.首先在HDFS上新建/mymapreduce2/in目录,然后将Linux本地/data/mapreduce2目录下的buyer_favorite1文件导入到HDFS的/mymapreduce2/in目录中。
-
hadoop fs -mkdir -p /mymapreduce2/in
-
hadoop fs -put /data/mapreduce2/buyer_favorite1 /mymapreduce2/in
5.新建Java Project项目,项目名为mapreduce2。
在mapreduce2项目下新建包,包名为mapreduce。
在mapreduce包下新建类,类名为Filter。
6.添加项目所需依赖的jar包
右键项目,新建一个文件夹,命名为:hadoop2lib,用于存放项目所需的jar包。
将/data/mapreduce2目录下,hadoop2lib目录中的jar包,拷贝到eclipse中mapreduce2项目的hadoop2lib目录下。
选中所有项目hadoop2lib目录下所有jar包,并添加到Build Path中。
7.编写程序代码,并描述其思路
数据去重的目的是让原始数据中出现次数超过一次的数据在输出文件中只出现一次。我们自然想到将相同key值的所有value记录交到一台reduce机器,让其无论这个数据出现多少次,最终结果只输出一次。具体就是reduce的输出应该以数据作为key,而对value-list没有要求,当reduce接收到一个时,就直接将key复制到输出的key中,将value设置为空。
Map代码
-
public static class Map extends Mapper<Object , Text , Text , NullWritable>
-
//map将输入中的value复制到输出数据的key上,并直接输出
-
{
-
private static Text newKey=new Text(); //从输入中得到的每行的数据的类型
-
public void map(Object key,Text value,Context context) throws IOException, InterruptedException
-
//实现map函数
-
{ //获取并输出每一次的处理过程
-
String line=value.toString();
-
System.out.println(line);
-
String arr[]=line.split(" ");
-
newKey.set(arr[1]);
-
context.write(newKey, NullWritable.get());
-
System.out.println(newKey);
-
}
-
}
map阶段采用Hadoop的默认的作业输入方式,把输入的value用split()方法截取,截取出的商品id字段设置为key,设置value为空,然后直接输出<key,value>。
reduce端代码
-
public static class Reduce extends Reducer<Text, NullWritable, Text, NullWritable>{
-
public void reduce(Text key,Iterable<NullWritable> values,Context context) throws IOException, InterruptedException
-
//实现reduce函数
-
{
-
context.write(key,NullWritable.get()); //获取并输出每一次的处理过程
-
}
-
}
map输出的<key,value>键值对经过shuffle过程,聚成<key,value-list>后,会交给reduce函数。reduce函数,不管每个key 有多少个value,它直接将输入的赋值给输出的key,将输出的value设置为空,然后输出<key,value>就可以了。
完整代码
-
package mapreduce;
-
import java.io.IOException;
-
import org.apache.hadoop.conf.Configuration;
-
import org.apache.hadoop.fs.Path;
-
import org.apache.hadoop.io.IntWritable;
-
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.TextInputFormat;
-
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
-
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
-
public class Filter{
-
public static class Map extends Mapper<Object , Text , Text , NullWritable>{
-
private static Text newKey=new Text();
-
public void map(Object key,Text value,Context context) throws IOException, InterruptedException{
-
String line=value.toString();
-
System.out.println(line);
-
String arr[]=line.split(" ");
-
newKey.set(arr[1]);
-
context.write(newKey, NullWritable.get());
-
System.out.println(newKey);
-
}
-
}
-
public static class Reduce extends Reducer<Text, NullWritable, Text, NullWritable>{
-
public void reduce(Text key,Iterable<NullWritable> values,Context context) throws IOException, InterruptedException{
-
context.write(key,NullWritable.get());
-
}
-
}
-
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException{
-
Configuration conf=new Configuration();
-
System.out.println("start");
-
Job job =new Job(conf,"filter");
-
job.setJarByClass(Filter.class);
-
job.setMapperClass(Map.class);
-
job.setReducerClass(Reduce.class);
-
job.setOutputKeyClass(Text.class);
-
job.setOutputValueClass(NullWritable.class);
-
job.setInputFormatClass(TextInputFormat.class);
-
job.setOutputFormatClass(TextOutputFormat.class);
-
Path in=new Path("hdfs://localhost:9000/mymapreduce2/in/buyer_favorite1");
-
Path out=new Path("hdfs://localhost:9000/mymapreduce2/out");
-
FileInputFormat.addInputPath(job,in);
-
FileOutputFormat.setOutputPath(job,out);
-
System.exit(job.waitForCompletion(true) ? 0 : 1);
-
}
-
}
8.在Filter类文件中,右键并点击=>Run As=>Run on Hadoop选项,将MapReduce任务提交到Hadoop中。
9.待执行完毕后,进入命令模式下,在HDFS中/mymapreduce2/out查看实验结果。
-
hadoop fs -ls /mymapreduce2/out
-
hadoop fs -cat /mymapreduce2/out/part-r-00000