Feed流系统设计-总纲

简介
差很少十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具表明性的产品就是微博、微信,以及后来的今日头条、快手等。这些移动化联网时代的新产品在过去几年间借着智能手机的风高速成长。算法

这些产品都是Feed流类型产品,因为Feed流通常是按照时间“从上往下流动”,很是适合在移动设备端浏览,最终这一类应用就脱颖而出,迅速抢占了上一代产品的市场空间。数据库

Feed流是Feed + 流,Feed的本意是饲料,Feed流的本意就是有人一直在往一个地方投递新鲜的饲料,若是须要饲料,只须要盯着投递点就能够了,这样就能源源不断获取到新鲜的饲料。 在信息学里面,Feed实际上是一个信息单元,好比一条朋友圈状态、一条微博、一条咨询或一条短视频等,因此Feed流就是不停更新的信息单元,只要关注某些发布者就能获取到源源不断的新鲜信息,咱们的用户也就能够在移动设备上逐条去浏览这些信息单元。缓存

当前最流行的Feed流产品有微博、微信朋友圈、头条的资讯推荐、快手抖音的视频推荐等,还有一些变种,好比私信、通知等,这些系统都是Feed流系统,接下来咱们会介绍如何设计一个Feed流系统架构。微信

Feed流系统特色
Feed流本质上是一个数据流,是将 “N个发布者的信息单元” 经过 “关注关系” 传送给 “M个接收者”。
图片描述session

Feed流系统是一个数据流系统,因此咱们核心要看数据。从数据层面看,数据分为三类,分别是:架构

发布者的数据:发布者产生数据,而后数据须要按照发布者组织,须要根据发布者查到全部数据,好比微博的我的页面、朋友圈的我的相册等。
关注关系:系统中个体间的关系,微博中是关注,是单向流,朋友圈是好友,是双向流。无论是单向仍是双向,当发布者发布一条信息时,该条信息的流动永远是单向的。
接收者的数据:从不一样发布者那里获取到的数据,而后经过某种顺序(通常为时间)组织在一块儿,好比微博的首页、朋友圈首页等。这些数据具备时间热度属性,越新的数据越有价值,越新的数据就要排在最前面。
针对这三类数据,咱们能够有以下定义:框架

存储库:存储发布者的数据,永久保存。
关注表:用户关系表,永久保存。
同步库:存储接收者的时间热度数据,只须要保留最近一段时间的数据便可。
设计Feed流系统时最核心的是肯定清楚产品层面的定义,须要考虑的因素包括:运维

产品用户规模:用户规模在十万、千万、十亿级时,设计难度和侧重点会不一样。
关注关系(单向、双写):若是是双向,那么就不会有大V,不然会有大V存在。
上述是选择数据存储系统最核心的几个考虑点,除此以外,还有一些须要考虑的:
如何实现Meta和Feed内容搜索?分布式

虽然Feed流系统自己能够不须要搜索,可是一个Feed流产品必需要有搜索,不然信息发现难度会加大,用户留存率会大幅降低。
Feed流的顺序是时间仍是其余分数,好比我的的喜爱程度?优化

双向关系时因为关系很紧密,必定是按时间排序,就算一个关系很紧密的人发了一条空消息或者低价值消息,那咱们也会须要关注了解的。
单向关系时,那么可能就会存在大V,大V的粉丝数量理论极限就是整个系统的用户数,有一些产品会让全部用户都默认关注产品负责人,这种产品中,该负责人就是最大的大V,粉丝数就是用户规模。
接下来,咱们看看整个Feed流系统如何设计。
Feed流系统设计
上一节,咱们提早思考了Feed流系统的几个关键点,接下来,在这一节,咱们自顶向下来设计一个Feed流系统。

  1. 产品定义

第一步,咱们首先须要定义产品,咱们要作的产品是哪种类型,常见的类型有:

微博类
朋友圈类
抖音类
私信类
接着,再详细看一下这几类产品的异同:

