利用Hadoop实现求共同好友的示例详解

目录
  • 前言
  • 业务分析
  • 实现思路分析
  • 编码实现
    • 1、第一个map类
    • 2、第一个Reduce类
    • 3、第一个Job类
    • 4、第二个map类
    • 5、第二个Reducer类
    • 6、第二个Job类

前言

在很多社交APP中,比如大家熟悉的QQ好友列表中,打开会话框,经常可以看到下面有一栏共同好友的推荐列表,用户通过这种方式,可以添加潜在的关联好友

这种功能该如何实现呢?对redis比较了解的同学应该能很快想到,可以使用redis来实现这个功能。没错,redis确实是个不错的可以实现这个功能的方案。

但redis的实现有一定的局限性,因为redis存储和数据和计算时需要耗费较多的内存资源,设想一下,像腾讯QQ这样的规模,如果用这种方式做的话,估计Redis服务器的投入成本将是一笔不小的开销。

利用hadoop中的MapReduce同样可以实现这个功能,该如何实现呢?

业务分析

下面是原始的数据文件,第一栏可理解为本人,第二行为该用户的好友列表,以逗号分割,比如A用户的好友包括:B,C,D,F,E,O这几个,后面的行依次类推

A:B,C,D,F,E,O
B:A,C,E,K
C:F,A,D,I
D:A,E,F,L
E:B,C,D,M,L
F:A,B,C,D,E,O,M
G:A,C,D,E,F
H:A,C,D,E,O
I:A,O
J:B,O
K:A,C,D
L:D,E,F
M:E,F,G
O:A,H,I,J

现在的需求是:通过原始的数据文件,输出该文件中所有用户中哪些人两两之间存在共同好友并输出,格式如下:

A-B C,E
A-C    F,D
A-D    E,F
......

实现思路分析

步骤一:将原始数据拆分为如下格式

通过这一步,得到一组K/V,可以清晰的反映出一个用户的所有好友

B:A            #B是A的好友
C:A            #C是A的好友
D:A            #D是A的好友
F:A
E:A
O:A

A:B
C:B
E:B
K:B

F:C
A:C
D:C
I:C

B:E
C:E
D:E
M:E
L:E

步骤二、对第一步的数据进一步处理成如下格式

从第一步格式完毕后的数据,可以很明显看出并总结出一个规律,那就是左边那些用户的好友列表,以C用户为例,可以看出C这个用户有A,B,E三个好友,反过来讲,ABE这三个用户,他们有一个共同的好友A

其他的类推进行理解

C  A-B-E  #C是A和B和E的共同好友
D  A-C      #D是A和B的共同好友
A  B-C      #A是B和C的共同好友
B  A-E    #A是E和B的共同好友
......

步骤三、将步骤二中的数据调换位置

从步骤2中我们得知,C的好友有ABE,反过来说,ABE他们的共同好友有C,针对这种超过3个的,可以考虑下一步进行两两组合即可

A-B-E   C     #A、B、E有共同好友C
A-C     D     #A与C有共同好友D
B-C     A     #B与C有共同好友A
A-E     B     #A与E有共同好友B

步骤四、将步骤三得到的数据继续拆分

步骤三中,像 : A-B-E C 这种数据,显然需要进一步拆分,因为最终的结果是求取两两好友之间的共同好友,所以可以拆为: A-B C,A-E C,B-E C,为下一步数据组合做最后的准备

A-B  C
A-E  C
B-E  C
A-C  D
B-C  A
A-E  B
......

步骤五、将步骤四得到的数据合并

在使用MapReduce编程中我们知道,Map阶段出去的数据,进入reduce方法中的数据都是key相同的,以第四步中的: A-E 这个key为例,就有2个,这样通过 reduce方法最终输出的结果就是: A-E C,B ,即A-E 这两个用户的共同好友为 C和B

