Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >基于时间线的Feed流后台系统设计

基于时间线的Feed流后台系统设计

作者头像
腾讯大讲堂
发布于 2020-11-09 02:10:07
发布于 2020-11-09 02:10:07
5.3K0
举报

| 导语  本文将总结一下常用的基于时间线Feed流的后台存储设计方案。结合具体的业务场景,讲述一下根据实际需求,在基本设计思路上做一些灵活运用。

01

背景介绍

Feed流产品在我们手机APP中几乎无处不在,常见的Feed流比如微信朋友圈、新浪微博、今日头条等。对Feed流的定义,可以简单理解为只要大拇指不停地往下划手机屏幕,就有一条条的信息不断涌现出来。就像给牲畜喂饲料一样,只要它吃光了就要不断再往里加,故此得名Feed(饲养)。

大多数Feed流产品都包含两种Feed流,一种是基于算法推荐,另一种是基于关注(好友关系)。例如下图中的微博和知乎,顶栏的页卡都包含“关注”和“推荐”这两种。两种Feed流背后用到的技术差别会比较大。本文将重点探索一下“关注”页卡的后台实现方式。

图片来源:新浪微博

图片来源:知乎

不同于“推荐”页卡那种千人前面算法推荐的方式,通常“关注”页卡所展示的内容先后顺序都有固定的规则,最常见的规则是基于时间线来排序,也就是展示“我关注的人所发的帖子,根据发帖时间从晚到早依次排列”。

02

Feed流实现方案介绍

方案1——读扩散

读扩散也称为拉模式,这应该是最符合我们直觉的一种实现方式。如下图:

每一个内容发布者都有一个自己的发件箱(“我发布的内容”),每当我们发出一个新帖子,都存入自己的发件箱中。当我们的粉丝来阅读时,系统首先需要拿到粉丝关注的所有人,然后遍历所有发布者的发件箱,取出他们所发布的帖子,然后依据发布时间排序,展示给阅读者。

这种设计,阅读者读一次Feed流,后台会扩散为N次读操作(N等于关注的人数)以及一次聚合操作,因此称为读扩散。每次读Feed流相当于去关注者的收件箱主动拉取帖子,因此也得名拉模式。

这种模式的好处是底层存储简单,没有空间浪费。坏处是每次读操作会非常重,操作非常多。设想一下如果我关注的人数非常多,遍历一遍我所关注的所有人,并且再聚合一下,这个系统开销会非常大,时延上可能达到无法忍受的地步。因此读扩散主要适用系统中阅读者关注的人没那么多,并且刷Feed流并不频繁的场景。

拉模式还有一个比较大的缺点就是分页不方便,我们刷微博或朋友圈,肯定是随着大拇指在屏幕不断划动,内容一页一页的从后台拉取。如果不做其他优化,只采用实时聚合的方式,下滑到比较靠后的页码时会非常麻烦。

方案2——写扩散

据统计,大多数Feed流产品的读写比大概在100:1,也就是说大部分情况都是刷Feed流看别人发的朋友圈和微博,只有很少情况是自己亲自发一条朋友圈或微博给别人看。因此,读扩散那种很重的读逻辑并不适合大多数场景。我们宁愿让发帖的过程复杂一些,也不愿影响用户读Feed流的体验,因此稍微改造一下前面方案就有了写扩散。写扩散也称为推模式,这种模式会对拉模式的一些缺点做改进。如下图:

系统中每个用户除了有发件箱,也会有自己的收件箱。当发布者发表一篇帖子的时候,除了往自己发件箱记录一下之外,还会遍历发布者的所有粉丝,往这些粉丝的收件箱也投放一份相同内容。这样阅读者来读Feed流时,直接从自己的收件箱读取即可。

这种设计,每次发表帖子,都会扩散为M次写操作(M等于自己的粉丝数),因此成为写扩散。每篇帖子都会主动推送到所有粉丝的收件箱,因此也得名推模式。

