實驗七:掌握基本的MapReduce編程方法 (JAVA+Python實現)(編程實現文件合併和去重操作,編寫程序實現對輸入文件的排序,對給定的表格進行信息挖掘)

一、實驗目的:

1. 理解MapReduce的工作機制;
2. 掌握基本的MapReduce編程方法
3. 重點理解map過程,shuffle過程和reduce過程

二、實驗環境:

Hadoop+Eclipse+JDK

三、實驗內容和要求:

1.編程實現文件合併和去重操作

對於兩個輸入文件,即文件A和文件B,請編寫MapReduce程序,對兩個文件進行合併,並剔除其中重複的內容,得到一個新的輸出文件C。下面是輸入文件和輸出文件的一個樣例供參考。
輸入文件A的樣例如下:

20150101     x
20150102     y
20150103     x
20150104     y
20150105     z
20150106     x

輸入文件B的樣例如下:

20150101      y
20150102      y
20150103      x
20150104      z
20150105      y

根據輸入文件A和B合併得到的輸出文件C的樣例如下:

20150101      x
20150101      y
20150102      y
20150103      x
20150104      y
20150104      z
20150105      y
20150105      z
20150106      x

2. 編寫程序實現對輸入文件的排序

現在有多個輸入文件,每個文件中的每行內容均爲一個整數。要求讀取所有文件中的整數,進行升序排序後,輸出到一個新的文件中,輸出的數據格式爲每行兩個整數,第一個數字爲第二個整數的排序位次,第二個整數爲原待排列的整數。下面是輸入文件和輸出文件的一個樣例供參考。
輸入文件1的樣例如下:

33
37
12
40

輸入文件2的樣例如下:

4
16
39
5

輸入文件3的樣例如下:

1
45
25

根據輸入文件1、2和3得到的輸出文件如下:

1 1
2 4
3 5
4 12
5 16
6 25
7 33
8 37
9 39
10 40
11 45

3. 對給定的表格進行信息挖掘

下面給出一個child-parent的表格,要求挖掘其中的父子輩關係,給出祖孫輩關係的表格。
輸入文件內容如下:

child          parent
Steven        Lucy
Steven        Jack
Jone         Lucy
Jone         Jack
Lucy         Mary
Lucy         Frank
Jack         Alice
Jack         Jesse
David       Alice
David       Jesse
Philip       David
Philip       Alma
Mark       David
Mark       Alma

輸出文件內容如下:

grandchild       grandparent
Steven          Alice
Steven          Jesse
Jone            Alice
Jone            Jesse
Steven          Mary
Steven          Frank
Jone            Mary
Jone            Frank
Philip           Alice
Philip           Jesse
Mark           Alice
Mark           Jesse

四、問答題

1. 說明Map過程的執行流程

  • Map過程通過在輸入列表中的每一項執行函數,生成一系列的輸出列表。

2. 說明Shuffle過程的執行流程(包括如何分區,排序,分組)

  • 因爲頻繁的磁盤I/O操作會嚴重的降低效率,因此“中間結果”不會立馬寫入磁盤,而是優先存儲到map節點的“環形內存緩衝區”,在寫入的過程中進行分區(partition),也就是對於每個鍵值對來說,都增加了一個partition屬性值,然後連同鍵值對一起序列化成字節數組寫入到緩衝區(緩衝區採用的就是字節數組,默認大小爲100M)。當寫入的數據量達到預先設置的闕值後(mapreduce.map.io.sort.spill.percent,默認0.80,或者80%)便會啓動溢寫出線程將緩衝區中的那部分數據溢出寫(spill)到磁盤的臨時文件中,並在寫入前根據key進行排序(sort)和合並(combine,可選操作)。溢出寫過程按輪詢方式將緩衝區中的內容寫到mapreduce.cluster.local.dir屬性指定的目錄中。當整個map任務完成溢出寫後,會對磁盤中這個map任務產生的所有臨時文件(spill文件)進行歸併(merge)操作生成最終的正式輸出文件,此時的歸併是將所有spill文件中的相同partition合併到一起,並對各個partition中的數據再進行一次排序(sort),生成key和對應的value-list,文件歸併時,如果溢寫文件數量超過參數min.num.spills.for.combine的值(默認爲3)時,可以再次進行合併。至此,map端shuffle過程結束,接下來等待reduce task來拉取數據。對於reduce端的shuffle過程來說,reduce task在執行之前的工作就是不斷地拉取當前job裏每個map task的最終結果,然後對從不同地方拉取過來的數據不斷地做merge最後合併成一個分區相同的大文件,然後對這個文件中的鍵值對按照key進行sort排序,排好序之後緊接着進行分組,分組完成後纔將整個文件交給reduce task處理。

