高并发系统设计-Feed流系统设计

news2024/10/6 16:28:35

有两种实现方式:push和pull实现,首先讨论push模式

概念

我们在讲如何设计Feed流系统之前,先来看一下Feed流中的一些概念:

Feed:Feed流中的每一条状态或者消息都是Feed,比如朋友圈中的一个状态就是一个Feed,微博中的一条微博就是一个Feed。

Feed流:持续更新并呈现给用户内容的信息流。每个人的朋友圈,微博关注页等等都是一个Feed流。

Timeline:Timeline其实是一种Feed流的类型,微博,朋友圈都是Timeline类型的Feed流,但是由于Timeline类型出现最早,使用最广泛,最为人熟知,有时候也用Timeline来表示Feed流。

关注页Timeline:展示其他人Feed消息的页面,比如朋友圈,微博的首页等。

个人页Timeline:展示自己发送过的Feed消息的页面,比如微信中的相册,微博的个人页等。

push模式

push模式的特点是:用户每次新增一条贴子,将此贴子推到他的follower所在DB分片上,将此用户推到他的follower所在DB分片上,follower在每次浏览timeline时,直接查询自己分片所存储的数据。

所以这里就需要一个timeline表与post表做对应。

timeline字段包括:id,userId,postId,postTime

timeline表的约束是userId+postId,同一个用户的timeline下相同的一条帖子只能出现一次。

用户根据时间浏览自己timeline下一定时间范围内的帖子,落在userId所属的DB分片上:

select postId from timeline where userId='xx' and postTime between ? and ?;
select * from post where postId in (...);

用户关注了新用户:在新用户所在分片将postId获取,并插入到用户的timeline表中。

用去取关了新用户:

delete from timeline where userId = 'xx' and postId like 'B%';

但是这种基于DB的push方式面临一个困难场景:A用户关注的B用户的发布/删除了自己的帖子时,除了删除B本身的post表之外,需要插入/删除E的timeline表。假设B是一个热点用户,他拥有上亿的follower,那么这个用户的每一次新增删除帖子操作,将会被复制上亿次,造成增删帖子瓶颈。

在这里插入图片描述

pull模式

和pull模式不同,pull模式下用户每次新增/删除不需要同步到他的所有follower,所以不存在push模式下热点用户增删帖子瓶颈。但是每个用户查询一段时间的timeline时,需要同时查询其所有的关注的用户近期帖子列表。