类型 关注关系 是否有大V 时效性 排序
微博类 单向 有 秒~分 时间
抖音类 单向/无 有 秒~分 推荐
朋友圈类 双向 无 秒 时间
私信类 双向 无 秒 时间
上述对比中,只对比各种产品最核心、或者最根本特色,其余次要的不考虑。好比微博中互相关注后就是双向关注了,可是这个不是微博的立命之本,只是补充,没法撼动根本。

从上面表格能够看出来,主要分为两种区分:

关注关系是单向仍是双向:

若是是单向,那么可能就会存在大V效应,同时时效性能够低一些,好比到分钟级别;
若是是双向,那就是好友,好友的数量有限,那么就不会有大V,由于每一个人的精力有限,他不可能主动加几千万的好友,这时候由于关系更精密,时效性要求会更高,须要都秒级别。
排序是时间仍是推荐:

用户对feed流最容易接受的就是时间,目前大部分都是时间。
可是有一些场景,是从全网数据里面根据用户的喜爱给用户推荐和用户喜爱度最匹配的内容,这个时候就须要用推荐了,这种状况通常也会省略掉关注了,相对于关注了全网全部用户,好比抖音、头条等。
肯定了产品类型后,还须要继续肯定的是系统设计目标:须要支持的最大用户数是多少?十万、百万、千万仍是亿?
用户数不多的时候,就比较简单,这里咱们主要考虑 亿级用户 的状况,由于若是系统能支持亿级,那么其余量级也能支持。为了支持亿级规模的用户,主要子系统选型时须要考虑水平扩展能力以及一些子系统的可用性和可靠性了,由于系统大了后,任何一个子系统的不稳定都很容易波及整个系统。

  1. 存储

咱们先来看看最重要的存储,无论是哪一种同步模式,在存储上都是同样的,咱们定义用户消息的存储为存储库。存储库主要知足三个需求:

可靠存储用户发送的消息,不能丢失。不然就找不到本身曾经发布到朋友圈状态了。
读取某我的发布过的全部消息,好比我的主页等。
数据永久保存。
因此,存储库最重要的特征就是两点:

数据可靠、不丢失。
因为数据要永久保存,数据会一直增加,因此要易于水平扩展。
综上,能够选为存储库的系统大概有两类:

特色 分布式NoSQL 关系型数据库(分库分表)
可靠性 极高 高
水平扩展能力 线性 须要改造
水平扩展速度 毫秒 无
常见系统 Tablestore、Bigtable MySQL、PostgreSQL
对于可靠性,分布式NoSQL的可靠性要高于关系型数据库,这个可能有违不少人的认知。主要是关系型数据库发展很长时间了,且很成熟了,数据放在上面你们放心,而分布式NoSQL数据库发展晚,使用的并很少,不太信任。可是,分布式NoSQL须要存储的数据量更多,对数据可靠性的要求也加严格,因此通常都是存储三份,可靠性会更高。目前在一些云厂商中的关系型数据库由于采用了和分布式NoSQL相似的方式,因此可靠性也获得了大幅提升。
水平扩展能力:对于分布式NoSQL数据库,数据自然是分布在多台机器上,当一台机器上的数据量增大后,能够经过自动分裂两部分,而后将其中一半的数据迁移到另外一台机器上去,这样就作到了线性扩展。而关系型数据库须要在扩容时再次分库分表。
因此,结论是:

若是是自建系统,且不具有分布式NoSQL数据库运维能力,且数据规模不大,那么可使用MySQL,这样能够撑一段时间。
若是是基于云服务,那么就用分布式NoSQL,好比Tablestore或Bigtable。
若是数据规模很大,那么也要用分布式NoSQL,不然就是走上一条不归路。
若是使用Tablestore,那么存储库表设计结构以下:

主键列 第一列主键 第二列主键 属性列 属性列
列名 user_id message_id content other
解释 消息发送者用户ID 消息顺序ID,可使用timestamp。 内容 其余内容
到此,咱们肯定了存储库的选型,那么系统架构的轮廓有了:

图片描述

  1. 同步

系统规模和产品类型,以及存储系统肯定后,咱们能够肯定同步方式,常见的方式有三种:

推模式(也叫写扩散):和名字同样,就是一种推的方式,发送者发送了一个消息后,当即将这个消息推送给接收者,可是接收者此时不必定在线,那么就须要有一个地方存储这个数据,这个存储的地方咱们称为:同步库。推模式也叫写扩散的缘由是,一个消息须要发送个多个粉丝,那么这条消息就会复制多份,写放大,因此也叫写扩散。这种模式下,对同步库的要求就是写入能力极强和稳定。读取的时候由于消息已经发到接收者的收件箱了,只须要读一次本身的收件箱便可,读请求的量极小,因此对读的QPS需求不大。概括下,推模式中对同步库的要求只有一个:写入能力强。
拉模式(也叫读扩散):这种是一种拉的方式,发送者发送了一条消息后,这条消息不会当即推送给粉丝,而是写入本身的发件箱,当粉丝上线后再去本身关注者的发件箱里面去读取,一条消息的写入只有一次,可是读取最多会和粉丝数同样,读会放大,因此也叫读扩散。拉模式的读写比例恰好和写扩散相反,那么对系统的要求是:读取能力强。另外这里还有一个误区,不少人在最开始设计feed流系统时,首先想到的是拉模式,由于这种和用户的使用体感是同样的,可是在系统设计上这种方式有很多痛点,最大的是每一个粉丝须要记录本身上次读到了关注者的哪条消息,若是有1000个关注者,那么这我的须要记录1000个位置信息,这个量和关注量成正比的,远比用户数要大的多,这里要特别注意,虽然在产品前期数据量少的时候这种方式能够应付,可是量大了后就会事倍功半,得不偿失,切记切记。
推拉结合模式:推模式在单向关系中,由于存在大V,那么一条消息可能会扩散几百万次,可是这些用户中可能有一半可能是僵尸,永远不会上线,那么就存在资源浪费。而拉模式下,在系统架构上会很复杂,同时须要记录的位置信息是天量,很差解决,尤为是用户量多了后会成为第一个故障点。基于此,因此有了推拉结合模式,大部分用户的消息都是写扩散,只有大V是读扩散,这样既控制了资源浪费,又减小了系统设计复杂度。可是总体设计复杂度仍是要比推模式复杂。
用图表对比:

类型 推模式 拉模式 推拉结合模式
写放大 高 无 中
读放大 无 高 中
用户读取延时 毫秒 秒 秒
读写比例 1:99 99:1 ~50:50
系统要求 写能力强 读能力强 读写都适中
常见系统 Tablestore、Bigtable等LSM架构的分布式NoSQL Redis、memcache等缓存系统或搜索系统(推荐排序场景) 二者结合
架构复杂度 简单 复杂 更复杂
介绍完同步模式中全部场景和模式后,咱们概括下:

若是产品中是双向关系,那么就采用推模式。
若是产品中是单向关系,且用户数少于1000万,那么也采用推模式,足够了。
若是产品是单向关系,单用户数大于1000万,那么采用推拉结合模式,这时候能够从推模式演进过来,不须要额外从新推翻重作。
永远不要只用拉模式。
若是是一个初创企业,先用推模式,快速把系统设计出来,而后让产品去验证、迭代,等客户数大幅上涨到1000万后,再考虑升级为推拉集合模式。
若是是按推荐排序,那么是另外的考虑了,架构会彻底不同,这个后面专门文章介绍。
若是选择了Tablestore,那么同步库表设计结构以下:

主键列 第一列主键 第二列主键 属性列 属性列 属性列
列名 user_id sequence_id sender_id message_id other
解释 消息接收者用户ID 消息顺序ID,可使用timestamp + send_user_id,也能够直接使用Tablestore的自增列。 发送者的用户ID store_table中的message_id列的值,也就是消息ID。经过sender_id和message_id能够到store_table中查询到消息内容 其余内容,同步库中不须要包括消息内容。
肯定了同步库的架构以下:
图片描述

  1. 元数据

前面介绍了同步和存储后,整个Feed流系统的基础功能完成了,可是对于一个完整Feed流产品而言,还缺元数据部分,接下来,咱们看元数据如何处理:

Feed流系统中的元数据主要包括:

用户详情和列表。
关注或好友关系。
推送session池。
咱们接下来逐一来看。

