互聯網社交好友推薦

互聯網行業中聊天交友軟件爲了能夠良好的適應使用人羣的習慣,與日常使用。同事爲了提高用戶的註冊量,故而需要有效的推廣方式和具有特色的功能來吸引人的眼球。通過好友推薦的方式能夠根據現有用戶的使用數據以及與其他同類型產品的合作可以通過計算分析出其好友圈內有望成爲註冊用戶的人並進行推薦。

實現過程

  已有的數據是每個人以及他的好友,無法直接從這個數據得到他的好友的好友,換一種思路,能夠知道他的所有好友都有一個共同好友就是他,例如A 有兩個好友 B 和 C,如果 B 和 C 不是好友那可以把 B 推薦給 C,因爲 B 和 C 有一個共同好友。基於這種思路有了案例的實現方案。

  第一個 Map,首先輸出 key 爲自己和每一個好友組合,value 標識已經是好友,另外還有輸出key爲好友的兩兩組合,value爲1代表共同好友個數。 Reduce判斷得到的數據的value 如果已經是共同好友就直接返回,否則就累加,就能得到每兩個非好友的人的共同好友人數。

  這個思路有個問題,Map的key爲兩個人的組合,但是組合A+B和組合B+A是一樣的, 所以我們在寫出數據之前,應該對數據進行排序,保證不會同時出現A+B 和 B+A。另外最後得到了每兩個人的共同好友個數,但是應該排序才能得到應該推薦的好友是哪些。在 Reduce 進行排序的方法讀者都懂,需要自定義輸出類,實現 compareTo 方法,同時定義 GroupingComparator 類。

實驗步驟

1、代碼分析

  第一個MapReduce實現類似 Wordcount,只不過每次輸出之前要排序,保證A+B 和B+A 都能輸出 A|B,另外直接好友多輸出一個2標識已經是好友了。

  第二個 MapReduce 先定義一個 FriendOfFriend 類,compareTo 方法先比較 id 然後比較共同好友數。另外自定義分組類,按照 id 分組

  自定義一個 FriendOfFriend 的輸出類,有 id,推薦好友 id,共同好友數量三個屬性, Map 讀進來的數據直接封裝成這樣的對象寫出,他的分組 compareTo 方法先比較 id 然後比較共同好友數量,然後分組函數直接按 id 分組。在 reduce 的時候魅族數據就是 id 一樣按照count 排序好的 FriendOfFriend 對象,直接寫出。

2、創建項目

第一步:自定義一個 FriendOfFriend 的輸出類

推薦好友 id,共同好友數量三個屬性, Map 讀進來的數據直接封裝成這樣的對象寫出,他的分組 compareTo 方法先比較 id 然後比較共同好友數量,然後分組函數直接按 id 分組。在 reduce 的時候魅族數據就是 id 一樣按照count 排序好的 FriendOfFriend 對象,直接寫出。創建輸出類命令如下。

mkdir /qq_recommendation
cd /qq_recommendation
vi FriendOfFriend.java

結果如下圖所示。

在這裏插入圖片描述

代碼如下

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparable;
public class FriendOfFriend implements WritableComparable<FriendOfFriend> { 
private String username; 
private String fof;
 private int count; 
 public String getUsername () {
      return username;
 }
 public int getCount () {
      return count;
 }
  public String getFof () {
      return fof;
 }
 public void setCount(int count){
     this.count = count;
 }
   public void setFof(String Fof){
     this.fof = Fof;
 }
  public void setUsername(String Username){
     this.username = Username;
 }
 public void readFields(DataInput arg0) throws IOException { 
      this.username=arg0.readUTF(); 
       this.count=arg0.readInt(); 
       this.fof=arg0.readUTF(); 
 }
  public void write(DataOutput arg0) throws IOException { 
       arg0.writeUTF(username);       
        arg0.writeInt(count); 
         arg0.writeUTF(fof); 
  }
   /**     * 相當於 Object對象equals     */
   @Override 
    public int compareTo(FriendOfFriend o) {
           int result = this.username.compareTo(o.getUsername()); 
             if(result == 0){ 
               return Integer.compare(o.count, this.getCount()); 
               }
                 return result; 
     }
}

結果下圖所示。

在這裏插入圖片描述

第二步:創建分組類FriendRecommendGroup

創建用於對好友數據進行分組的的java代碼,用來將具有相同好友的人分配到同一組中。創建分組類命令如下。

vi FriendRecommendGroup.java

結果如下圖所示。
  
在這裏插入圖片描述
  
代碼如下:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.io.WritableComparable;
public class FriendRecommendGroup extends WritableComparator { 
 public FriendRecommendGroup(){ 
      super(FriendOfFriend.class,true); 
 }
 public int compare(WritableComparable a, WritableComparable b) {    
       FriendOfFriend k1=(FriendOfFriend) a;        
        FriendOfFriend k2=(FriendOfFriend) b;     
        return k1.getUsername().compareTo(k2.getUsername()); 
}
}

