超牛逼的 Feed 流系统设计!
发布者的数据:发布者产生数据,然后数据需要按照发布者组织,需要根据发布者查到所有数据,比如微博的个人页面、朋友圈的个人相册等。
关注关系:系统中个体间的关系,微博中是关注,是单向流,朋友圈是好友,是双向流。不管是单向还是双向,当发布者发布一条信息时,该条信息的流动永远是单向的。
接收者的数据:从不同发布者那里获取到的数据,然后通过某种顺序(一般为时间)组织在一起,比如微博的首页、朋友圈首页等。这些数据具有时间热度属性,越新的数据越有价值,越新的数据就要排在最前面。
存储库:存储发布者的数据,永久保存。
关注表:用户关系表,永久保存。
同步库:存储接收者的时间热度数据,只需要保留最近一段时间的数据即可。
设计Feed流系统时最核心的是确定清楚产品层面的定义,需要考虑的因素包括:
产品用户规模:用户规模在十万、千万、十亿级时,设计难度和侧重点会不同。
关注关系(单向、双向):如果是双向,那么就不会有大V,否则会有大V存在。
上述是选择数据存储系统最核心的几个考虑点,除此之外,还有一些需要考虑的:如何实现Meta和Feed内容搜索?
虽然Feed流系统本身可以不需要搜索,但是一个Feed流产品必须要有搜索,否则信息发现难度会加大,用户留存率会大幅下降。
Feed流的顺序是时间还是其他分数,比如个人的喜好程度?
双向关系时由于关系很紧密,一定是按时间排序,就算一个关系很紧密的人发了一条空消息或者低价值消息,那我们也会需要关注了解的。
单向关系时,那么可能就会存在大V,大V的粉丝数量理论极限就是整个系统的用户数,有一些产品会让所有用户都默认关注产品负责人,这种产品中,该负责人就是最大的大V,粉丝数就是用户规模。
接下来,我们看看整个Feed流系统如何设计。
微博类
朋友圈类
抖音类
私信类
关注关系是单向还是双向:
如果是单向,那么可能就会存在大V效应,同时时效性可以低一些,比如到分钟级别;
如果是双向,那就是好友,好友的数量有限,那么就不会有大V,因为每个人的精力有限,他不可能主动加几千万的好友,这时候因为关系更精密,时效性要求会更高,需要到秒级别。
排序是时间还是推荐:
用户对feed流最容易接受的就是时间,目前大部分都是时间。
但是有一些场景,是从全网数据里面根据用户的喜好给用户推荐和用户喜好度最匹配的内容,这个时候就需要用推荐了,这种情况一般也会省略掉关注了,相对于关注了全网所有用户,比如抖音、头条等。
确定了产品类型后,还需要继续确定的是系统设计目标:需要支持的最大用户数是多少?十万、百万、千万还是亿?
可靠存储用户发送的消息,不能丢失。否则就找不到自己曾经发布到朋友圈状态了。
读取某个人发布过的所有消息,比如个人主页等。
数据永久保存。
数据可靠、不丢失。
由于数据要永久保存,数据会一直增长,所以要易于水平扩展。
对于可靠性,分布式NoSQL的可靠性要高于关系型数据库,这个可能有违很多人的认知。主要是关系型数据库发展很长时间了,且很成熟了,数据放在上面大家放心,而分布式NoSQL数据库发展晚,使用的并不多,不太信任。但是,分布式NoSQL需要存储的数据量更多,对数据可靠性的要求也加严格,所以一般都是存储三份,可靠性会更高。目前在一些云厂商中的关系型数据库因为采用了和分布式NoSQL类似的方式,所以可靠性也得到了大幅提高。
水平扩展能力:对于分布式NoSQL数据库,数据天然是分布在多台机器上,当一台机器上的数据量增大后,可以通过自动分裂两部分,然后将其中一半的数据迁移到另一台机器上去,这样就做到了线性扩展。而关系型数据库需要在扩容时再次分库分表。
如果是自建系统,且不具备分布式NoSQL数据库运维能力,且数据规模不大,那么可以使用MySQL,这样可以撑一段时间。
如果是基于云服务,那么就用分布式NoSQL,比如Tablestore或Bigtable。
如果数据规模很大,那么也要用分布式NoSQL,否则就是走上一条不归路。
推模式(也叫写扩散):和名字一样,就是一种推的方式,发送者发送了一个消息后,立即将这个消息推送给接收者,但是接收者此时不一定在线,那么就需要有一个地方存储这个数据,这个存储的地方我们称为:同步库。推模式也叫写扩散的原因是,一个消息需要发送个多个粉丝,那么这条消息就会复制多份,写放大,所以也叫写扩散。这种模式下,对同步库的要求就是写入能力极强和稳定。读取的时候因为消息已经发到接收者的收件箱了,只需要读一次自己的收件箱即可,读请求的量极小,所以对读的QPS需求不大。归纳下,推模式中对同步库的要求只有一个:写入能力强。
拉模式(也叫读扩散):这种是一种拉的方式,发送者发送了一条消息后,这条消息不会立即推送给粉丝,而是写入自己的发件箱,当粉丝上线后再去自己关注者的发件箱里面去读取,一条消息的写入只有一次,但是读取最多会和粉丝数一样,读会放大,所以也叫读扩散。拉模式的读写比例刚好和写扩散相反,那么对系统的要求是:读取能力强。另外这里还有一个误区,很多人在最开始设计feed流系统时,首先想到的是拉模式,因为这种和用户的使用体感是一样的,但是在系统设计上这种方式有不少痛点,最大的是每个粉丝需要记录自己上次读到了关注者的哪条消息,如果有1000个关注者,那么这个人需要记录1000个位置信息,这个量和关注量成正比的,远比用户数要大的多,这里要特别注意,虽然在产品前期数据量少的时候这种方式可以应付,但是量大了后就会事倍功半,得不偿失,切记切记。
推拉结合模式:推模式在单向关系中,因为存在大V,那么一条消息可能会扩散几百万次,但是这些用户中可能有一半多是僵尸,永远不会上线,那么就存在资源浪费。而拉模式下,在系统架构上会很复杂,同时需要记录的位置信息是天量,不好解决,尤其是用户量多了后会成为第一个故障点。基于此,所以有了推拉结合模式,大部分用户的消息都是写扩散,只有大V是读扩散,这样既控制了资源浪费,又减少了系统设计复杂度。但是整体设计复杂度还是要比推模式复杂。关注微信订阅号码匠笔记,回复架构获取一些列的架构知识。
如果产品中是双向关系,那么就采用推模式。
如果产品中是单向关系,且用户数少于1000万,那么也采用推模式,足够了。
如果产品是单向关系,单用户数大于1000万,那么采用推拉结合模式,这时候可以从推模式演进过来,不需要额外重新推翻重做。
永远不要只用拉模式。
如果是一个初创企业,先用推模式,快速把系统设计出来,然后让产品去验证、迭代,等客户数大幅上涨到1000万后,再考虑升级为推拉集合模式。
如果是按推荐排序,那么是另外的考虑了,架构会完全不一样,这个后面专门文章介绍。
用户详情和列表。
关注或好友关系。
推送session池。
如果已经有了关系型数据库了,且数据量较少,则选择关系型数据库,比如MySQL等。
如果数据量比较大,这个时候就有两种选择:
使用具有索引的系统,比如云上的Tablestore,更简单,吞吐更高,扩容能力也一并解决了。
需要分布式事务,可以采用支持分布式事务的系统,比如分布式关系型数据库。
如果需要查询某个人的粉丝列表:使用TermQuery查询固定user_id,且按照timestamp排序。
如果需要查询某个人的关注列表:使用TermQuery查询固定follow_user_id,且按照timestamp排序。
当前数据写入Table后,需要5~10秒钟延迟后会在多元索引中查询到,未来会优化到2秒以内。
如果系统中已经有了分布式NoSQL数据库,比如Tablestore、Bigtable等,那么直接用这些即可。
如果没有上述系统,那么如果有MySQL等关系型数据库,那就选关系型数据库即可。
如果选择了Tablestore,那么“评论表”设计结构如下:
微博中的搜索用户。
搜索微博内容。
微信中搜索好友等。
如果存储库使用了MySQL或者Tablestore,那么直接选择这两个系统就可以了。
如果整个系统都没使用MySQL、Tablestore,且已经使用了搜索系统,那么可以直接复用搜索系统,其他场景都不应该再额外加一个搜索系统进来,徒添复杂度。
如果需要对用户名支持搜索,那么需要对user_table建立多元索引,其中的nick_name需要是Text类型,且单字分词。
如果需要对Feed流内容支持搜索,那么需要对存储库表:store_table建立多元索引,这样就能直接对Feed流内容进行各种复杂查询了,包括多条件筛选、全文检索等。
开源组件组成的组合系统:包括MySQL、Redis、HBase等,这些系统单个都不能解决Feed流系统中遇到的问题,需要组合在一起,各司其职才能完成一个Feed流系统,适用于热衷开源系统,人多且喜欢运维操作的团队。
Tablestore单系统:只使用Tablestore单个系统就能解决上述的所有问题,这时候肯定有人要问?你是不是在吹牛?这里不是吹牛,Tablestore在三年前就已经开始重视Feed流类型业务,之前也发表过多篇文章介绍,功能上也在专门为Feed流系统特别定制设计,所以到今天,只使用Tablestore一款产品,是可以满足上述需求的。选择Tablestore做Feed流系统的用户具有以下一些特征:
产品设计目标规模大,千万级或亿级。
不喜欢运维,喜欢专注于开发。
高效率团队,希望尽快将产品实现落地。
希望一劳永逸,未来系统随着用户规模增长可以自动扩容。
希望能按量付费,用户少的时候费用低,等用户增长起来后费用在跟随用户数增长。
如果具有上述四个特征的任何一个,那么都是适合于用Tablestore。