A-B  C        #A,B共同好友有C
A-E  C,B      #A,E有共同好友 C,B
B-E  C        #B,E有共同好友 C
A-C  D        #A,C有共同好友 D
B-C  A        #B,C有共同好友 A
......

通过以上的数据分析,最终可以达到预期的效果,同时也可以看出,上面的步骤划分到MapRedcue中,显然一个MapReduce肯定是无法完成的,至少需要2个

下面是结合上面的步骤分析,得出需要两个MapReduce的数据流程图,参考这个图来协助我们分析编写代码逻辑做参考

编码实现

1、第一个map类

public class FirstMapper extends Mapper<LongWritable,Text,Text,Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String val = value.toString();
        String[] split = val.split(":");
        //A:B,C,D,F,E,O  拆分后,左边是原用户,右边是好友
        String user = split[0];
        String friends = split[1];
        String[] friendLists = friends.split(",");
        //Map1 输出的结果为 :
        /**
         * B A
         * C A
         * D A
         * F A
         * E A
         */
        for(String str :friendLists ){
            context.write(new Text(str),new Text(user));
        }
    }

}

2、第一个Reduce类

public class FirstReducer extends Reducer<Text,Text,Text,Text> {

    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        StringBuffer stringBuffer = new StringBuffer();
        for (Text text : values){
            stringBuffer.append(text).append("-");
        }
        //最终写出去的数据格式为: A-E B ......
        context.write(new Text(stringBuffer.toString()),key);
    }

}

3、第一个Job类

public class FirstJob {

    public static void main(String[] args) throws Exception {

        //1、获取job
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        //2、设置jar路径
        job.setJarByClass(FirstJob.class);

        //3、关联mapper 和 Reducer
        job.setMapperClass(FirstMapper.class);
        job.setReducerClass(FirstReducer.class);

        //4、设置 map输出的 key/val 的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        //5、设置最终输出的key / val 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        //6、设置最终的输出路径
        String inputPath = "F:\\网盘\\csv\\friends.txt";
        String outPath = "F:\\网盘\\csv\\friends1";

        FileInputFormat.setInputPaths(job,new Path(inputPath));
        FileOutputFormat.setOutputPath(job,new Path(outPath));

        // 7 提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }

}

运行上面的Job代码,然后打开运行完毕后的第一个阶段的文件,从内容格式上看,符合第一阶段的输出结果要求的, 即下面的这种数据格式

4、第二个map类

public class SecondMapper extends Mapper<LongWritable,Text,Text,Text> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

        // I-K-C-B-G-F-H-O-D-    A  阶段1的文件输出格式
        /**
         * 最终输出格式:
         * I-K A
         * I-C A
         * I-B A
         * ......
         */
        //需要将左边的数据进行两两拆分,与V进行组合输出
        String val = value.toString();
        String[] split = val.split("\t");

        String v2 = split[1];
        String[] allUsers = split[0].split("-");
        Arrays.sort(allUsers);

        for(int i=0;i<allUsers.length-1;i++){
            for(int j=i+1;j<allUsers.length;j++){
                context.write(new Text(allUsers[i] + "-" + allUsers[j]),new Text(v2));
            }
        }
    }
}

5、第二个Reducer类

public class SecondReducer extends Reducer<Text,Text,Text,Text> {

    @Override
    protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
        //上一步输出的结果:
        /**
         * A-B C
         * A-B D
         * A-E C
         * A-E D
         * ......
         */
        //只需要将相同的key的Val进行组合即可,即 : A-B C-D,A-E C-D
        StringBuffer stringBuffer = new StringBuffer();
        for (Text text :values ){
            stringBuffer.append(text.toString()).append("-");
        }
        context.write(key,new Text(stringBuffer.toString()));
    }

}

6、第二个Job类