4.1 用户详情和列表

主要是用户的详情,包括用户的各类自定义属性和系统附加的属性,这部分的要求只须要根据用户ID查询到就能够了。

能够采用的分布式NoSQL系统或者关系型数据库均可以。

若是使用NoSQL数据库Tablestore,那么用户详情表设计结构以下:

主键顺序 第一列主键 属性列-1 属性列-2 ......
字段名 user_id nick_name gender other
备注 主键列,用于惟一肯定一个用户 用户昵称,用户自定义属性 用户性别,用户自定义属性 其余属性,包括用户自定义属性列和系统附加属性列。Tablestore是FreeSchema类型的,能够随时在任何一行增长新列而不影响原有数据。
4.2 关注或好友关系

这部分是存储关系,查询的时候须要支持查询关注列表或者粉丝列表,或者直接好友列表,这里就须要根据多个属性列查询须要索引能力,这里,存储系统也能够采用两类,关系型、分布式NoSQL数据库。

若是已经有了关系型数据库了,且数据量较少,则选择关系型数据库,好比MySQL等。
若是数据量比较大,这个时候就有两种选择:

须要分布式事务,能够采用支持分布式事务的系统,好比分布式关系型数据库。
使用具备索引的系统,好比云上的Tablestore,更简单,吞吐更高,扩容能力也一并解决了。
若是使用Tablestore,那么关注关系表设计结构以下:

Table:user_relation_table

主键顺序 第一列主键 第一列主键 属性列 属性列
Table字段名 user_id follow_user_id timestamp other
备注 用户ID 粉丝用户ID 关注时间 其余属性列
多元索引的索引结构:

Table字段名 user_id follow_user_id timestamp
是否Index 是 是 是
是否enableSortAndAgg 是 是 是
是否store 是 是 是
查询的时候:

若是须要查询某我的的粉丝列表:使用TermQuery查询固定user_id,且按照timestamp排序。
若是须要查询某我的的关注列表:使用TermQuery查询固定follow_user_id,且按照timestamp排序。
当前数据写入Table后,须要5~10秒钟延迟后会在多元索引中查询到,将来会优化到2秒之内。
除了使用多元索引外,还可使用GlobalIndex。

4.3 推送session池

思考一个问题,发送者将消息发送后,接收者如何知道本身有新消息来了?客户端周期性去刷新?若是是这样子,那么系统的读请求压力会随着客户端增加而增加,这时候就会有一个风险,好比平时的设备在线率是20%~30%,忽然某天平台爆发了一个热点消息,大量休眠设备登录,这个时候就会出现“查询风暴”,一会儿就把系统打垮了,全部的用户都不能用了。

解决这个问题的一个思路是,在服务端维护一个推送session池,这个里面记录哪些用户在线,而后当用户A发送了一条消息给用户B后,服务端在写入存储库和同步库后,再通知一下session池中的用户B的session,告诉他:你有新消息了。而后session-B再去读消息,而后有消息后将消息推送给客户端。或者有消息后给客户端推送一下有消息了,客户端再去拉。

这个session池使用在同步中,可是本质仍是一个元数据,通常只须要存在于内存中便可,可是考虑到failover状况,那就须要持久化,这部分数据因为只须要指定单Key查询,用分布式NoSQL或关系型数据库均可以,通常复用当前的系统便可。

若是使用Tablestore,那么session表设计结构以下:

主键列顺序 第一列主键 第二列主键 属性列
列名 user_id device_id last_sequence_id
备注 接收者用户ID 设备ID,同一个用户可能会有多个设备,不一样设备的读取位置可能不一致,因此这里须要一个设备ID。若是不须要支持多终端,则这一列能够省略。 该接收者已经推送给客户端的最新的顺序ID

  1. 评论

除了私信类型外,其余的feed流类型中,都有评论功能,评论的属性和存储库差很少,可是多了一层关系:被评论的消息,因此只要将评论按照被被评论消息分组组织便可,而后查询时也是一个范围查询就行。这种查询方式很简单,用不到关系型数据库中复杂的事务、join等功能,很适合用分布式NoSQL数据库来存储。

