【微信朋友圈设计专题】朋友圈的高性能复杂度分析【学习思路】

参考微信朋友圈技术之道:三个人的后台团队与每日十亿的发布量,朋友圈数据有四个核心要素:

  • 发布。发布数据记录了来自所有用户所有的 feed,比如一个用户发布了几张图片,每张图片的 URL 是什么,在 CDN 里的 URL 是什么,它有哪些元属性,谁可以看,谁不可以看等等。
  • 相册。相册是每个用户独立的,记录了该用户所发布的所有内容。
  • 评论。评论就是针对某个具体发布的朋友评论和点赞操作。
  • 时间线。所谓“刷朋友圈”,就是刷时间线,就是一个用户所有朋友的发布内容。

微信朋友圈的主要数据类型

从上面可以看出,既有结构化数据又有非结构化数据:

  • 非结构化数据:图片\视频和文字,数据量较大。
  • 结构化数据:评论以及时间线。

根据数据的结构特征对数据分类

  • 结构化数据通常是指关系模型数据,其特征是数据关联较大、格式固定。结构化数据具有格式固定的特征,因此一般采用分布式关系数据库进行存储和查询
  • 半结构化数据通常是指非关系模型的,有基本固定结构模式的数据,其特征是数据之间关系比较简单。比如 HTML 文档。一般采用分布式键值系统进行存储和使用。
  • 非结构化数据是指没有固定模式的数据,其特征是数据之间关联不大。比如文本数据就是一种非结构化数据。这种数据可以存储到文档中,通过 ElasticSearch(一个分布式全文搜索引擎)等进行检索

根据数据的存储特性对数据分类

  • 关系型
  • 键值对性
  • 面向列的
  • 面向文档的

存储的种类

  • (分布式)数据库,通过表格来存储结构化数据,方便查找。
  • 分布式键值系统,通过键值对来存储半结构化数据。常用的分布式键值系统有 Redis、Memcache 等,可用作缓存系统。
  • 分布式存储系统,通过文件、块、对象等来存储非结构化数据。常见的分布式存储系统有 Ceph、GFS、HDFS、Swift 等。

最后,选择存储可以参照Visual Guide to NoSQL Systems,根据 CAP 的特性来选择存储
在这里插入图片描述

微信朋友圈数据模型

读写模型

基本上是一个人写,多个人读,相当于发布订阅模型。这里的读包括自己读自己的朋友圈,在集群部署的情况下,用户会往集群的主节点写入数据。主节点负责将数据复制到备份节点。所有需要考虑数据的复制格式和复制方式,虽然这两个概念华仔放在高可用模型中讲了,但是对朋友圈还是很重要的,参见本节“一致性模型”部分。

读写冲突模型——隔离级别

  • 发朋友圈。写操作,“自己”发朋友圈不会和自己冲突。
  • 刷朋友圈。多人读操作,也不存在冲突。
  • 点赞、评论,增量操作,也不会存在冲突。但对数据的顺序上存在一致性的要求。

综上,虽然在 2015 年朋友圈每天的发表量(包括赞和评论)超过 10 亿,浏览量超过 100 亿,并发量极高,但数据冲突不多,相比事务型数据,处理起来相对容易一些。

一致性模型

这里的一致性是指用户发朋友圈,朋友点赞、评论朋友圈,即从使用者出发而不是从服务器端出发来看的概念,即和会话有关的一致性。

在集群部署的情况下,同一个用户的不同请求可能会被发送到不同的机器上处理。虽然这时候是多台机器在处理你的请求,但是从用户的角度来看,需要保证最后的处理结果,和在一台机器上处理的结果是一样的。

写后读一致性

“写后读一致性”(Read after Write Consistency),它也称为“读写一致性”,或“读自己写一致性”(Read My Writes Consistency)。还可以称为“自读自写一致性”。

发朋友圈的人,也会读自己发的朋友圈。自己发布成功的朋友圈,下一刻一定能刷到,这就是“读自己写一致性”名字的由来。当然,从旁观者角度看,可以称为“读你写一致性”(Read Your Writes Consistency)。

在这里插入图片描述

单调读一致性

在这里插入图片描述

在小明发布照片后的瞬间,小红也刷新了朋友圈,此时读取到副本 R1,所以小红看到了照片;片刻之后,小红再次刷新,此时读取到的副本是 R2,于是照片消失了。小红以为小明删除了照片,但实际上这完全是程序错误造成的,数据向后回滚,出现了“时光倒流”。

想要排除这种异常,系统必须实现单调读一致性(Monotonic Read Consistency)。关于单调读一致性的定义,常见的解释是这样的:一个用户一旦读到某个值,不会读到比这个值更旧的值。

