论微信是如何实现好友一发送朋友圈,我就能收到的

对于这个问题我的第一反应是:这不就是个广播吗, 当我一发送朋友圈就将该消息推送给我的所有好友, 但是与我的导师进行深层次的交流后, 我发现这个问题还挺有意思的, 所以觉得写一篇文章来详细谈一下我的看法

IM系统概述

IM系统就是即时通讯(Instant Messaging)系统的简称。而微信、qq就是典型IM技术应用场景.

IM基础架构中的各分层职责如下:

  1. 客户端:作为与服务端进行消息收发通信的终端
  2. 网关层:也叫接入层,为客户端收发消息提供入口
  3. 服务层:负责IM系统各功能的核心业务逻辑实现,比如聊天服务、离线消息服务、红包服务、直播服务等
  4. 存储层:负责IM系统相关数据的持久化存储,包括消息内容、账号信息、社交关系链等

信息推流

信息推流(以下简称“Feed流”)这种功能在我们手机APP中几乎无处不在(尤其是社交/社群产品中),最常用的就是微信朋友圈、新浪微博等。

对Feed流的定义,可以简单理解为只要大拇指不停地往下划手机屏幕,就有一条条的信息不断涌现出来。就像给牲畜喂饲料一样,只要它吃光了就要不断再往里加,故此得名Feed(饲养)。

大多数带有Feed流功能的产品都包含两种Feed流:

  • 基于算法:即动态算法推荐,比如今日头条、抖音短视频;
  • 基于关注:即社交/好友关系,比如微信、QQ。

它们背后用到的技术差别会比较大。不同于“推荐”页卡那种千人千面算法推荐的方式,通常“关注”页卡所展示的内容先后顺序都有固定的规则,最常见的规则是基于时间线来排序,也就是展示“我关注的人所发的帖子、动态、心情,根据发布时间从晚到早依次排列”。对于微信朋友圈来说,主要是基于关注的Feed流

Feed流技术实现方案1:读扩散

读扩散也称为“拉模式”,这应该是最符合我们认知直觉的一种技术实现方式。

读扩散原理图:

如上图所示:每一个内容发布者都有一个自己的发件箱(“发布内容列表”),每当我们发出一个新帖子,都存入自己的发件箱中。当我们的粉丝来阅读时,系统首先需要拿到粉丝关注的所有人,然后遍历所有发布者的发件箱,取出他们所发布的帖子,然后依据发布时间排序,展示给阅读者。

这种设计:阅读者读一次Feed流,后台会扩散为N次读操作(N等于关注的人数)以及一次聚合操作,因此称为读扩散。每次读Feed流相当于去关注者的收件箱主动拉取帖子,因此也得名——拉模式

  • 优点:底层存储简单,没有空间浪费;
  • 缺点:每次读操作会非常重,操作非常多。

设想一下:如果我关注的人数非常多,遍历一遍我所关注的所有人,并且再聚合一下,这个系统开销会非常大,时延上可能达到无法忍受的地步。

因此:读扩散主要适用系统中阅读者关注的人没那么多,并且刷Feed流并不频繁的场景。

拉模式还有一个比较大的缺点:就是分页不方便,我们刷微博或朋友圈,肯定是随着大拇指在屏幕不断划动,内容一页一页的从后台拉取。如果不做其他优化,只采用实时聚合的方式,下滑到比较靠后的页码时会非常麻烦。

Feed流技术实现方案2:写扩散

读扩散那种很重的读逻辑并不适合大多数场景。我们宁愿让发帖的过程复杂一些,也不愿影响用户读Feed流的体验,因此稍微改造一下前面方案就有了写扩散。写扩散也称为“推模式”,这种模式会对拉模式的一些缺点做改进。

写扩散原理图:

如上图所示:系统中每个用户除了有发件箱,也会有自己的收件箱。当发布者发表一篇帖子的时候,除了往自己发件箱记录一下之外,还会遍历发布者的所有粉丝,往这些粉丝的收件箱也投放一份相同内容。这样阅读者来读Feed流时,直接从自己的收件箱读取即可。

这种设计:每次发表帖子,都会扩散为M次写操作(M等于自己的粉丝数),因此成为写扩散。每篇帖子都会主动推送到所有粉丝的收件箱,因此也得名推模式

这种模式可想而知:发一篇帖子,背后会涉及到很多次的写操作。通常为了发帖人的用户体验,当发布的帖子写到自己发件箱时,就可以返回发布成功。后台另外起一个异步任务,不慌不忙地往粉丝收件箱投递帖子即可。

  • 优点:在于通过数据冗余(一篇帖子会被存储M份副本),提升了阅读者的用户体验。
  • 缺点:当好友量很大的时候, 每一次发布内容, 都会导致很多次写操作

另外:由于写扩散是异步操作,写的太慢会导致帖子发出去半天,有些粉丝依然没能看见,这种体验也不太好。

Feed流技术实现方案3:读写混合模式

读写混合也可以称作“推拉结合”,这种方式可以兼具读扩散和写扩散的优点。

我们首先来总结一下读扩散和写扩散的优缺点:

优点

缺点

使用场景

读扩散

底层存储简单,没有空间浪费, 发帖操作简单

读帖复杂,关注人数多的时候,读操作非常重

用户不活跃,很少读帖

写扩散

读帖简单

发帖复杂,有大量的数据冗余

用户活跃,经常发帖

因此:在设计后台存储的时候,我们如果能够区分一下场景,在不同场景下选择最适合的方案,并且动态调整策略,就实现了读写混合模式。

Feed流中的分页问题

不管是读扩散还是写扩散,Feed流本质上是一个动态列表,列表内容会随着时间不断变化。传统的前端分页参数使用page_size和page_num,分表表示每页几条,以及当前是第几页。

对于一个动态列表会有如下问题:

如上图所示:在T1时刻读取了第一页,T2时刻有人新发表了“内容11”,在T3时刻如果来拉取第二页,会导致错位出现,“内容6”在第一页和第二页都被返回了。事实上,但凡两页之间出现内容的添加或删除,都会导致错位问题。

为了解决这一问题:通常Feed流的分页入参不会使用page_size和page_num,而是使用last_id来记录上一页最后一条内容的id。前端读取下一页的时候,必须将last_id作为入参,后台直接找到last_id对应数据,再往后偏移page_size条数据,返回给前端,这样就避免了错位问题。

采用last_id的方案有一个重要条件:就是last_id本身这条数据不可以被硬删除。

设想一下:

  • 上图中T1时刻返回5条数据,last_id为内容6;
  • T2时刻内容6被发布者删除;
  • 那么T3时刻再来请求第二页,我们根本找不到last_id对应的数据了,也就无法确认分页偏移量。

通常碰到删除的场景:我们采用软删除方式,只是在内容上置一个标志位,表示内容已删除。

由于已经删除的内容不应该再返回给前端,因此软删除模式下,找到last_id并往后偏移page_size条,如果其中有被删除的数据会导致获得足够的数据条数给前端。

这里一个解决方案是找不够继续再往下找,另一种方案是与前端协商,允许返回条数少于page_size条,page_size只是个建议值。甚至大家约定好了以后,可以不要page_size参数。

结论

通常写扩散适用于好友量不大的情况,微信朋友圈应该就是写扩散模式。每一名微信用户的好友上限为5000人,也就是说你发一条朋友圈最多也就扩散到5000次写操作,如果异步任务性能好一些,完全没有问题。

 欢迎大家关注我的个人公众号,我会持续更新技术相关的文章或一些科技资讯

参考文献: IM开发技术学习:揭秘微信朋友圈这种信息推流背后的系统设计-IM开发/专项技术区 - 即时通讯开发者社区!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值