因此,通常的选择方式就是:

若是系统中已经有了分布式NoSQL数据库,好比Tablestore、Bigtable等,那么直接用这些便可。
若是没有上述系统,那么若是有MySQL等关系型数据库,那就选关系型数据库便可。
若是选择了Tablestore,那么“评论表”设计结构以下:
主键列顺序 第一列主键 第二列主键 属性列 属性列 属性列
字段名 message_id comment_id comment_content reply_to other
备注 微博ID或朋友圈ID等消息的ID 这一条评论的ID 评论内容 回复给哪一个用户 其余
若是须要搜索评论内容,那么对这张表创建多元索引便可。

最近几年,“赞”或“like”功能很流行,赞功能的实现和评论相似,只是比评论少了一个内容,因此选择方式和评论同样。

若是选择了Tablestore,那么“赞表”设计结构同评论表,这里就再也不赘述了。

系统架构中加了元数据系统后的架构以下:
图片描述

  1. 搜索

到此,咱们已经介绍完了Feed流系统的主题架构,Feed流系统算是完成了。可是Feed流产品上还未结束,对于全部的feed流产品都须要有搜索能力,好比下面场景:

微博中的搜索用户。
搜索微博内容。
微信中搜索好友等。
这些内容搜索只须要字符匹配到便可,不须要很是复杂的相关性算法,因此只须要有能支持分词的检索功能便可,因此通常有两种作法:

使用搜索引擎,将存储库的内容和用户信息表内容推送给搜索系统,搜索的时候直接访问搜索系统。
使用具有全文检索能力的数据库,好比最新版的MySQL、MongoDB或者Tablestore。

因此,选择的原则以下:

若是存储库使用了MySQL或者Tablestore,那么直接选择这两个系统就能够了。
若是整个系统都没使用MySQL、Tablestore,且已经使用了搜索系统,那么能够直接复用搜索系统,其余场景都不该该再额外加一个搜索系统进来,徒添复杂度。
若是使用Tablestore,那么只须要在相应表上创建多元索引便可:

若是须要对用户名支持搜索,那么须要对user_table创建多元索引,其中的nick_name须要是Text类型,且单字分词。
若是须要对Feed流内容支持搜索,那么须要对存储库表:store_table创建多元索引,这样就能直接对Feed流内容进行各类复杂查询了,包括多条件筛选、全文检索等。
系统架构中加了搜索功能后的架构以下:
图片描述

  1. 排序

目前的Feed流系统中的排序方式有两种,一种是时间,一种是分数。

咱们经常使用的微博、朋友圈、私信这些都是时间线类型的,由于这些产品定义中,须要咱们主动关注某些人后才会看到这些人发表的内容,这个时候,最重要的是实时性,而不是发布质量,就算关注人发布了一条垃圾信息,咱们也会被动看到。这种类型的产品适用于按照时间线排序。这一篇咱们介绍的架构都是基于时间类型的。

另一种是不须要关注任何人,咱们能看到的都是系统但愿咱们看到的,系统在后台会分析咱们的每一个人的爱好,而后给每一个人推送差别化的、各自喜欢的内容,这一种的架构和基于时间的彻底不同,咱们在后续的推荐类型中专门介绍。

  1. 删除Feed内容

在Feed流应用中有一个问题,就是若是用户删除了以前发表的内容,系统该如何处理?由于系统里面有写扩散,那么删除的时候是否是也要写扩散一遍?这样的话,删除就不及时了,很难应对法律法规要求的快速删除。

针对这个问题,咱们在以前设计的时候,同步表中只有消息ID,没有消息内容,在用户读取的时候须要到存储库中去读消息内容,那么咱们能够直接删除存储库中的这一条消息,这样用户读取的时候使用消息ID是读不到数据的,也就至关于删除的内容,并且删除速度会很快。除了直接删除外,另一种办法是逻辑删除,对于删除的feed内容,只作标记,当查询到带有标记的数据时就认为删除了。

  1. 更新Feed内容

更新和删除Feed处理逻辑同样,若是使用了支持多版本的存储系统,好比Tablestore,那么也能够支持编辑版本,和如今的微博同样。

  1. 总结

