什么是 Feed 流

Feed 流是社交和资讯类应用中常见的一种形态, 比如微博知乎的关注页、微信的订阅号和朋友圈等。Feed 流源于 RSS 订阅, 用户将自己感兴趣的网站的 RSS 地址登记到 RSS 阅读器中, 在阅读器里聚合成的列表就是 Feed 流。

Feed 流的本质是 M 个用户订阅了 \(N_i\) 个信息源形成的多对多关系, Feed 流系统需要聚合用户订阅的 \(N_i\) 个 信息源产生的信息单元(Feed), 在按照一定顺序排列后推送给用户。接下来我们以关注页为例来介绍 Feed 流的实现。

Feed 流有两种基本实现模式:

  • 推模式:当发布新的 Feed 后(比如某大V发了条微博),将这条内容插入到所有粉丝的 Feed 流中。
  • 拉模式:当用户上线后遍历他的关注关系,实时拉取他关注的人发布的内容并聚合成 Feed 流。

两种实现方式各有优缺:

  • 推模式的优点在于用户关注人数较多时, 推模型性能较好。但粉丝数较多的大V发布内容时需要插入的 Feed 流过多,开销很大。
  • 拉模式的优点在于粉丝数较多的大V发布内容时系统不需要短时间内执行大量操作。但用户关注人数较多时,构建 Feed 流耗时很长。

推模式的 Feed 流系统在用户发布新的 Feed 后需要执行较多插入操作,通常需要使用 MQ 来异步地进行。

在实际应用中我们通常采取推拉结合的实现方式。

Feed 流的存储

Feed 流系统中需要存储的数据有 3 部分:

  1. 作者发布的 Feed 列表,比如个人发布的所有微博、某个答主回答的所有问题或者某人发布的朋友圈。这些数据需要可靠的持久化存储,通常采用 MySQL 等关系型数据库即可。因为很可能需要按照发布时间排序, 若要使用 NoSQL 最好使用支持有序存储的数据库。
  2. 用户和作者之间的关注关系。同样需要可靠的持久化存储,采用 MySQL 等关系型数据库或者 KV 结构的 NoSQL 数据库均可。
  3. 用户的 Feed 流。Feed 流可以根据 Feed 数据库和关注关系构建,因此可以不做持久化存储。

最轻量的解决方案是使用 Redis 存储 Feed 流。在数据量较大 Redis 内存不够用时,也可以采用一些持久化的存储方案。

有序集合 SortedSet 是非常适合存储 Feed 流的数据结构,一般以 Feed 的 ID 作为 SortedSet 的 member,时间戳或者热度值、推荐值作为 score 进行排序。SortedSet 保证了 Feed 不会重复,且插入过程线程安全,无论是推拉模式实现起来都非常方便。

为了避免 Redis 中缓存的 Feed 流占用过多内存,通常需要给 Feed 流设置 TTL.

Feed 的具体内容存储可以在 MySQL 中,同时在 Redis 中做一层缓存。关注关系可以存储在 MySQL 中,因为有些大V的粉丝数较多所以不推荐做缓存。

持久化存储

一个用户的 Feed 流大小是他所有关注者发布的 Feed 数总和,数据量巨大且增长迅速。在用户量较大的系统中将所有 Feed 流存储在 Redis 中需要消耗巨量的内存。在必要的时候可以利用持久化存储作多级缓存,比如:将当日活跃用户的Feed 流数据存储在 Redis 中, 当月活跃用户的 Feed 流持久化到数据库中,长期未活跃的用户则在他重新登录后使用 MySQL 中存储的关注关系重新构建 Feed 流。

持久化存储 Feed 流的数据库需要有较大的数据容量和吞吐量并且支持排序(Order By 查询)。鉴于这两个原因不建议使用数据容量较小的 MySQL 或者不支持排序的 KV 数据库。

作者推荐使用 Cassandra 来持久化 Feed 流。使用用户的 UID 作为 Partition Key, Feed 时间戳在前 Feed ID 在后, 共同作为 Clustering Key 用于排序和去重。Cassandra 支持 TTL 可以用来自动清除冷数据。Feed 流数据属于只追加不修改,与 Cassandra 使用的 LSM 结构非常契合,可以有效减少 Cassandra 进行 Compaction 的负担。

