feed流的技术实现

:-}

场景

我们现在好多软件都有一个类似朋友圈的功能:微博、微信、QQ…… 只要涉及到好友、粉丝这样的 app 或者是网站,一定有这样的一个功能。那这个功能是怎么样来实现的呢?

功能

简单来说,Feeds这块主要包括两块内容

生成feeds
更新feed

生成feeds

比如我们已经关注的人做了特定操作,我们需要把这些活动加入你的feeds,让你接收到。
比如某大牌明星发了个微博,这时候我们首先找到明星的所有关注者,然后给需要推送的关注者推送此微博,大家可以把每个人的feeds简单想象为一个个的有序列表,推送很简单,就是在每个人的列表末尾添加这个操作。

更新feeds

你新关注了一个人,需要把他的活动加入已有feeds。这时候我们需要取出此人的活动历史,然后按照时间顺序把这些历史塞到你的feeds中。此操作的复杂度会比较高,需要使用合适的数据结构来达到最佳性能,目前是O(log(N))。

取消关注

你的关注点做了一些更新操作,比如你取消关注了一个人,那么他的动态就要在feeds中清除掉。

难点

我们可以在用常用的mysql设计个2个数据表,一个表存人和人的关注关系,一个表存发布动态的信息。这样通过链表查询就可以实现feeds流了。但是我们知道feeds流的数据量级应该会非常大,性能和可用性就会直线下降,单一依靠mysql实现feeds已经无法应对业务的增长。

实现

推模式

什么是推模式?推模式就是,用户A关注了用户B,用户B每发送一个动态,后台遍历用户B的粉丝,往他们粉丝的feed里面推送一条动态。

拉模式

与推模式相反,拉模式则是,用户每次刷新feed第一页,都去遍历关注的人,把最新的动态拉取回来。

但是,不管推模式还是拉模式都存在若关注数量或者粉丝数量过多,导致遍历时间太长的问题,怎么去解决 ?这里就出现了第三种模式,推拉模式。

推拉模式

这是一种折中的解决方案,就是在线推,离线拉。粉丝几百上千万, 跟你发布动态同时在线的肯定也就只有那么顶天几百几千几万,何况这类大V很少,只推给在线的粉丝,离线的粉丝上线后,再去拉取动态即可!但是,不管是什么模式,每个用户都会维护一个类似发件箱跟收件箱的东西,保存自己发过的动态以及Feed动态(具体实现看下面),来完成推与拉。

而这里讲的,肯定就是推拉模式,用户A关注了用户B , 用户B发布动态则将动态推进用户A的feed,这里使用redis的zset实现,sort为time(记得以毫秒为时间戳,秒级在数据量达到一定程度后,会有读取不到的问题,比如以时间戳为分页页码),value为具体的动态 ID(为什么是动态ID, 其实很简单, 就是因为动态的内容可以进行缓存,在redis里面全部走ID,修改动态内容也需要修改一处,动态内容可以保存在hash结构里), 每个用户维护一个zset保存我发布的动态,一个zset保存我的feed动态,过期时间3~7天看情况而定。为什么要设计过期时间后面会细说。

OK,全局维护一个在线用户列表,怎么设计这个就自己琢磨了,为了防止用户挂后台导致与服务端为离线状态,所以最好是1~3小时未操作或者离线时间不大于3小时的,都当做在线处理,反正这个看情况定。

那么,当用户发了一条动态后,后台会有以下这些操作:
在线推: 异步遍历在线的粉丝,将动态ID,添加到粉丝的Feed中。
离线拉: 离线用户打开APP后,我们是会请求一个公共的入口接口,主做统计以及其他初始化操作,在这里,我们也开了一个异步线程,对用户进行Feed更新操作,防止用户进入APP后等待拉取时间过长,毕竟关注成千上万的人肯定有(其实万单位以下遍历都很快)。拉取过程其实就是把自己最后一条Feed的时间戳取出,去遍历关注的人的feed,将大于该时间的ID全部拉取回来。用户进入APP后,刷新即可看到最新操作。

另:如果有Feed新消息数提示的需求,可以在推拉的同时进行增加, 刷新feed时清空即可。

用户feed里面过长,占用内存怎么办?

我是这么处理的,一个用户的feed第一次拉取的时候,feed长度为500条,在我们APP里,相当于50页,而后的数据,都走数据库。

大页码翻页其实就是个伪需求而且耗性能的东西,用户除了第一次用这个APP,才会翻到底,第一次使用, 能有几个动态 ?而对于二次使用以上的用户,一般来讲, 翻了几页就已经到达上一次看过的地方了,所以500条数据,在关注量一般的情况下,内容已经足够消费,甚至达到疲劳,可能有关注量很大的用户他的Feed每天可能有很多很多动态,但是,不用说,肯定是做广告的,关注一堆人等着回粉,这种人更不会去消费内容,50页的内容,翻起来都累。当然,并不是说放弃了这些人,feed找不到走数据库嘛~~~~爱走不走,想走就给我翻50页再说~

每个用户都维护自己的动态跟Feed队列,当用户上百万时,内存的占有量肯定不小,要怎么释放内存才合适 ?

这里就回到上面那个问题了,为什么要给feed的key设计过期时间?为什么是设计3~7天过期时间?

原因有以下:

一、一个用户3~7天不打开APP,可能已经对APP失去兴趣了,打开几率很小,或者已经被卸载了,没有存在的意义了。
二、3~7天未登陆APP,关注的人发的动态也不少了,Feed未拉取回来的数据肯定也不少,那么这时候去遍历其实拉取量很大,那么还不如直接全部重新拉一边或者拉取用户最后登陆时间后产出的数据。

到这里,其实已经差不多了,大部分业务逻辑已经足够满足,并且速度也理想,目前我们线上这种模式走了半年,feed一般都是10~80ms响应完毕。

<?php 
echo '求关注 求喜欢';
?>