public class SecondJob {
    public static void main(String[] args) throws Exception {

        //1、获取job
        Configuration configuration = new Configuration();
        Job job = Job.getInstance(configuration);

        //2、设置jar路径
        job.setJarByClass(SecondJob.class);

        //3、关联mapper 和 Reducer
        job.setMapperClass(SecondMapper.class);
        job.setReducerClass(SecondReducer.class);

        //4、设置 map输出的 key/val 的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(Text.class);

        //5、设置最终输出的key / val 类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(Text.class);

        //6、设置最终的输出路径
        String inputPath = "F:\\网盘\\csv\\friends1\\part-r-00000";
        String outPath = "F:\\网盘\\csv\\friends2";

        FileInputFormat.setInputPaths(job,new Path(inputPath));
        FileOutputFormat.setOutputPath(job,new Path(outPath));

        // 7 提交job
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }

}

运行上面的Job代码,查看最终的输出结果,可以看到,也是符合我们预期的业务的

以上就是利用Hadoop实现求共同好友的示例详解的详细内容,更多关于Hadoop求共同好友的资料请关注我们其它相关文章!

时间: 2022-01-14

利用Python查看微信共同好友功能的实现代码

总有思路清奇的朋友存在,想实现查看微信共同好友: 由于之前分享的代码有获取过微信好友头像,所以当时第一反应是通过itchat微信接口获取好友信息,比对两个人的好友信息列表就可以实现了.按理说这么简单的话,应该早有现成的代码了,然而并没有搜到,那正好,拿来练练手! 先放最终结果图: 思路 首先通过itchat这个微信个人号接口扫码登录个人微信网页版,获取可以识别好友身份的数据.这里是需要分别登录两人微信的,拿到两人各自的好友信息存到列表中. 这样一来,查共同好友就转化成了查两个列表中相同元素的问题

redis实现共同好友的思路详解

