1.应用场景
二度好友发现在社交网络中大量使用,QQ、微博、微信、社交网站等经常会推荐添加好友,其依据就是用户之间的好友关系。
几个概念:
一度好友是你的好友
二度好友是好友的好友,二度好友发现是社交网络关系发现中的重要问题。
从数据库设计的角度出发,即所谓的第三范式,尽量避免冗余,好友关系的表结构一般被设计为“用户-好友”,且一般用id来描述关系。如
user friend
A B
B C
A D
D C
C D
E F
E A
E B
B D
二度好友关系发现的即找到A、C的共同好友B、D,找到二度好友关系后就可以根据共同好友的多少给相应的用户推荐好友了。
注:有些文章把好友关系的数据结构描述成“A:B,C,D,F,E”,这是已经预处理的结果,一般不会直接这么存储。
2.实现分析
(1)数据预处理
场景描述中的数据,如果是设计良好的数据表结构,A和B是好友,则代表着B和A也是好友,但有很大可能在实际业务过程中造成了好友关系表中同时维护了A-B和B-A,而其他好友关系则是单向关系,这就为数据分析带来了一定的麻烦,针对这一问题,我们可以在数据预处理阶段先对双向关系进行数据去重,只保留单向关系,然后统一输出双向关系。结果如
A B
B A
B C
C B
A D
D A
C D
D C
E F
F E
E A
A E
E B
B E
B D
D B
(2)数据分析
a.首先找到每个用户的所有好友,即以用户为Key进行聚合,产生“(用户,(友,友,友,…))”的中间结果,输出如:
A:E,D,B
B:D,E,A,C
C:D,B
D:B,A,C
E:A,B,F
F:E
b.循环地对上一步输出的每对关系中的“(友,友,友,…)”两两配对进行输出“((友-友),用户)”,Mapper之后由Shuffle机制合并成“((友-友),(用户,用户,用户,…))”的结果,在Reducer中按照将聚合结果后循环输出即可实现。输出结果
A-B 2:E D
A-C 2:D B
A-D 1:B
A-E 1:B
A-F 1:E
B-C 1:D
B-D 2:C A
B-E 1:A
B-F 1:E
C-D 1:B
C-E 1:B
D-E 2:A B
3.关键代码分析
3.1数据去重
使用组合键对A-B、B-A进行去重,对象O1(A-B)跟对象O2(B-A)使用compareTo方法比较时返回0,其他情况不相等即可。
public int compareTo(RelationKey o) {
if (o.b.equals(a) && o.a.equals(b)) {
return 0;
}
return 1;
}
3.2二度好友两两匹配
根据用户聚合后,两两匹配输出二度好友关系。
/**
* 输入:A:B,C,D
* 输出:((B-C),A)((B-D),A)((C-D),A)
*/
static class FriendMapper extends Mapper<Object, Text, Text, Text> {
@Override
protected void map(Object key, Text value, Context context)
throws IOException, InterruptedException {
String[] words = value.toString().split(":");
String user = words[0];
String[] friends = words[1].split(",");
//排序:避免出现A-B、B-A重复输出的情况
Arrays.sort(friends);
//循环两两匹配
for (int i = 0; i < friends.length - 1; i++) {
for (int j = i + 1; j < friends.length; j++) {
context.write(new Text(friends[i] + "-" + friends[j]), new Text(user));
}
}
}
}
4.完整代码
完整代码见:https://github.com/majxbear/mapreduce-applications
4.1预处理:数据去重
4.1.1组合键定义
/**
* 好友关系组合键,用于去重
*/
public class RelationKey implements WritableComparable<RelationKey> {
private String a;
private String b;
/**
* 默认构造方法必须有,否则会报<init>失败
* java.lang.NoSuchMethodException: .<init>()
*/
public RelationKey() {
}
public RelationKey(String a, String b) {
this.a = a;
this.b = b;
}
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(