上面介绍了不一样子功能的特色和系统要求,能知足需求的系统主要有两类,一类是阿里云的Tablestore单系统,一类是开源组件组成的组合系统。

开源组件组成的组合系统:包括MySQL、Redis、HBase等,这些系统单个都不能解决Feed流系统中遇到的问题,须要组合在一块儿,各司其职才能完成一个Feed流系统,适用于热衷开源系统,人多且喜欢运维操做的团队。
Tablestore单系统:只使用Tablestore单个系统就能解决上述的全部问题,这时候确定有人要问?你是否是在吹牛? 这里不是吹牛,Tablestore在三年前就已经开始重视Feed流类型业务,以前也发表过多篇文章介绍,功能上也在专门为Feed流系统特别定制设计,因此到今天,只使用Tablestore一款产品,是能够知足上述需求的。选择Tablestore作Feed流系统的用户具备如下一些特征:

产品设计目标规模大,千万级或亿级。
不喜欢运维,喜欢专一于开发。
高效率团队,但愿尽快将产品实现落地。
但愿一劳永逸,将来系统随着用户规模增加能够自动扩容。
但愿能按量付费,用户少的时候费用低,等用户增加起来后费用在跟随用户数增加。
若是具备上述四个特征的任何一个,那么都是适合于用Tablestore。
架构实践
上面咱们介绍了Feed流系统的设计理论,具体到不一样的类型中,会有不一样的侧重点,下面会逐一介绍。

朋友圈
朋友圈是一种典型的Feed流系统,关系是双写关系,关系有上限,排序按照时间,若是有我的持续产生垃圾内容,那就只能屏蔽掉TA,这一种类型就是典型的写扩散模型。

咱们接下来会在文章《朋友圈类系统架构设计》中详细介绍朋友圈类型Feed流系统的设计。

微博
微博也是一种很是典型的Feed流系统,但不一样于朋友圈,关系是单向的,那么也就会产生大V,这个时候就须要读写扩散模式,用读扩散解决大V问题。同时,微博也是主动关注类型的产品,因此排序也只能是时间,若是按照推荐排序,那么效果就会比较差。

接下里会在文章《微博类系统架构设计》中详细介绍微博类型Feed流系统的设计。

头条
头条是最近几年快速崛起的一款应用,在原有微博的Feed流系统上产生了进化,用户不须要主动关注其余人,只要初始浏览一些内容后,系统就会自动判断出你的喜爱,而后后面再根据你的喜爱给你推荐你可能会喜爱的内容,训练时间长了后,推送的内容都会是你最喜欢看的。

后面,咱们会在文章《头条类系统架构设计》中详细介绍头条类型Feed流系统的设计。

私信
私信也算是一种简单的Feed流系统,或者也能够认为是一种变相的IM,都是单对单的,没有群。咱们后面也会有一篇文章《私信类系统架构设计》中作详细介绍。

总结
上面咱们介绍了Feed流系统的总体框架,主要是产品定义、同步、存储、元数据、评论、赞、排序和搜索等内容,因为篇幅有限,每一章节都介绍的比较简单。读者若是对某一部分看完后仍然有疑问,能够继续再文后提问,我会继续去完善这篇文章,但愿将来读者看完这篇文章后,就能够轻轻松松设计出一个亿级规模的Feed流系统。

另外,咱们也欢迎有兴趣的读者一块儿来完成这个系列,帮忙实现朋友圈、微博、头条或者私信类型的文章,有任何问题都欢迎来讨论。

延伸
Feed类型的系统架构和IM(即时聊天)类型的系统架构很是相似,自从Tablestore从2016年开始优化此类系统,咱们研发了Feed流和IM的通用底层框架-Timeline,目前已经演进到了V2版本,一体化支持存储、同步和搜索功能,咱们已经有文章作了介绍:

《亿级消息系统的核心存储:Tablestore发布Timeline 2.0模型》《现代IM系统中的消息系统架构 - 架构篇》《现代IM系统中的消息系统架构 - 模型篇》《Tablestore权威指南》

相关文章
相关标签/搜索