3. 說明Reduce過程的執行流程

  • Reduce過程在一個輸入的列表進行掃描工作,隨後生成一個聚集值,作爲最後的輸出

五、實驗結果與分析(含程序、數據記錄及分析和實驗總結等):

所有關鍵代碼在文檔最後
1. 將你設計的幾個頁面截圖拷貝粘貼到此處,每張截圖後附上關鍵代碼 (關鍵代碼見附錄)


在這裏插入圖片描述
在這裏插入圖片描述
注:文件事先上傳至 /input/test1中,上圖分別是java和python mapreduce的運行結果
在這裏插入圖片描述
在這裏插入圖片描述
注:測試文件事先上傳到/input/test2中, 上圖爲java和python測試結果
在這裏插入圖片描述
2. 截圖中應包含各位同學的個人信息,防止抄襲
3. 實驗總結(學到的知識點、遇到的難點、心得等)必須要寫(否則大扣分)
在做實驗的時候遇到了下面幾個問題,現在已經一一解決:

1. 實驗配置的問題 :
- eclipse中hadoop插件的配置: 已經解決並整理爲博客:點擊此處
- 嘗試用python運行,需要下載Pydev插件 已解決
2. 代碼思路的問題 :
因爲之前只學習了詞頻統計的應用,在面對其他問題的時候突然沒有什麼好的思路,原因是因爲對map 和 reduce的原理還不是特別清楚 查閱了大量資料之後 理解, map階段將 輸入的信息整理成 <key,value>的形式,在shuffle之後形成了<key,value-list> 而reduce接受到 < key,value-list >再對他進行操作 來得到想要的輸出
下面給出三個實驗的思路:

  1. 編程實現文件合併和去重操作
    本道題主要目的是去重,我在編寫的時候的思路就是 通過map函數讀取 key,value 因爲我的目的是去重,所以在這裏我完全可以把整個數據作爲一個key,而value我可以不管他 而reduce會接受到的是<key,value-list>形式的數據,我們只需要輸出他接受到的key就可以了,因爲重複的值體現在value-list裏面,而key是位移的.
    可以從python字典的角度來理解,我們把文檔的每一行作爲字典的鍵,出現的次數作爲值,最終我們循環輸出所有的鍵
  2. 編寫程序實現對輸入文件的排序
    因爲MR自帶排序,所以我們只要把輸入的數字以int的形式交給map map將這個數字作爲key輸出,而rerduce函數將map輸入的key複製到輸出的value上即可,因爲要輸入排序的序號,所以再定義個變量用來記錄輸出數字的排序即可
  3. 對給定的表格進行信息挖掘
    本題其實相當於一個表的自身join,但是我們需要轉化一下,輸入的文件只是child和parent ,將他正序輸出一次作爲右表,反序輸出一次作爲左表,這樣就可以完成child parent grand三個字段的兩張表操作,輸出的時候加上兩張表的標識來區分 reduce函數則用來取出左表中的child 即爲grandchild 再取出右表的parent相當於grandparent即可

實驗代碼

總:上傳文件代碼

vim test1.txt # 裏面放上要測試的文本
hdfs dfs - mkdir /input/test1/	# 創建文件夾
hdfs dfs - put test1.txt /input/test1/	# 上傳文件

第一題Java解