結果如下圖所示。

在這裏插入圖片描述

第三步:創建執行好友推薦的任務類

創建用於執行好友推薦任務的job類,該類能夠讀取好友數據調並調用用於完成好友推薦計算的Map與Reduce最後調用用來排序的Map和Reduce類完成任務。創建job任務類命令如下。

vi FriendRecommendJob.java

結果如下圖所示。

在這裏插入圖片描述
  
代碼如下:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;  
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.conf.Configuration; 
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.io.*;
public class FriendRecommendJob { 
  public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf =new Configuration(); 
         conf.set("fs.defaultFS","hdfs://localhost:9000");
         FileSystem fs = FileSystem.get(conf); 
          Job job = Job.getInstance(conf); 
          job.setJobName("friend"); 
           job.setJarByClass(FriendRecommendJob.class); 
            job.setMapperClass(FriendRecommendMap.class);
            job.setReducerClass(FriendRecommendReduce.class); 
            job.setMapOutputKeyClass(Text.class); 
             job.setMapOutputValueClass(IntWritable.class);
             FileInputFormat.addInputPath(job, new Path("/data/input")); 
              Path outdir =new Path("/data/output"); 
              if(fs.exists(outdir)){ 
                    fs.delete(outdir, true); 
            }
             FileOutputFormat.setOutputPath(job, outdir);//path 一個目錄,而且不能存在
              boolean f =job.waitForCompletion(true);  
              if (f){ 
                    System.out.println("job---1 執行成功!");
                     job =Job.getInstance(conf); 
                     job.setJobName("f2");
                     job.setJarByClass(FriendRecommendJob.class); 
                      job.setMapperClass(SortMap.class); 
                      job.setReducerClass(SortReduce.class); 
                      job.setMapOutputKeyClass(FriendOfFriend.class); 
                       job.setMapOutputValueClass(NullWritable.class); 
                        job.setGroupingComparatorClass(FriendRecommendGroup.class); 
                        job.setNumReduceTasks(1); 
                        FileInputFormat.addInputPath(job, new Path("/data/output")); 
                        //mapreduce執行之後的結果存放到hdfs中一個目錄中,該目錄不能存在 
                        Path outpath =new Path("/data/output2"); 
                        if(fs.exists(outpath)){ 
                               fs.delete(outpath, true); 
                       }
                       FileOutputFormat.setOutputPath(job, outpath); 
                       boolean f2 =job.waitForCompletion(true); 
                        if(f2){ 
                              System.out.println("job---2 成功執行!");
                        }
                }
     }
}

結果如下圖所示。

在這裏插入圖片描述

第四步:創建數據切分類

創建用於對數據進行切分的類,將數據按照空格進行切分並將用戶作爲Key好友作爲Value的形式進行保存用於Reduce類對其進行數據分析,創建數據切分類命令如下。

vi FriendRecommendMap.java

結果如下圖所示。

在這裏插入圖片描述

代碼如下:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.*;
public class FriendRecommendMap extends Mapper<LongWritable,Text,Text,IntWritable>{
private Text outputKey; 
private IntWritable outputValue; 
@Override 
protected void setup(Context context) throws IOException, InterruptedException {
           outputKey = new Text(); 
           outputValue = new IntWritable(); 
       }
@Override 
 protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
     //輸入爲 user|friend1,friend2... 
      String[] splits = value.toString().split("\\|"); 
       if (splits.length < 2){ 
            return ;
      }
      String user = splits[0]; 
      String[] friends = splits[1].split(",");       
      //排序保證輸出的時候是有序的 
      Arrays.sort(friends); 
       for (int i = 0; i < friends.length; i++) { 
          String f1 = friends[i]; 
           int result = user.compareTo(f1); 
            if(result<0){ 
               outputKey.set(user+"|"+f1); 
           }else{ 
               outputKey.set(f1+"|"+user); 
           }
            //設置爲2表示是直接好友
            outputValue.set(2); 
             context.write(outputKey,outputValue); 
              for (int j = i+1; j < friends.length; j++) { 
                  String f2 = friends[j]; 
                  outputKey.set(f1 + "|" + f2); 
                  outputValue.set(1); 
                   context.write(outputKey, outputValue);
               }
       }
}
}

結果如下圖所示。

在這裏插入圖片描述

第五步:創建好友推薦Reduce類

創建好友推薦Reduce類,該類能夠計算出兩個人之間潛在好友關係,並將又可能認識的人推薦給對方完成好友推薦。創建好友推薦Reduce類命令如下。

vi FriendRecommendReduce.java

結果如下圖所示。

在這裏插入圖片描述

代碼如下:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.io.*;
public class FriendRecommendReduce extends Reducer<Text,IntWritable,Text,IntWritable> {
 @Override 
  protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
      int sum =0; 
       boolean f = true; 
       for(IntWritable i:values){ 
           if(i.get()==2){ 
               f=false; 
                break; 
           }else{ 
                sum=sum+i.get(); 
           }
      }
      //是直接好友就直接過濾掉 
       if(f){ 
          context.write(key, new IntWritable(sum)); 
      }
  }
}