这种模式可想而知,发一篇帖子,背后会涉及到很多次的写操作。通常为了发帖人的用户体验,当发布的帖子写到自己发件箱时,就可以返回发布成功。后台另外起一个异步任务,不慌不忙地往粉丝收件箱投递帖子即可。写扩散的好处在于通过数据冗余(一篇帖子会被存储M份副本),提升了阅读者的用户体验。通常适当的数据冗余不是什么问题,但是到了微博明星这里,完全行不通。比如目前微博粉丝量Top2的谢娜与何炅,两个人微博粉丝过亿。

图片来源:新浪微博

设想一下,如果单纯采用推模式,那每次谢娜何炅发一条微博,微博后台都要地震一次。一篇微博导致后台上亿次写操作,这显然是不可行的。另外由于写扩散是异步操作,写的太慢会导致帖子发出去半天,有些粉丝依然没能看见,这种体验也不太好。

通常写扩散适用于好友量不大的情况,据悉微信朋友圈正是写扩散模式。每一名微信用户的好友上限为5000人,也就是说你发一条朋友圈最多也就扩散到5000次写操作,如果异步任务性能好一些,完全没有问题。

方案3——读写混合模式

读写混合也可以称作推拉结合。这种方式可以兼具读扩散和写扩散的优点。我们首先来总结一下读扩散和写扩散的优缺点:

优点

缺点

适用场景

读扩散

节约存储空间发帖操作简单

读帖操作复杂关注人数多时是灾难

用户不活跃,很少读帖有大V粉丝量多,但每个粉丝关注的人少

写扩散

读帖操作简单

发帖操作复杂,浪费存储空间大V粉丝量多时是灾难

用户非常活跃,经常刷帖无大V,用户粉丝量都比较少

仔细比较一下读扩散与写扩散的优缺点,不难发现两者的适用场景是互补的。因此在设计后台存储的时候,我们如果能够区分一下场景,在不同场景下选择最适合的方案,并且动态调整策略,就实现了读写混合模式。如下图:

当何炅这种粉丝量超大的人发帖时,将帖子写入何炅的发件箱,另外提取出来何炅粉丝当中比较活跃的那一批(这已经可以筛掉大部分了),将何炅的帖子写入他们的收件箱。当一个粉丝量很小的路人甲发帖时,采用写扩散方式,遍历他的所有粉丝并将帖子写入粉丝收件箱。

对于那些活跃用户登录刷Feed流时,他直接从自己的收件箱读取帖子即可,保证了活跃用户的体验。当一个非活跃的用户突然登录刷Feed流时,我们一方面需要读他的收件箱,另一方面需要遍历他所关注的大V用户的发件箱提取帖子,并且做一下聚合展示。在展示完后,系统还需要有个任务来判断是否有必要将该用户升级为活跃用户。因为有读扩散的场景存在,因此即使是混合模式,每个阅读者所能关注的人数也要设置上限,例如新浪微博限制每个账号最多可以关注2000人。如果不设上限,设想一下有一位用户把微博所有账号全部关注了,那他打开关注列表会读取到微博全站所有帖子,一旦出现读扩散,系统必然崩溃;即使是写扩散,他的收件箱也无法容纳这么多的微博。

读写混合模式下,系统需要做两个判断。一个是哪些用户属于大V,我们可以将粉丝量作为一个判断指标。另一个是哪些用户属于活跃粉丝,这个判断标准可以是最近一次登录时间等。这两处判断标准就需要在系统发展过程中动态地识别和调整,没有固定公式了。

可以看出读写结合模式综合了两种模式的优点,属于最佳方案。然而他的缺点是系统机制非常复杂,给程序员带来无数烦恼。通常在项目初期,只有一两个开发人员,用户规模也很小的时候,一步到位地采用这种混合模式还是要慎重,容易出bug。当项目规模逐渐发展到新浪微博的水平,有一个大团队专门来做Feed流时,读写混合模式才是必须的。

Feed流中的分页问题

前文已经叙述了基于时间线的Feed流常见设计方案,但实操起来会比理论要麻烦许多。接下来专门讨论一个困难点——Feed流的分页。不管是读扩散还是写扩散,Feed流本质上是一个动态列表,列表内容会随着时间不断变化。传统的前端分页参数使用page_size和page_num,分表表示每页几条,以及当前是第几页。对于一个动态列表会有如下问题:

在T1时刻读取了第一页,T2时刻有人新发表了“内容11”,在T3时刻如果来拉取第二页,会导致错位出现,“内容6”在第一页和第二页都被返回了。事实上,但凡两页之间出现内容的添加或删除,都会导致错位问题。

为了解决这一问题,通常Feed流的分页入参不会使用page_size和page_num,而是使用last_id来记录上一页最后一条内容的id。前端读取下一页的时候,必须将last_id作为入参,后台直接找到last_id对应数据,再往后偏移page_size条数据,返回给前端,这样就避免了错位问题。如下图:

采用last_id的方案有一个重要条件,就是last_id本身这条数据不可以被硬删除。设想一下上图中T1时刻返回5条数据,last_id为内容6;T2时刻内容6被发布者删除;那么T3时刻再来请求第二页,我们根本找不到last_id对应的数据了,也就无法确认分页偏移量。通常碰到删除的场景,我们采用软删除方式,只是在内容上置一个标志位,表示内容已删除。由于已经删除的内容不应该再返回给前端,因此软删除模式下,找到last_id并往后偏移page_size条,如果其中有被删除的数据会导致获得足够的数据条数给前端。这里一个解决方案是找不够继续再往下找,另一种方案是与前端协商,允许返回条数少于page_size条,page_size只是个建议值。甚至大家约定好了以后,可以不要page_size参数。

03

实际业务应用

业务需求

文章最后结合我们自身业务,介绍一下实际业务场景中碰到的一个非常特殊的Feed流设计方案。直享直播是一款直播带货工具,主播可以创建一场未来时刻的直播,到时间后开播卖货,直播结束后,主播的粉丝可以查看直播回放。这样,每个直播场次就有三种状态——预告中(创建一场直播但还未开播)、直播中、回放。作为观众,我可以关注多位主播,这样从粉丝视角来看,也会有个直播场次的Feed流页面。这个Feed流最特殊的地方在于它的Feed流排序规则。

Feed流排序规则:

1.我关注的所有主播,正在直播中的场次排在最前;预告中的场次排中间;回放场次排最后

2.多场次都在直播中的,按开播时间从晚到早排序

3.多场次都在预告中的,按预计开播时间从早到晚排序

4.多场次都在回放的,按直播结束时间从晚到早排序

04

问题分析

本需求最复杂的点在于Feed流内容融入的“状态”因素,状态的转变会直接导致Feed流顺序不同。为了更清晰解释一下对排序的影响,我们可以用下图详细说明:

图中展示了4个主播的5个直播场次,作为观众,当我在T1时刻打开页面,看到的顺序是场次3在最上方,其余场次均在预告状态,按照预计开播时间从早到晚展示。当我在T2时刻打开页面,场次5在最上方,其余有三场在预告状态排在中间,场次3已经结束了所以排在最后。以此类推,直到所有直播都结束,所有场次最终的状态都会变为回放。

这里需要注意一点,如果我在T1时刻打开第一页,然后盯着页面不动,一直盯到T4时刻再下划到第二页,这时上一页的last_id,即分页偏移量很有可能因为直播状态变化而不知道飞到了什么位置,这会导致严重的错位问题,以及直播状态展示不统一的问题(第一页展示的是T1时刻的直播状态,第二页展示的是T4时刻的直播状态)。

05

解决方案

直播系统是个单向关系链,和微博有些类似,每个观众会关注少量主播,每个主播会可能有非常多的关注者。由于有状态变化的存在,写扩散几乎无法实现。因为如果采用写扩散的方式,每次主播创建直播、直播开播、直播结束这三个事件发生时导致的场次状态变化,会扩散为非常多次的写操作,不仅操作复杂,时延上也无法接受。微博之所以可以写扩散,就是因为一篇帖子发出后,这篇帖子就不会再有任何影响排序的状态转变。在我们场景中,“预告中”与“直播中”是两个中间态,而“回放”状态才是所有直播的最终归宿,一旦进入回放,这场直播也就不会再有状态转变。因此“直播中”与“预告中”状态可以采用读扩散方式,“回放”状态采取写扩散方式。