1.	package org.apache.hadoop.example;  
2.	  
3.	import java.io.IOException;  
4.	  
5.	import org.apache.hadoop.conf.Configuration;  
6.	import org.apache.hadoop.fs.Path;  
7.	import org.apache.hadoop.io.IntWritable;  
8.	import org.apache.hadoop.io.Text;  
9.	import org.apache.hadoop.mapreduce.Job;  
10.	import org.apache.hadoop.mapreduce.Mapper;  
11.	import org.apache.hadoop.mapreduce.Reducer;  
12.	import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
13.	import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
14.	import org.apache.hadoop.util.GenericOptionsParser;  
15.	  
16.	public class Merge {  
17.	  
18.	    /** 
19.	     * @param args 
20.	     * 對A,B兩個文件進行合併,並剔除其中重複的內容,得到一個新的輸出文件C 
21.	     */  
22.	    //在這重載map函數,直接將輸入中的value複製到輸出數據的key上 注意在map方法中要拋出異常:throws IOException,InterruptedException  
23.	    public static class Map extends Mapper<Object, Text, Text, Text> {    
24.	        private static Text text = new Text();  
25.	        public void map(Object key, Text value, Context content) throws IOException, InterruptedException {    
26.	  
27.	            text = value;    
28.	            content.write(text, new Text(""));    
29.	        }    
30.	    }    
31.	    //在這重載reduce函數,直接將輸入中的key複製到輸出數據的key上  注意在reduce方法上要拋出異常:throws IOException,InterruptedException  
32.	        public static class Reduce extends Reducer<Text, Text, Text, Text> {    
33.	        public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {    
34.	            context.write(key, new Text(""));    
35.	        }    
36.	    }  
37.	  
38.	  
39.	        public static void main(String[] args) throws Exception{  
40.	  
41.	        // TODO Auto-generated method stub  
42.	        Configuration conf = new Configuration();  
43.	        conf.set("fs.default.name","hdfs://localhost:9000");  
44.	        String[] otherArgs = new String[]{"/input/test1","/output/test1"}; /* 直接設置輸入參數 */  
45.	        if (otherArgs.length != 2) {  
46.	            System.err.println("Usage: wordcount <in> <out>");  
47.	            System.exit(2);  
48.	            }  
49.	        Job job = Job.getInstance(conf,"Merge and duplicate removal");  
50.	        job.setJarByClass(Merge.class);  
51.	        job.setMapperClass(Map.class);  
52.	        job.setCombinerClass(Reduce.class);  
53.	        job.setReducerClass(Reduce.class);  
54.	        job.setOutputKeyClass(Text.class);  
55.	        job.setOutputValueClass(Text.class);  
56.	        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));  
57.	        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));  
58.	        System.exit(job.waitForCompletion(true) ? 0 : 1);  
59.	    }  
60.	  
61.	}

第一題 Python解

# mapper.py
62.	#!/usr/bin/env python3  
63.	import sys  
64.	for line in sys.stdin:  
65.	    if line:  
66.	        print(line),  
# reducer.py	
67.	#!/usr/bin/env python  
68.	from operator import itemgetter  
69.	import sys  
70.	  
71.	li = []  
72.	for line in sys.stdin:  
73.	    if line in li:  
74.	        continue  
75.	    print(line),  
76.	    li.append(line)  
77.	      

運行腳本

78.	hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.8.3.jar \  
79.	-file ~/eclipse-workspace/PyWordCount/mapper2.py -mapper ~/eclipse-workspace/PyWordCount/mapper2.py \  
80.	-file ~/eclipse-workspace/PyWordCount/reducer2.py -reducer ~/eclipse-workspace//PyWordCount/reducer2.py \  
81.	-input /input/test1/ -output /output/t1  

第二題Java解