Feed 流系统优化

在线推 离线拉

一个拥有 10 万粉丝的大V在发布微博时,他的粉丝中可能只有 1 千人在线。因此我们常用的优化策略是:对于在线的粉丝采用推模式,将新的 Feed 直接插入到粉丝的信息流中;对于离线的粉丝采用拉模式,在粉丝登录时遍历他的关注关系重新构建 Feed 流。

在线推的部分需要计算粉丝和在线用户的交集,然后进行插入操作。因为在线用户数和粉丝数都比较大,所以计算交集的过程需要分批进行。比如说每次查询 100 个粉丝,然后去查询这 100 个用户中有多少在线(取交集),直到遍历完粉丝列表。这个过程类似于将两个表做 join, 同样适用小表驱动大表的原则以减少取交集操作的次数, 大多数情况下使用数量较少的粉丝表作为驱动表。(不要问我什么情况下用在线用户表做驱动表)

分页器

由于 Feed 流通常比较大客户端不可能将所有内容拉取到本地,所以一般需要支持分页的查询。若使用常见的 Limit + Offset 分页器时,可能用户在浏览过程中他关注的用户发布了新的 Feed 导致原来的某个 Feed 从第 1 页挤到了第 2 页,客户端在拉取第 2 页时就会再次拉到这个 Feed。于是客户端上显示了两条相同的内容, 非常影响用户体验。

解决重复问题最简单的方法是使用 LastId + Limit 式的分页器。以使用 SortedSet 存储 Feed 流为例,客户端加载下一页时不使用序号而是使用本地最后一个 Feed 的时间戳作为游标,服务端使用 ZRangeByScore 命令获得更早的 Feed 流, 只要时间戳不重复服务端就不会下发重复的 Feed.

一个简单实用的避免重复的方法是:以发布时间作为 score 的整数部分,Feed ID 作为小数部分。这样 Feed ID 不会干扰排序,此外 Feed ID 不会重复所以 score 也不会重复。

深度分页

由于 Feed 流比较大而大多数用户大多数时候只浏览最新的内容,因此通常不需要缓存全部 Feed 流只需要缓存最新的部分即可。由于我们无法阻止用户继续向下浏览未缓存的内容,所以还是得想办法支持深度分页。

我们在实践中采用的解决方案是: 默认缓存最近一个月的数据,当用户快浏览完缓存内容时则采用拉模式构建最近一年的 Feed 流缓存起来。当用户快读完最近一年的内容时,继续构建更旧的 Feed 流直至构建了完整 Feed 流。在追加 Feed 流缓存的同时减少它的 TTL, 以避免过大的 Feed 流长期占据内存。

Feed 流系统杂谈的更多相关文章

  1. 如何打造千万级Feed流系统

    from:https://www.cnblogs.com/taozi32/p/9711413.html 在互联网领域,尤其现在的移动互联网时代,Feed流产品是非常常见的,比如我们每天都会用到的朋友圈 ...

  2. 从小白到架构师(4): Feed 流系统实战

    「从小白到架构师」系列努力以浅显易懂.图文并茂的方式向各位读者朋友介绍 WEB 服务端从单体架构到今天的大型分布式系统.微服务架构的演进历程.读了三篇万字长文之后各位想必已经累了(主要是我写累了), ...

  3. Feed流系统重构-架构篇

    重构,于我而言,很大的快乐在于能够解决问题. 第一次重构是重构一个c#版本的彩票算奖系统.当时的算奖系统在开奖后,算奖经常超时,导致用户经常投诉.接到重构的任务,既兴奋又紧张,花了两天时间,除了吃饭睡 ...

  4. Feed流系统设计-总纲

    https://mp.weixin.qq.com/s/ccxM2thPbzg5vDWgGVJ5vQ 作者:少强 简介 差不多十年前,随着功能机的淘汰和智能机的普及,互联网开始进入移动互联网时代,最具代 ...

  5. feed流拉取,读扩散,究竟是啥?

    from:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961214&idx=1&sn=5e80ad6f2 ...

  6. 微博Feed流

    一.微博核心业务图 二.微博的架构设计图 三.简述 先来看看Feed流中的一些概念: Feed:Feed流中的每一条状态或者消息都是Feed,比如微博中的一条微博就是一个Feed. Feed流:持续更 ...

  7. 数据人看Feed流-架构实践

    背景 Feed流:可以理解为信息流,解决的是信息生产者与信息消费者之间的信息传递问题.我们常见的Feed流场景有:1 手淘,微淘提供给消费者的首页商品信息,用户关注店铺的新消息等2 微信朋友圈,及时获 ...

  8. feed 流数据请求时机的两个思路

    最近 SF 首页 进行了大改版,效果如下: 其他地方都没什么难点,中间的 feed 流思考了不少时间,效果需要类似微博或者知乎 feed 流.之前一直没有做过类似的功能,现总结两个方案. 方案一 方案 ...

  9. 基于Redis的限流系统的设计

    本文讲述基于Redis的限流系统的设计,主要会谈及限流系统中限流策略这个功能的设计:在实现方面,算法使用的是令牌桶算法来,访问Redis使用lua脚本.   1.概念 In computer netw ...