結果如下圖所示。

在這裏插入圖片描述

第六步:創建排序Map類

以上代碼已經完成了好友推薦任務,爲了便於保存和再次進行類似數據分析需要對數據進行排序保存操作,map類用於對數據計算結果按照“\t”進行切分,創建排序Map類命令如下。

vi SortMap.java

結果如下圖所示。

在這裏插入圖片描述

代碼如下:

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.*;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.io.*;
public class SortMap extends Mapper<LongWritable,Text,FriendOfFriend,NullWritable>{ 
private FriendOfFriend outputKey;
@Override    
protected void setup(Context context) throws IOException, InterruptedException {
     outputKey = new FriendOfFriend(); 
 }
 @Override 
  protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
      String[] splits = value.toString().split("\t"); 
       if (splits.length < 2 ){ 
                return; 
        }
        String relation = splits[0]; 
        String user = relation.split("\\|")[0]; 
        String friend = relation.split("\\|")[1]; 
        int count = Integer.parseInt(splits[1]); 
        outputKey.setUsername(user); 
        outputKey.setFof(friend); 
        outputKey.setCount(count); 
        context.write(outputKey,NullWritable.get()); 
        outputKey.setUsername(friend); 
        outputKey.setFof(user); 
        outputKey.setCount(count); 
        context.write(outputKey,NullWritable.get()); 
    }
}

結果如下圖所示。

在這裏插入圖片描述

第七步:創建排序Reduce類

最終完成數據排序和代碼編寫,好友推薦數據分完成,創建排序Reduce類命令如下。

vi SortReduce.java

結果如下圖所示。

在這裏插入圖片描述

代碼如下:

   import java.io.DataInput; 
   import java.io.DataOutput;
   import java.io.IOException;
   import java.util.*;
   import org.apache.hadoop.mapreduce.Mapper;
   import org.apache.hadoop.mapreduce.Reducer;
   import org.apache.hadoop.io.*;
   public class SortReduce extends Reducer<FriendOfFriend,NullWritable,Text,Text> { 
   @Override 
   protected void reduce(FriendOfFriend key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException { 
         for (NullWritable ignored :values){ 
               context.write(new Text(key.getUsername()),new Text(key.getFof() + "|" + key.getCount()));
           }
   }
}

結果如下圖所示。

在這裏插入圖片描述

3、運行代碼

第一步:編譯代碼

javac -cp .:/usr/local/hadoop/share/hadoop/common/* FriendOfFriend.java
javac -cp .:/usr/local/hadoop/share/hadoop/common/* FriendOfFriend.java FriendRecommendGroup.java
javac -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/* FriendRecommendMap.java
javac -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/* FriendRecommendReduce.java
javac -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/* FriendOfFriend.java SortMap.java
javac -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/* FriendOfFriend.java SortReduce.java

第二步:創建實驗數據

此實驗使用的是示列數據,實驗的數據 adada.txt 文件。

  • 命令:vi adada.txt
      添加數據:

    aaa|bbb,ccc,ddd

    bbb|ccc,eee,fff

    ccc|ddd,aaa,eee

    ggg|ddd,eee,aaa,fff

第三步:輸入輸出路徑

設置好輸入輸出路徑,可以看到代碼中有兩個輸出路徑,注意輸出文件不能已經存在,在時輸出的時候創建的,實驗中輸出文件爲 output,若 output 已經存在,則將 output 文件作爲新的輸入文件,然後輸出新文件爲 output2。讀者也可以自定義輸出路徑及文件名。

將adada.txt上傳到hdfs。
hadoop fs -mkdir -p /data/input
hadoop fs -put adada.txt /data/input

結果如下圖所示。

在這裏插入圖片描述

編譯FriendRecommendJob.java程序,命令如下。

javac -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/* FriendRecommendJob.java

第四步:執行代碼

可以看到執行結果中爲 job–2 成功執行!。

  • 運行程序:
 java -cp .:/usr/local/hadoop/share/hadoop/common/*:/usr/local/hadoop/share/hadoop/tools/lib/*:/usr/local/hadoop/share/hadoop/mapreduce/*:/usr/local/work/*:/usr/local/hadoop/share/hadoop/hdfs/*:/usr/local/hadoop/share/hadoop/yarn/* FriendRecommendJob

執行結果如下圖所示。

在這裏插入圖片描述

在這裏插入圖片描述

第五步:查看結果

查看結果數據 part-r-00000 文件:

hadoop fs -cat /data/output2/part-r-00000

結果如下圖所示。

在這裏插入圖片描述

  可以看出,計算出來每兩個人之間的共同好友數量,並且排序好了就可以基於這個結果進行好友推薦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章