最终的方案如下图所示:

会影响直播状态的三种事件(创建直播、开播、结束直播)全部采用监听队列异步处理。我们为每一位主播维护一个直播中+预告中状态的优先级队列。每当监听到有主播创建直播时,将直播场次加入队列中,得分为开播的时间戳的相反数(负数)。每当监听到有主播开播时,把这场直播在队列中的得分修改为开播时间(正数)。每当监听到有主播结束直播,则异步地将播放信息投递到每个观众的回放队列中。

这里有一个小技巧,前文提到,直播中状态按照开播时间从大到小排序,而预告中状态则按照开播时间从小到大排序,因此如果将预告中状态的得分全部取开播时间相反数,那排序同样就成为了从大到小。这样的转化可以保证直播中与预告中同处于一个队列排序。预告中得分全都为负数,直播中得分全都为正数,最后聚合时可以保证所有直播中全都自然排在预告中前面。

另外前文还提到的另一个问题是T1时刻拉取第一页,T4时刻拉取第二页,导致第一页和第二页直播间状态不统一。解决这个问题的办法是通过快照方式。当观众来拉取第一页Feed流时,我们依据当前时间,将全部直播中和预告中状态的场次建立一份快照,使用一个session_id标识,每次前端分页拉取时,我们直接从快照中读取即可。如果快照中读取完毕,证明该观众的直播中和预告中场次全部读完,剩下的则使用回放队列进行补充。照此一来,我们的Feed流系统,前端分页拉取的参数一共有4个:

含义

值来源

读第一页时参数值

session_id

快照队列ID,从该快照中读取直播中和预告中场次

上一页返回值

空字符串

last_id

上一页读取到哪一场直播

上一页返回值

空字符串

state

枚举值0或1,表示last_id处于快照队列还是回放队列

上一页返回值

0

page_size

每页建议读几条

前后端约定

10

每当碰到session_id和last_id为空,则证明用户想要读取第一页,需要重新构建快照。这里还有一个衍生问题,session_id的如何取值?如果不考虑同一个观众在多端登录的情况,其实每一位观众维护一个快照id即可,也就是直接将系统用户id设为session_id;如果考虑多端登录的情况,则session_id中必须包含每个端的信息,以避免多端快照相互影响;如果不心疼内存,也可以每次随机一个字符串作为session_id,并设置一个足够长的过期时间,让快照自然过期。

以上设计,其实系统计算量最大的时刻就是拉取第一页,构建快照的开销。目前的线上数据,对于只关注不到10个主播的观众(这也是大多数场景),拉取第一页的QPS可以达到1.5万。如果将第二页以后的请求也算进来,Feed流的综合QPS可以达到更高水平,支撑目前的用户规模已经绰绰有余。如果我们拉取第一页时只获取到前10条即可直接返回,将构建快照操作改为异步,也许QPS可以更高一些,这可能是后续的优化点。

06

总结

读扩散、写扩散、读写混合,几乎所有基于时间线和关注关系的Feed流都逃不开这三种基本设计模式。具体到实际业务中,可能会有更复杂的场景,比如本文所说的状态流转影响排序,微博朋友圈场景中也会有广告接入、特别关注、热点话题等可能影响到Feed流排序的因素。这些场景就只能根据业务需求,做相对应的变通了。

▼近期热文▼

用户访谈(一):如何做好访谈前的准备工作

用户访谈(二):如何进行一场有效的访谈?

渠道质量评估模型

