☆* o(≧▽≦)o *☆嗨~我是小奥
个人博客:小奥的博客
Github:传送门
面经分享(牛客主页):传送门
文章作者技术和水平有限,如果文中出现错误,希望大家多多指正!
如果觉得内容还不错,欢迎点赞收藏关注哟! ❤️
Feed流是一个目前非常常见的功能,在众多产品中都有展现,比如微博,朋友圈,消息广场,通知,IM等。通过Feed流可以把动态实时的传播给订阅者,是用户获取信息流的一种有效方式。
Feed的本质就是M个用户订阅了N个信息源形成的多对多关系,Feed需要聚合用户订阅的N个信息源产生的信息单元(Feed),并且按照一定的顺序排序后推送给用户。
Feed的常见分类有两种:
Feed流有两种基本实现模式:
两种实现方式各有优缺:
Feed流的初始化是指当用户的Feed流还不存在的时候,为该用户创建一个属于自己的Feed流,其实就是遍历一遍关注列表,取出所有的Feed,将FeedId存放到Redis的SortedSet中,score的值如果是TimeLine类型,就取Feed创建的时间戳,如果是rank类型,就把对应权重设置进去。
Feed的推送就是更新(这里使用推拉结合模式)。更新分为两类情况:
Feed流系统中需要存储的数据有3部分:
最轻量级的解决方式是使用Redis存储Feed流。在数据量比较大Redis内存不够用的时候,也可以采用一些持久化方案。
Redis的SortedSet是非常适合存储Feed流的数据结构,一般以Feed的ID作为Sorted的member,时间戳、热度值、推荐值作为score进行排序,SortedSet保证Feed不会重复,且插入过程线程安全,无论是推拉模式实现起来都比较方便。
为了避免Redis缓存中的Feed流占用过多内存,通常需要给Feed流设置TTL。
一个用户的Feed流大小是他所有关注者发布的Feed流总和,在用户量较大的系统中Feed数据量巨大而且增长迅速,将所有的Feed流存储在Redis中需要消耗巨量的内存。
在必要的时候可以利用持久化存储作多级缓存,比如:将当日活跃用户的Feed流数据存储在Redis中,当月活跃的用户的Feed流持久化到数据库中,长期未活跃的用户则在他重新登录后使用MySQL中存储的关注关系重新构建Feed流。
因为持久化存储的Feed流的数据库需要有较大的数据容量、较高的吞吐量并且需要支持排序。所以不建议使用数据容量较小的MySQL或者不支持排序的KV数据库来存储Feed流数据。
① 在线推 离线拉
一个拥有 10 万粉丝的大V在发布微博时,他的粉丝中可能只有 1 千人在线。因此我们常用的优化策略是:对于在线的粉丝采用推模式,将新的 Feed 直接插入到粉丝的信息流中;对于离线的粉丝采用拉模式,在粉丝登录时遍历他的关注关系重新构建 Feed 流。
在线推的部分需要计算粉丝和在线用户的交集,然后进行插入操作。因为在线用户数和粉丝数都比较大,所以计算交集的过程需要分批进行。比如说每次查询 100 个粉丝,然后去查询这 100 个用户中有多少在线(取交集),直到遍历完粉丝列表。这个过程类似于将两个表做 join,同样适用小表驱动大表的原则以减少取交集操作的次数, 大多数情况下使用数量较少的粉丝表作为驱动表。
② 定时推 离线拉
一个拥有 10 万粉丝的大V在发布微博时,以常驻进程的方式定时推送到粉丝的动态列表。
由于 Feed 流通常比较大,不可能一次性将所有内容拉取到本地,所以一般需要支持分页查询。
若在用户浏览过程中他关注的人发布了新的内容,导致原来在第 1 页最后一位的 Feed A 被挤到了第 2 页首位。在使用 Limit + Offset 分页器拉取第 2 页时就会再次拉到 Feed A。于是客户端上显示了两条相同的内容,这个问题非常影响用户体验。
解决重复问题最简单的方法是使用 LastId + Limit
式的分页器。客户端加载下一页时使用本地最后一个 Feed 的 ID 作为游标,服务端使用 ZRangeByScore 命令获得发布时间比它更早的 Feed 作为下一页。无论浏览过程中 Feed 流内被插入了多少新内容,只要 Feed 的时间戳唯一就不会下发重复的 Feed。
一个简单实用的避免时间戳重复的方法是:以发布时间作为 score 的整数部分,Feed ID 作为小数部分。这样 Feed ID 不会干扰排序,此外 Feed ID 不会重复所以 score 也不会重复。
由于 Feed 流比较大而用户大多数时候只浏览最新的内容,所以通常不需要缓存全部 Feed 流只需要缓存最新的部分即可。但是我们无法阻止用户继续向下浏览未缓存的内容,所以还是得想办法支持深度分页。
我们在实践中采用的解决方案是: 默认缓存最近一个月的数据,当用户快浏览完缓存内容时则异步地采用拉模式构建最近一年的 Feed 流缓存起来。当用户快读完最近一年的内容时继续缓存更旧的 Feed 流,直至缓存了完整 Feed 流。在追加 Feed 流缓存的同时减少它的 TTL, 以避免过大的 Feed 流长期占据内存。
如何打造千万级Feed流系统-阿里云开发者社区 (aliyun.com)