82.	package org.apache.hadoop.example;  
83.	  
84.	import java.io.IOException;  
85.	  
86.	  
87.	public class MergeSort {  
88.	  
89.	    /** 
90.	     * @param args 
91.	     * 輸入多個文件,每個文件中的每行內容均爲一個整數 
92.	     * 輸出到一個新的文件中,輸出的數據格式爲每行兩個整數,第一個數字爲第二個整數的排序位次,第二個整數爲原待排列的整數 
93.	     */  
94.	    //map函數讀取輸入中的value,將其轉化成IntWritable類型,最後作爲輸出key  
95.	    public static class Map extends Mapper<Object, Text, IntWritable, IntWritable>{  
96.	  
97.	        private static IntWritable data = new IntWritable();  
98.	        public void map(Object key, Text value, Context context) throws IOException,InterruptedException{  
99.	            /********** Begin **********/  
100.	        String line = value.toString();  
101.	        data.set(Integer.parseInt(line));  
102.	        context.write(data, new IntWritable(1));  
103.	            /********** End **********/  
104.	  
105.	        }  
106.	    }  
107.	  
108.	    //reduce函數將map輸入的key複製到輸出的value上,然後根據輸入的value-list中元素的個數決定key的輸出次數,定義一個全局變量line_num來代表key的位次  
109.	    public static class Reduce extends Reducer<IntWritable, IntWritable, IntWritable, IntWritable>{  
110.	        private static IntWritable line_num = new IntWritable(1);  
111.	  
112.	        public void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException,InterruptedException{  
113.	            /********** Begin **********/  
114.	           for(IntWritable num : values) {  
115.	             context.write(line_num, key);  
116.	             line_num = new IntWritable(line_num.get() + 1);  
117.	    }  
118.	            /********** End **********/  
119.	  }  
120.	}  
121.	  
122.	    //自定義Partition函數,此函數根據輸入數據的最大值和MapReduce框架中Partition的數量獲取將輸入數據按照大小分塊的邊界,然後根據輸入數值和邊界的關係返回對應的Partiton ID  
123.	    public static class Partition extends Partitioner<IntWritable, IntWritable>{  
124.	        public int getPartition(IntWritable key, IntWritable value, int num_Partition){  
125.	            /********** Begin **********/  
126.	            int Maxnumber = 65223;//int型的最大數值  
127.	            int bound = Maxnumber / num_Partition + 1;  
128.	            int Keynumber = key.get();  
129.	            for(int i = 0; i < num_Partition; i++){  
130.	              if(Keynumber < bound * i && Keynumber >= bound * (i - 1)) {  
131.	                return i - 1;  
132.	      }  
133.	    }  
134.	            return -1 ;  
135.	            /********** End **********/  
136.	  
137.	        }  
138.	    }  
139.	  
140.	    public static void main(String[] args) throws Exception{  
141.	        // TODO Auto-generated method stub  
142.	        Configuration conf = new Configuration();  
143.	        conf.set("fs.default.name","hdfs://localhost:9000");  
144.	        String[] otherArgs = new String[]{"/input/test2","/output/test2"}; /* 直接設置輸入參數 */  
145.	        if (otherArgs.length != 2) {  
146.	            System.err.println("Usage: wordcount <in> <out>");  
147.	            System.exit(2);  
148.	            }  
149.	        Job job = Job.getInstance(conf,"Merge and Sort");  
150.	        job.setJarByClass(MergeSort.class);  
151.	        job.setMapperClass(Map.class);  
152.	        job.setReducerClass(Reduce.class);  
153.	        job.setPartitionerClass(Partition.class);  
154.	        job.setOutputKeyClass(IntWritable.class);  
155.	        job.setOutputValueClass(IntWritable.class);  
156.	        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));  
157.	        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));  
158.	        System.exit(job.waitForCompletion(true) ? 0 : 1);  
159.	  
160.	    }  
161.	  
162.	}  

Python解

# Mapper.py
163.	#!/usr/bin/env python3  
164.	import sys  
165.	for line in sys.stdin:  
166.	    if line:  
167.	        print (line,end='')  

# reducer.py
168.	#!/usr/bin/env python3  
169.	from operator import itemgetter  
170.	import sys  
171.	  
172.	con = 1  
173.	li = []  
174.	for line in sys.stdin:  
175.	    if line:  
176.	        a = str(line).strip()  
177.	        li.append(int(a))  
178.	for i in sorted(li):  
179.	    print(con,i),  
180.	    con += 1  

運行腳本

182.	hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-2.8.3.jar \  
183.	-file ~/eclipse-workspace/pytesr/mapper.py -mapper ~/eclipse-workspace/pytesr/mapper.py \  
184.	-file ~/eclipse-workspace/pytesr/reducer.py -reducer ~/eclipse-workspace//pytesr/reducer.py \  
185.	-input /input/test2/ -output /output/t2  

第三題