pull的实现很简单,并不需要单独新增timeline表,每个用户自己发出的帖子都维护在post表和缓存中。pull模式下针对写操作,没有额外的开销,但是对于更加频繁的读操作(用户查看一段时间内所有关注用户的帖子)时,需要用户对自己的所有关注对象的帖子进行按时间的扫描。假设每个用户平均关注500人,那么每次用户刷新timeline页面将进行100次扫描(假设有100个DB分片。一个大型的社交系统,假设同时在线100万人,平均每10s刷新一次页面,那么DB的查询压力将是每秒1亿次查询,仅仅依靠DB,需要上万的DB实例。

这里pull模式是有一个可以优化的点的,在我们关注列表中,其实大部分都是大v,意味着很多用户其实同一时间被多个follower的timeline查询,那么这些并发的关注查询可以只访问一次DB,可以把同一时间的查询进行合并,不过这样其实优化是甚微的。

在这里插入图片描述

push和pull进行结合

我们可以对大V采用拉模式,普通用户采用推模式。对活跃粉丝采用推模式,非活跃粉丝采用拉模式(这种方式可以较好的避免大流量对平台的冲击)

在这里插入图片描述

但是这种架构是有一个很大的风险的:例如某一个大V突然发了一个很有话题性的Feed,那么可能会导致整个Feed产品中的所有用户都无法读取新内容了,原因是这样的:

  1. 大V发送Feed消息
  2. 大V使用拉模式
  3. 大V的活跃粉丝开始通过拉模式读取大V的新Feed

Feed内容太有话题性了,快速传播。未登录的大V粉丝开始登录产品,登录进去后自动刷新,再次桶读3步骤读取大V的Feed内容。

结果就是,服务崩了,服务雪崩

完全使用推模式可以完全解决这个问题,但是会带来存储量的增大,大V微博发送总时间增大,从发送给第一个用户到发送给最后一个用户可能要花费几分钟的时间(一亿用户,100万行每秒,需要100秒),还要为最大并发预留好资源,如果使用表格存储,因为是云服务,则不需要考虑预留最大额度资源的问题。

还有一种解决方案就是把用户分为活跃用户和非活跃用户,但是这样做的缺点就是系统设计复杂性就上来了。

加入缓存

虽然上述用push和pull结合方式已经做到了很大的优化,但是每秒的DB访问频率也让DB很难承受,所以这里也需要加入缓存。

  • 每个列表的元素是用户ID
  • 每十秒产生一个新的用户列表,这10秒内所有发过帖子的用户的ID都在本列表内
  • 同一个列表内的用户ID按照顺序排列,并用B+树。

作为缓存,增量查询部分不会保留数据。但是由于每个用户默认保留了上次查询的结果,可以认为增量部分不会查询太老的数据,假设保留1小时。每10秒如果算10万帖子的话,最多10万不同的用户ID,存储每个ID大约占用32-50个字节,缓存一个小时的数据大约占用1.6GB。

对于中小型的Feed系统,feed数据可以直接通过同步push模式进行分发。用户每发表一条feed,后端系统根据用户的粉丝列表进行全量推送,粉丝用户通过自己的inbox来查看最新的feed。

在这里插入图片描述

对于大型的Feed系统肯定是push+pull

一个feed调用流程需要进行如下操作:

  1. 根据用户uid获取关注列表
  2. 根据关注列表获取每一个被关注者的最新微博ID列表
  3. 获取用户自己收到的微博ID列表
  4. 对这些ID列表进行合并,排序以及分页处理后,拿到需要展现的微博ID列表
  5. 根据这些ID获取对应的微博内容
  6. 对于转发feed进一步获取源feed的内容
  7. 获取用户设置的过滤条件进行过滤
  8. 获取feed作者的user信息进行组装
  9. 获取请求者对这些feed是否收藏,是否点赞等进行组装
  10. 获取这些feed的转发,评论,赞等计数进行组装
  11. 组装完毕,转换成标准格式返回给请求方

可以看到这个步骤是非常复杂的,因此我们要合理的设计Feed系统的缓存。

一个典型的Feed系统的缓存设计主要分为:INBOX, OUTBOX, SOCIAL, GRAPH, CONTENT, EXISTENTCE, COUNTE共六个部分。

Feed id存放在INBOX cache和OUTBOX cache中,存储格式都是vector(有序数组),如图:
在这里插入图片描述

其中INBOX缓存层用于存放聚合效率低的feed id。当用户发表只展现给特定粉丝,特点成员组织的feed时,Feed系统会首先拿到待推送(push)的用户列表,然后将整个feed id 推送给对应的粉丝的inbox。因此inbox是以访问者UID来构建key的,其更新方式是先gets到本地,变更后再cas到异地缓存。

OUTBOX缓存层用于直接缓存用户发表的普通类型feed id,整个cache以发布者UID来构建key。

SOCIAL GRAPH缓存层主要包括用户的关注关系及用户的user信息。用户的关注关系主要包括用户的关注列表,粉丝列表,双向列表等。

EXISTENCE缓存层主要用于缓存各种存在性判断的业务,比如是否已赞,是否阅读这类需求。

COUNTER缓存用于缓存各种计数。Feed系统中计数众多,如用户的feed发表数,关注数,粉丝数,单挑feed的评论数,赞数,阅读数,话题相关计数等。

CONTENT缓存层主要包括热门feed的content,全量feed的content。热门feed是指热点事件爆发时,引发热点事件的源feed。由于热门feed被访问的频率大于普通feed,比如微博中单挑热门feed的QPS可能达到数十万的级别,所以热门feed需要独立缓存,并缓存多份,以提高缓存的访问性能。

简单数据类型的缓存设计

一般系统中的大部分数据都是简单的KV数据类型。这些简单数据类型只需要进行get,set操作,不会用于特殊的记数操作,最适合用Memcached作为缓存组件。在选用某个中间件的时候,我们需要进行容量规划,分析业务数据的size,cache数量,峰值读写等。来确定缓存容量的大小,节点数,部署方式等。

评估表:业务名称,用途,单位(user),平均size,数量,峰值读(QPS),峰值写(QPS),命中率,过期时间,平均穿透加载时间。为了保证服务的可用性。让请求不会大批量的穿透到DB层,给DB带来巨大的压力,极端情况下会引发雪崩,可以引入Main-HA双层架构。

在这里插入图片描述

对后端数据的缓存访问,会先访问Main层,如果miss继续访问HA层,如果HA层命中,则返回结果,再将value回写到Main层,后续对相同的key的访问可以直接在Main层命中。由于Main-HA两层cache中数据不尽相同,通过Main-HA结构,业务可以获取更高的命中率。同时即便出现部分Main节点不可用,也可以通过HA层保证缓存的命中率,可用性

对于微博这种会经常发生超热点事件的情况,可以再加一层L1,L1缓存是小容量缓存,每组L1缓存的容量为Main层的三分之一 - 六分之一。client访问时,首先随机选择一个L1缓存进行访问,如果miss再按照Main->HA的顺序以此访问。由于L1的内容容量远远小于Main,稍冷的数据会迅速剔除,所以L1会持续存储最热的数据,而且L1有多组,大量热数据访问会平均分散到多个L1。

集合数据类型的缓存设计

对于需要计算的集合类数据,如Feed系统中用户关系中的关注列表,分组,最新粉丝列表等,需要进行分页获取,关系计算,从而满足诸如"共同关注",“我关注的人里谁也关注了TA”,可以用Redis进行缓存。Redis提供了丰富的集合类存储结构,支持list,set,zset,hash等。

Redis的架构已经是很完善的了,可以用高可用集群架构进行部署访问。不过,在大集合数据进行全量读取的场景,如用hgetAll获取数据,单次请求可能需要数百上千的元素,用过这个的同学都知道,hgetAll是一个比较坑的东西,容易卡死或者宕机。所以对于获取所有元素这块,我们可以在Redis的上层加一层Memcached,或者直接存在Redis中,单独存一份xx-All,全量查询的时候,查询Memcached或者Redis中后缀-All的,这样可以很好的提升系统的读取性能,还可以减少slave数量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173310.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

布隆过滤器算法

目录布隆过滤器主要有下面的参数:结论举例布隆过滤器主要有下面的参数: 1.假设数据量为n,预期的失误率为p(布隆过滤器大小和每个样本的大小无关)。 2.根据n和p,算出BloomFilter一共需要多少个bit位&#x…

【年度总结 | 2022】想干什么就去干吧,少年

🤵‍♂️ 个人主页: 计算机魔术师 👨‍💻 作者简介:CSDN内容合伙人,全栈领域优质创作者。 程序人生专栏 | 年度总结 ( 2022 ) 作者: 计算机魔术师 版本: 1.0 &#xff08…

关于性能测试需要知道的

随着各企业的业务发展、用户量以及数据量的不断增加,系统承载的压力也会随之增加,服务系统的性能好坏又严重影响企业的利益。因此,性能测试重要性与需求越来越强烈。 常见的性能测试目的 性能测试是确定系统在特定工作负载下的稳定性和响应…

JAVA 基础语法——(HelloWorld案例编写,Notepad软件的安装和使用,注释,关键字,常量,变量,计算机存储单元,数据类型,标识符,类型转换)

目录 HelloWorld案例的编写 Notepad软件的安装和使用 注释 关键字 常量 变量 计算机存储单元 数据类型概述 标识符 类型转换 HelloWorld案例的编写 首先定义一个类——–public class 类名在类定义后加上一对大括号 {}在大括号中间添加一个主(main)方法/函数——–publi…

详解Curl各参数的含义

详解Curl各参数的含义1. Introduction2. Detail2.1 参数-k2.2 参数-X2.3 参数-x2.4 参数-w %{http_code}2.5 参数-d2.6 参数-H2.7 参数-F2.8 参数-O2.9 参数-o2.10 参数-u2.11 参数-b2.12 参数-G3. Awakening1. Introduction [rootnolan ~]# curl -h Usage: curl [options...]…

如何快速部署一款小程序

小程序现在大家都不陌生,微信,qq,抖音,支付宝等等都有小程序,今天给的大家带有通用的小程序,如何快速部署两种方式:自己纯手工开发,或者找别人开发不管哪种方式,今天我带…

【数据结构与算法】选择排序

文章目录选择排序什么是选择排序?选择排序实例分析算法分析代码部分选择排序 什么是选择排序? 选择排序是一种简单直观的排序算法。 它的工作原理是:每一轮从待排序列中选取一个值最小的元素,将它和当前序列的第一个元素互换。 可…

【GD32F427开发板试用】4. ADC采集摇杆模块移动量

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:hehung 之前发帖 【GD32F427开发板试用】1. 串口实现scanf输入控制LED 【GD32F427开发板试用】2. RT-Thread标准版移植 【GD32F427开发板试用…

vue利用provide和inject做套娃组件设计

provide和inject原来用的不多,只是见人引用axios的时候在main.js里使用provide来注入 app.provide(axios, axios) 这样,在所有的vue文件里都可以使用inject来获取这个注入的axios const axios inject("axios"); 这种利用provide和inject做…

(考研湖科大教书匠计算机网络)第一章概述-第五节3:计算机网络体系结构之相关专业术语

文章目录一:实体二:协议三:服务四:协议数据单元本节对应视频 【计算机网络微课堂(有字幕无背景音乐版)】:1.6 计算机网络体系结构(4)—专用术语 注意:本节内容…

2023MyBatis精选面试题2(8道)

一. MyBatis的框架架构设计是怎么样的这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。1. 加载配置:配置来源于两个地方,一处是配置文件&#xff0…

【阅读笔记】《重构》 第一二章

第一章 重构,第一个案例 编译器不会在乎代码好不好看,都是正常运行的。但人在乎,差劲的系统很难修改,因为很难找到修改点,导致程序员很有可能犯错,从而引入bug 重构的第一步 得为即将修改的代码建立一组…

自动化测试Selenium【基础篇一】

自动化测试Selenium【基础篇一】🍎一.什么是自动化测试🍒1.1 自动化测试介绍🍒1.2 单元测试🍒1.3 接口自动化🍒1.4 UI自动化🍒1.5 为什么选择selenium作为我们的web自动化工具?🍒1.6什么是驱动…

DaVinci:限定器 - RGB

调色页面:限定器Color:Qualifier限定器 - RGB Qualifier - RGB根据像素的三原色通道(红、绿、蓝)的值来选择画面上的对应区域,从而限制节点调色的范围。限定器 - RGB 根据指定的各个原色通道的色阶范围来选择连续的近似…

Java 对象处理流(ObjectOutputStream\ObjectInputStream)

文章目录前言什么是对象流?基本介绍ObjectOutputStreamObjectInputStream对象处理流的使用细节前言 处理流:是对一个已存在的流进行处理和封装,通过所封装的流的功能调用实现对数据的操作。而处理流中也有不同的分类,此片介绍的是…

C规范编辑笔记(十一)

往期文章: C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) 正文: 因为太久没有更新了,今天就…