实现单调读一致性的方式,可以是将用户与副本建立固定的映射关系,比如使用哈希算法将用户 ID 映射到固定副本上,这样避免了在多个副本中切换,也就不会出现上面的异常了

单调写一致性

单调写一致的英文名是 Monotonic Write。如果你往有容灾的集群里写了多次数据,单调写一致要求所有的节点的写入顺序和你的写入顺序完全一致。这样我们就能保证对于任何一个节点,它看到的别人的写操作和自己的写操作是完全一致的。

一个反例:

在这里插入图片描述

这个对应发朋友圈中“时间线”的概念,所有好友,不论在什么地方,看到的朋友圈的发布顺序应该是一致的,否则会看不明白。

先读后写一致性

前面三个一致性规定了一个会话的行为应该是怎样的。先读后写不同,它规定了多个会话之间互动应该满足怎样的一致性要求。

先读后写要求比较严格。假如你曾经读到了另一个人写入的结果,那么你想再写数据的话,你的写入一定要在另一个人的写入之后发生。也就是说,你们俩之间的写入有个先后顺序。

你如果看到了另一个人的结果,就表示另一个人的写入是过去发生的事情,这时候如果你想再写点新东西进去,那么整个集群需要保证你们俩写入的先后顺序。

在这里插入图片描述

这个适合于评论和点赞,必须先有朋友去圈发布,后有评论和点赞,否则会让人不知所措。

前缀一致性

在这里插入图片描述

小明和小红的评论分别写入了节点 N1 和 N2,但是它们与 N3 同步数据时,由于网络传输的问题,N3 节点接收数据的顺序与数据写入的顺序并不一致,所以小刚是先看到答案后看到问题。

显然,问题与答案之间是有因果关系的,但这种关系在复制的过程中被忽略了,于是出现了异常。

保持这种因果关系的一致性,被称为前缀读或前缀一致性(Consistent Prefix)。要实现这种一致性,可以考虑在原有的评论数据上增加一种显式的因果关系,这样系统可以据此控制在其他进程的读取顺序。

发朋友圈功能实现

数据特性

如下描述引自微信朋友圈技术之道:三个人的后台团队与每日十亿的发布量

比如有两个用户小王和 Mary。小王和 Mary 各自有各自的相册,可能在同一台服务器上,也可能在不同的服务器上。现在小王上传了一张图片到自己的朋友圈。上传图片不经过微信后台服务器,而是直接上传到最近的腾讯 CDN 节点,所以非常快。图片上传到该 CDN 后,小王的微信客户端会通知微信的朋友圈 CDN:这里有一个新的发布(比如叫 K2),这个发布的图片 URL 是什么,谁能看到这些图片,等等此类的元数据,来把这个发布写到发布的表里。

在发布的表写完之后,会把这个 K2 的发布索引到小王的相册表里。所以相册表其实是很小的,里面只有索引指针。相册表写好了之后,会触发一个批处理的动作。这个动作就是去跟小王的每个好友说,小王有一个新的发布,请把这个发布插入到每个好友的时间线里面去。

可以总结出,有如下特点:

  • 发朋友圈的数据含有图片和视频,是非结构化数据,使用 CDN 进行存储
  • 发布动作本身对应一个新的数据结构——“发布”,包含一些元信息——比如图片的 URL 是啥,谁能看到这些图片。“发布”本身是结构化的信息,需要持久化。
  • 发布内容的索引需要写到发布人的相册表里。
  • 发布本身的相关信息需要插入到每个好友的“时间线”中去(每个好友的时间线可能分布在不同的存储上)
  • 发布是一个写扩散的过程——首先写自己的数据副本(自己的相册表),还要把副本的指针插到好友的时间线里面去。所以写比较复杂,会慢一些,但是好友的读取比较简单,只要读取自己的时间线就可以了。(数据同步:写扩散)

(摘自引文) 为什么选择这样一个写扩散的模型?因为读是有很多失败的。一个用户如果要去读两百个好友的相册表,极端情况下可能要去两百个服务器上去问,这个失败的可能性是很大的。但是写失败了就没关系,因为写是可以等待的,写失败了就重新去拷贝,直到插入成功为止。所以这样一个模型可以很大的减少服务的开销。

  • 数据操作冲突
    • 读写冲突:不存在。读发朋友圈的发布信息,写入到好友的时间线里。写入时间线不存在回滚的问题,因为会一直尝试知道写入成功。好友间写时间线不存在写冲突——各写各的。单独一个时间线同时接收好友的发布信息,是新增即追加写,也不存在冲突。
    • 写写冲突:不存在,发朋友圈和刷朋友圈的人各写各的存储。
    • 写读冲突:发朋友圈的人在刷朋友圈的人评论后,又删除了自己的朋友圈,已经读过的人怎么办?把删除操作也包装成一个特殊的“发布”,再“写扩散”到好友的时间线里去即可。但这是需要额外考虑处理手机端的缓存
  • 一致性分析
  • 需要满足前述的所有一致性。使用任务分解,将数据读写请求固定到同一个节点即可。