186.	package org.apache.hadoop.example;  
187.	  
188.	import java.io.IOException;  
189.	import java.util.*;  
190.	  
191.	import org.apache.hadoop.conf.Configuration;  
192.	import org.apache.hadoop.fs.Path;  
193.	import org.apache.hadoop.io.IntWritable;  
194.	import org.apache.hadoop.io.Text;  
195.	import org.apache.hadoop.mapreduce.Job;  
196.	import org.apache.hadoop.mapreduce.Mapper;  
197.	import org.apache.hadoop.mapreduce.Reducer;  
198.	import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
199.	import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
200.	import org.apache.hadoop.util.GenericOptionsParser;  
201.	  
202.	public class simple_data_mining {  
203.	    public static int time = 0;  
204.	  
205.	    /** 
206.	     * @param args 
207.	     * 輸入一個child-parent的表格 
208.	     * 輸出一個體現grandchild-grandparent關係的表格 
209.	     */  
210.	    //Map將輸入文件按照空格分割成child和parent,然後正序輸出一次作爲右表,反序輸出一次作爲左表,需要注意的是在輸出的value中必須加上左右表區別標誌  
211.	    public static class Map extends Mapper<Object, Text, Text, Text>{  
212.	        public void map(Object key, Text value, Context context) throws IOException,InterruptedException{  
213.	            /********** Begin **********/  
214.	             String line = value.toString();  
215.	             String[] childAndParent = line.split(" ");  
216.	             List<String> list = new ArrayList<>(2);  
217.	              for (String childOrParent : childAndParent) {  
218.	                 if (!"".equals(childOrParent)) {  
219.	                     list.add(childOrParent);  
220.	                  }  
221.	              }   
222.	              if (!"child".equals(list.get(0))) {  
223.	                  String childName = list.get(0);  
224.	                  String parentName = list.get(1);  
225.	                  String relationType = "1";  
226.	                  context.write(new Text(parentName), new Text(relationType + "+"  
227.	                        + childName + "+" + parentName));  
228.	                  relationType = "2";  
229.	                  context.write(new Text(childName), new Text(relationType + "+"  
230.	                        + childName + "+" + parentName));  
231.	              }  
232.	  
233.	            /********** End **********/  
234.	        }  
235.	    }  
236.	  
237.	    public static class Reduce extends Reducer<Text, Text, Text, Text>{  
238.	        public void reduce(Text key, Iterable<Text> values,Context context) throws IOException,InterruptedException{  
239.	                /********** Begin **********/  
240.	  
241.	                //輸出表頭  
242.	               if (time == 0) {  
243.	                context.write(new Text("grand_child"), new Text("grand_parent"));  
244.	                time++;  
245.	            }  
246.	  
247.	            //獲取value-list中value的child  
248.	            List<String> grandChild = new ArrayList<>();  
249.	            //獲取value-list中value的parent  
250.	            List<String> grandParent = new ArrayList<>();  
251.	                //左表,取出child放入grand_child  
252.	            for (Text text : values) {  
253.	                String s = text.toString();  
254.	                String[] relation = s.split("\\+");  
255.	                String relationType = relation[0];  
256.	                String childName = relation[1];  
257.	                String parentName = relation[2];  
258.	                if ("1".equals(relationType)) {  
259.	                    grandChild.add(childName);  
260.	                } else {  
261.	                    grandParent.add(parentName);  
262.	                }  
263.	            }  
264.	  
265.	                //右表,取出parent放入grand_parent  
266.	               int grandParentNum = grandParent.size();  
267.	               int grandChildNum = grandChild.size();  
268.	               if (grandParentNum != 0 && grandChildNum != 0) {  
269.	                for (int m = 0; m < grandChildNum; m++) {  
270.	                    for (int n = 0; n < grandParentNum; n++) {  
271.	                        //輸出結果  
272.	                    context.write(new Text(grandChild.get(m)), new Text(  
273.	                                grandParent.get(n)));  
274.	                    }  
275.	                }  
276.	            }  
277.	  
278.	                /********** End **********/  
279.	  
280.	        }  
281.	    }  
282.	    public static void main(String[] args) throws Exception{  
283.	        // TODO Auto-generated method stub  
284.	        Configuration conf = new Configuration();  
285.	        conf.set("fs.default.name","hdfs://localhost:9000");  
286.	        String[] otherArgs = new String[]{"/input/test3","/output/test3"}; /* 直接設置輸入參數 */  
287.	        if (otherArgs.length != 2) {  
288.	            System.err.println("Usage: wordcount <in> <out>");  
289.	            System.exit(2);  
290.	            }  
291.	        Job job = Job.getInstance(conf,"Single table join");  
292.	        job.setJarByClass(simple_data_mining.class);  
293.	        job.setMapperClass(Map.class);  
294.	        job.setReducerClass(Reduce  
295.	                .class);  
296.	        job.setOutputKeyClass(Text.class);  
297.	        job.setOutputValueClass(Text.class);  
298.	        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));  
299.	        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));  
300.	        System.exit(job.waitForCompletion(true) ? 0 : 1);  
301.	  
302.	    }  
303.	  
304.	}  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章