Elasticsearch7.8.0版本高级查询—— 多关键字精确查询文档

目录一、初始化文档数据二、多关键字精确查询文档2.1、概述2.2、示例一、初始化文档数据 在 Postman 中,向 ES 服务器发 POST 请求 :http://localhost:9200/user/_doc/1,请求体内容为: {"name":"张三","…

干货 | 算力网络节点可信度评估和安全管控方案

以下内容整理自清华大学《数智安全与标准化》课程大作业期末报告同学的汇报内容。第一部分:算力网络第二部分:可信度评估一、可信度评估在整个算力网络处理任务的实施流程中,不同部分有不同可信度评估的方法,具体包括:…

忙活了一年的开源社区,终于赶上了春节前的末班车!

随着春节的临近,忙碌了一年的小伙伴们,是不是都已经踏上了回乡的列车?我呢也终于在春节前,完成了 HelloGitHub.com 的重构。HelloGitHub.com 是我在 2017 年的时候用 FlaskjQuery 开发的网站,最初的想法很简单就是为了…

MySQL34道练习题

1、取得每个部门最高薪水的人员名称(要求显示部门编号、人员名称和薪资) 第一步:查询每个部门的最高薪资 SELECT deptno,max(sal) as maxsal FROM emp GROUP BY deptno; 第二步:把上面查询结果当做临时表t&#xff0…