性能方案

在这里插入图片描述

架构图

在这里插入图片描述

说明:

  • 根据对数据模型的描述,这里的数据读写冲突较少,所以关系数据库不是最好的选择,应该根据团队的技术栈选择:
    • 如果对关系数据库比较熟悉,那就选用;
    • 如果有现成的键值存储那么用键值存储应该是更好的选择。
    • 此处主要追求 AP 特性。
  • 主要需要处理各种数据一致性问题。但是朋友圈数据的事务特征不那么强,所以不需要复杂的分布式数据库,用任务分解,将同一个朋友圈的请求固定到同一个节点即可。

点赞和评论

至于赞和评论的实现,是相对简单的。上面说了微信后台有一个专门的表存储评论和赞的数据,比如 Kate 是 Mary 和小王的朋友的话,刷到了 K2 这一条发布,就会同时从评论表里面拉取对应 K2 的、Mary 留下的评论内容,插入到 K2 内容的下方。而如果另一个人不是 Mary 和小王的共同朋友,则不会看到这条评论。

数据特性

  • 可以看出点赞和评论是依赖于发布数据的:先读取自己的时间线里的发布信息,然后再去评论表里拉取评论。这里评论表应该业务要水平拆分,拆分的键值首先应该包括“发布”的唯一标识,其次考虑到数据的冷热,还应该包括日期。
  • 评论和点赞数据是结构化的数据。
  • 需要持久化。
  • 数据操作冲突分析
    • 大家对发布者的评论是追加写的过程,相当于往数据库里插入一条数据,不存在冲突
    • 数据可见性。刷朋友圈的人读取自己的时间线,然后拿着发布的 ID 去评论表里拉取评论,应该都是可见的。
  • 一致性分析
    • 如何保证评论表里评论的顺序?后发表的评论可以先保存到评论表中么?
    • 比如小明和小红都去评论小刚的朋友圈,那么他们的顺序是无所谓的。但如果一但顺序确定了,他们自己或其他人再次读取到这两条评论时,顺序必须是一致的,这就是单调写一致性,如果大家都去同一个评论表里拉取,那么自然会满足。此时需要“任务分解器”将这个任务分配到同一个库中。
    • 如果小明先评论小刚,小红又去评论小明,那么小红一定是读取到了小明的评论,然后才评论小明,所以他们一定是有序的。这就是先读后写一致性得到满足。此时需要“任务分解器”将这个任务分配到同一个库中。

性能方案

经过上述分析,可以看出和发朋友圈的性能方案一样。

架构图

经过上述分析,可以看出和发朋友圈的架构图一样。

架构设计复杂度模型

可以按照业务复杂度和质量复杂度两个维度对复杂度建模:

  • 业务复杂度
  • 业务固有的复杂度,主要体现为难以理解、难以扩展,比如
    • 业务数量多(微信)
    • 业务流程长(支付宝)
    • 业务之间关系复杂(例如 ERP)
  • 质量复杂度
  • 主要是对质量属性的要求
    • 高性能
    • 高可用
    • 成本
    • 安全

MECE 法则

按照两个维度对架构设计复杂度建模,实际是 MECE 法则的一次具体应用。

MECE 法则,是麦肯锡咨询顾问芭芭拉·明托在《金字塔原理》中提出的一个思考工具,它是 Mutually Exclusive
Collectively Exhaustive 的缩写,意思是“相互独立,完全穷尽”,也常被称为“不重叠,不遗漏”。

穷尽分解后,可以根据分解的两个维度(比如业务复杂度和质量复杂度)把问题空间划分为 4
个象限,形成四象限坐标图,把各种系统按照复杂度分类分布到其中。四象限坐标图是最简单、最常用的管理、思考工具。

在这里插入图片描述

针对每个象限都有自己的应对之策

在这里插入图片描述

可以看到,DDD 并不能降低质量复杂度。因为 DDD 的主要流程是分解业务域成子域、子子域,即它是针对复杂业务问题的。

资料:https://xie.infoq.cn/article/bc8a70a7b4b6d8581baf843fd

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休居士

感谢您的支持,我会继续努!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值