背景 ​ 微信朋友圈的点赞.评论,只能看到自己好友的信息.这就涉及到了一个共同好友的概念,通过redis的set集合可以很轻松的实现此功能. 共同好友实现思路 每个人的好友存放在set集合中.key的名字为friend_{userId}.如下图: 用户1的好友为2,3,4 用户2的好友为1,3,4 用户3的好友为1,4,5 交集 用户1和2是好友.他们的共同好友可以通过他们的交集获取. redis命令示例: 127.0.0.1:6379> sadd friend_1 2 3 4 (integer

基于redis实现的点赞功能设计思路详解

前言 点赞其实是一个很有意思的功能.基本的设计思路有大致两种, 一种自然是用mysql等 数据库直接落地存储, 另外一种就是利用点赞的业务特征来扔到redis(或memcache)中, 然后离线刷回mysql等. 直接写入Mysql 直接写入Mysql是最简单的做法. 做两个表即可, 1.post_like 记录文章被赞的次数,已有多少人赞过这种数据就可以直接从表中查到; 2.user_like_post 记录用户赞过了哪些文章, 当打开文章列表时,显示的有没有赞过的数据就在这里面; 缺点 1.

redis 实现登陆次数限制的思路详解

title: redis-login-limitation  利用 redis 实现登陆次数限制, 注解 + aop, 核心代码很简单. 基本思路 比如希望达到的要求是这样: 在 1min 内登陆异常次数达到5次, 锁定该用户 1h 那么登陆请求的参数中, 会有一个参数唯一标识一个 user, 比如 邮箱/手机号/userName 用这个参数作为key存入redis, 对应的value为登陆错误的次数, string 类型, 并设置过期时间为 1min. 当获取到的 value == "4&qu

python发qq消息轰炸虐狗好友思路详解(完整代码)

因为我的某个好友在情人节的时候秀恩爱,所以我灵光一闪制作了qq消息轰炸并记录了下来. 首先 我的编程环境是: windows 10系统 python3.6 记得要下载win32 pip install win32 思路介绍 其实也非常简单 将要发出去的句子储存在列表中 然后用随机模块调用 将随机出来的元素储存在剪贴板中 连接QQ 找到指定对象 疯狂输出 怎么样,简单吧 开始打代码吧 import random import win32gui as a import win32con as b i

Redis 实现队列原理的实例详解

Redis 实现队列原理的实例详解 场景说明: ·用于处理比较耗时的请求,例如批量发送邮件,如果直接在网页触发执行发送,程序会出现超时 ·高并发场景,当某个时刻请求瞬间增加时,可以把请求写入到队列,后台在去处理这些请求 ·抢购场景,先入先出的模式 命令: rpush + blpop 或 lpush + brpop rpush : 往列表右侧推入数据 blpop : 客户端阻塞直到队列有值输出 简单队列: simple.php $stmt = $pdo->prepare('select id, c

AJAX页面状态保持思路详解

传统的页面,浏览器通过url访问页面,页面的内容由后台服务生成页面所有内容再发回给浏览器渲染展示.到AJAX流行的时候,很多信息为AJAX异步请求,比如:点击.翻页等.通常这种情况你一刷新浏览器,当前页面就会重置到初始状态.更不用说把看到的信息url发给好友了. 传统的状态保存在地址栏,如: www.abc.com/search?s=abc&id=23&page=3 如果通过这种方式的话,浏览器会刷新页面,如果使用锚点的话则不会刷新浏览器.具体是点击页面去请求数据的同时会改变地址栏&quo

关于日期正则表达式的思路详解

1        概述 首先需要说明的一点,无论是Winform,还是Webform,都有很成熟的日历控件,无论从易用性还是可扩展性上看,日期的选择和校验还是用日历控件来实现比较好. 前几天在CSDN多个版块看到需要日期正则的帖子,所以整理了这篇文章,和大家一起讨论交流,如有遗漏或错误的地方,还请大家指正. 日期正则一般是对格式有要求,且数据不是直接由用户输入时使用.因应用场景的不同,写出的正则也不同,复杂程度也自然不同.正则的书写需要根据具体情况具体分析,一个基本原则就是:只写合适的,不写复杂

PHPCMS V9 添加二级导航的思路详解

今天看了看phpcms 写到二级导航时发现点问题,查询导航栏的信息时返回的$r[arrchildid]与自己想象的不符,文档上说是返回子栏目id但是却有些不同. 开始的思路: <ul class="nav navbar-nav"> <li class="active"><a href="{siteurl($siteid)}">首页</a></li> {pc:content action=

redis数据结构之intset的实例详解

redis数据结构之intset的实例详解 在redis中,intset主要用于保存整数值,由于其底层是使用数组来保存数据的,因而当对集合进行数据添加时需要对集合进行扩容和迁移操作,因而也只有在数据量不大时redis才使用该数据结构来保存整数集合.其具体的底层数据结构如下: typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; }

JS中artdialog弹出框控件之提交表单思路详解

artDialog是一个基于javascript编写的对话框组件,它拥有精致的界面与友好的接口. 前言: 自适应内容 artDialog的特殊UI框架能够适应内容变化,甚至连外部程序动态插入的内容它仍然能自适应,因此你不必去考虑消息内容尺寸使用它.它的消息容器甚至能够根据宽度让文本居中或居左对齐--这一切全是XHTML+CSS原生实现. 完善的接口 它的接口完善,可以轻易与外部程序配合使用.如异步写入消息.控制位置.尺寸.显示与隐藏.关闭等. 细致的体验 如果不是在输入状态,它支持Esc快捷键关

jQuery validate+artdialog+jquery form实现弹出表单思路详解

功能描述: 在页面弹出一个form表单,ajax无刷新提交表单,表单需通过验证. 适用范围: 适用于在列表页面新增,修改记录. 需要加载的js文件: jquery.min.js artDialog.js iframeTools.js jquery.form.js jquery.validate.js 实现思路: 在页面中将表单放到一个隐藏的容器中,用artdialog弹出该form并对form加上jqueryvalidate验证,提交采用jqueryform ajax提交,由于都是用现成的插件写