喜欢本文?快点“在看”支持一下

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯大讲堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
feeds流系统设计概述
什么是 Feeds 流? 从用户层面来说, 各种手机 APP 里面, 特别是社交类的, 我们可以看到关注的内容、好友的动态聚合成一个列表(最典型的就是微信朋友圈)都是 feeds 流的一种形式。
leobhao
2024/06/18
8940
feeds流系统设计概述
Feed 流系统的架构设计方案
本文主要针对 Feed 流进行介绍,将从 Feed 流的演变入手,带你一步步了解 Feed 流,而后学习如何从开发角度入手,对其进行建模,抽象出 Feed 流常见的架构,最终搭建高可用、高扩展、高性能的 Feed 流应用。
腾讯云开发者
2024/11/21
5980
Feed 流系统的架构设计方案
揭秘:微信 / 微博 / 头条 / 快手是如何轻松处理亿级规模的 Feed 流的?
差不多十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具代表性的产品就是微博、微信,以及后来的今日头条、快手等。这些移动互联网时代的新产品在过去几年间借着智能手机的风高速成长。
iMike
2019/09/17
1.5K0
揭秘:微信 / 微博 / 头条 / 快手是如何轻松处理亿级规模的 Feed 流的?
Feed流系统设计
差不多十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具代表性的产品就是微博、微信,以及后来的今日头条、快手等。这些移动化联网时代的新产品在过去几年间借着智能手机的风高速成长。
架构师修炼
2020/11/19
1.4K0
Feed流系统设计
让人欲罢不能的Feed流系统是如何设计的?
差不多十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具代表性的产品就是微博、微信,以及后来的今日头条、快手等。这些移动化联网时代的新产品在过去几年间借着智能手机的风高速成长。
Bug开发工程师
2019/07/12
2.8K0
让人欲罢不能的Feed流系统是如何设计的?
周末小技 | 开发一个Feeds流系统——写扩散模式
点个关注👆跟腾讯工程师学技术 导语 | 本文主要针对Feeds流进行介绍,将从Feeds流的演变入手,带你一步步了解Feeds流,而后学习如何从开发角度入手,对其进行建模,抽象出Feeds流常见的架构,最终搭建高可用、高扩展、高性能的Feeds流应用。 了解Feeds流 在学习如何开发Feeds流应用前,我们需要先了解什么是Feeds流。 一、什么是Feeds流 Feeds流是一个持续更新并展示给用户的信息流。它将用户主动订阅的若干消息源组合在一起形成内容聚合器,帮助用户持续地获取最新的订阅源内容。所以
腾讯云开发者
2022/12/05
1.5K0
周末小技 | 开发一个Feeds流系统——写扩散模式
如何设计一个消息中心
如今的内容型产品,不管提供的是什么类型的内容,在其主功能之外,不可避免的会有另一个十分重要的功能——消息中心。
出其东门
2022/12/05
2.5K1
如何设计一个消息中心
Feed 流系统实战
「CSDN」里有一个页面叫「关注页」,关注页的逻辑十分常见就是将用户关注的创作者发表的文章聚合在一起,按时间倒序排列即可。
猫头虎
2024/04/08
2330
Feed 流系统实战
朋友圈微博feed流,推拉实践
上一篇《feed流拉取,读扩散,究竟是啥?》关于feed流的拉取还是推送,只写了一半“拉取”,今天把另一半“推送”(写扩散)的坑填完。
架构师之路
2018/07/27
5K1
朋友圈微博feed流,推拉实践
Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务(业务场景、实现思路和环境搭建)
在互联网领域,尤其现在的移动互联网时代,Feed流产品是非常常见的,比如我们每天都会用到的朋友圈,微博,就是一种非常典型的Feed流产品,还有图片分享网站Pinterest,花瓣网等又是另一种形式的Feed流产品。除此之外,很多App的都会有一个模块,要么叫动态,要么叫消息广场,这些也是Feed流产品,可以说,Feed流产品是遍布天下所有的App中。
共饮一杯无
2022/12/16
1.2K0
Redis实现朋友圈,微博等Feed流功能,实现Feed流微服务(业务场景、实现思路和环境搭建)
腾讯频道Feed流系统架构设计
面对千万级关系链与实时推送挑战,腾讯频道如何构建高性能Feeds流系统?本文深度解析三层架构设计策略,揭秘读写扩散混合方案与扩散量剪枝优化,破解超大社区场景下的空拉难题,为复杂社交产品架构提供实战经验。
腾讯云开发者
2025/02/26
1890
腾讯频道Feed流系统架构设计
Feed设计与实现
Feed,在社交和信息推荐的App与网站中,基本都会用到的。例如常用的新浪微博,用户登录进入后,展现给我们的就是feed信息流。新浪微博的信息,来自于你关注人所发布的内容。还有微信的朋友圈,今日头条的信息流,好友发布的美拍等,这些都是Feed。玩过知乎的人应该知道,在知乎Feed中,会显示某某关注了某某话题,某某点赞或者赞同了某个回答。广义来讲,这些也算是一种Feed。
gglinux
2019/02/23
1.4K0
高频场景题分析|Feeds 流怎么设计?
掘金里有一个页面叫「关注页」,关注页的逻辑十分常见就是将用户关注的创作者发表的文章聚合在一起,按时间倒序排列即可。
飞天小牛肉
2024/05/06
3940
高频场景题分析|Feeds 流怎么设计?
分布式架构--基本思想汇总
在互联网大行其道的今天,各种分布式系统已经司空见惯。搜索引擎、电商网站、微博、微信、O2O平台。。凡是涉及到大规模用户、高并发访问的,无一不是分布式。 关于分布式系统,并没有一个标准答案,说某某架构一定是最好的。不同的业务形态所面对的挑战不一样,使用的架构设计也不一样,通常都需要具体业务具体分析。 但不管那种业务,不管何种分布式系统,有一些基本的思想还是相通的。本文将对这些基本思想进行一个梳理汇总。 分拆 系统分拆 微信的架构师说过一句话:“大系统小做“。对于一个大的复杂系统,首先想到的就是对其分拆,拆
Java高级架构
2018/07/20
6040
Redis实现feed流
朋友圈,微博,都是 Feed 流产品,还有图片分享网站 Pinterest,花瓣网等又是另一种形式的 Feed 流产品。很多 App 也都会有一个模块,叫动态或消息广场,这些也是 Feed 流产品。
JavaEdge
2021/02/23
1.2K0
Redis实现feed流
微服务设计原则——高性能:存储设计
大多数业务都是读多写少,为了提高系统处理能力,可以采用读写分离的方式将主节点用于写,从节点用于读,如下图所示。
恋喵大鲤鱼
2024/08/19
2090
微服务设计原则——高性能:存储设计
巧用 Redis,实现微博 Feed 流功能!
Feed 流【关注页 Feed 流】的初始化指的是,当用户的 Feed 流还不存在的时候,为该用户创建一个属于他自己的关注页 Feed 流,具体怎么做呢?其实很简单,遍历一遍关注列表,取出所有关注用户的 feed,将 feedId 存放到 redis 的 sortSet 中即可。这里面有几个关键点:
搜云库技术团队
2023/10/21
6230
巧用 Redis,实现微博 Feed 流功能!
【 Redis | 实战篇 扩展 】
分析:实现点赞功能,那么一个用户是不是只能点赞一次,如果能进行多次点赞(这不刷起来了吗)
张哈大
2025/05/31
720
【 Redis | 实战篇 扩展 】
常见分布式应用系统设计图解(二):Feed 流系统
今天记录 Feed 流系统的设计学习笔记,Feed 流常见系统包括 Twitter、微博、Instagram 和抖音等等,它们的特点是,每个用户都是内容创作者,每个用户也都是内容消费者,每个用户看到的内容都是不同的,它取决于用户所关注的用户列表,再结合时间线(有时还包括优先级)将这些用户的最新 feed 聚合,并以流的方式展示出来。
四火
2022/07/19
9950
常见分布式应用系统设计图解(二):Feed 流系统
Feeds 系统简析 ---- 手Q游戏中心游戏圈
游戏圈,是手Q游戏中心在社交化场景的一个探索和实践,将用户在游戏内的战绩、高光等事件作为动态展示在好友的 feeds 流列表中,产品形态上类似微信朋友圈、QQ 空间、推特等。
叫你不戴帽子
2023/03/08
1.6K0
相关推荐
feeds流系统设计概述
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档