随机推荐

  1. Spring第一课:配置文件及IOC引入(一)

    Spring最核心的特点就是控制反转:(IOC)和面向切面(AOP) 首先作为一个Spring的项目需要导入的四个核心包,一个依赖: 四核心:core.context.beans.expression ...

  2. Spring之AspectJ

    时间:2017-2-4 21:12 --AspectJ简介1.AspectJ是一个基于Java语言的AOP框架.2.Spring2.0以后新增了对AspectJ切点表达式的支持.3.@AspectJ是 ...

  3. 自建纯净谷歌搜索「GitHub 热点速览 v.21.35」

    作者:HelloGitHub-小鱼干 虽然 Google 搜索的结果不如百度搜索结果那般广告丛生,但是对于一心只想找到匹配结果的我们而言,推广的信息条目能免则免.whoogle-search 便是一个 ...

  4. Hutool中那些常用的工具类和方法

    Hutool中那些常用的工具类和方法 Hutool是一个Java工具包,它帮助我们简化每一行代码,避免重复造轮子.如果你有需要用到某些工具方法的时候,不妨在Hutool里面找找,可能就有.本文将对Hu ...

  5. C# - 习题04_分析代码写出结果i1、i2、c.i、str、c.str

    时间:2017-08-23 整理:byzqy 题目:分析如下代码,写出程序输出结果. 文件:Class1.cs using System; namespace Interview3 { class C ...

  6. 【WPF】 OxyPlot图表控件学习

    最近在学习OxyPlot图表控件,一些基本的学习心得,在这里记录一下,方便以后进行查找.   一.引用 OxyPlot控件可以直接在VS的 " Nuget " 里面下载   选择: ...

  7. Kubernetes 组件简介

    关于Kubernetes是什么??? Kubernetes是致力于提供跨主机集群的自动部署.扩展.高可用以及运行应用程序容器的平台. Kubernets集群组成有哪些??? k8s由master和no ...

  8. RT-Thread 4.0 + STM32F407 学习笔记1

    RT Thread 4.0提供了新的BSP框架 新 BSP 框架的主要特性如下: 提供多系列 BSP 模板,大大降低新 BSP 的添加难度: 每个 BSP 都配有齐全的驱动文件,开发者可以方便地使用所 ...

  9. charles 抓包修改app页面数据

    1,首先给手机安装Charles证书,安装官方的来,在无线网配置项目,输入手动代理地址,后开启飞行模式刷新网络, 2,在浏览器输入chls.pro/ssl 下载并安装证书,此时电脑端charles 会 ...

  10. Linux原始套接字抓取底层报文

    1.原始套接字使用场景 我们平常所用到的网络编程都是在应用层收发数据,每个程序只能收到发给自己的数据,即每个程序只能收到来自该程序绑定的端口的数据.收到的数据往往只包括应用层数据,原有的头部信息在传递 ...