微信视频号聊天室技术介绍

之前介绍过微信红包系统的架构(参考:微信红包后台系统设计微信红包设计方案)。

微信作为国内在线用户最多的国民应用,其架构设计中的应对高流量、低延迟的一些技巧,在其他公司或团队解决高并发问题时一定有一些参考作用。今天介绍下微信视频号聊天室的高并发消息收发解决方案。

视频号在直播时相当于把所有的关注者拉到一个群里面,因为同一时间一个用户只能关注一个聊天室,所以他的技术方案和之前介绍的群聊天还是有些差别的。

之前介绍过钉钉的群聊天方案。参考:钉钉架构设计

  • 群功能消息特点:一个群少于500人,群成员之间有关系,群成员流动性低,对离线消息较为关注。

  • 聊天室消息特点:数万人参与,成员之间没有关系,聊天室成员流动性较高,不关注离线消息。

基于以上两个特点,群消息适合写扩散机制,而聊天室适合读扩散机制。

聊天室可以看做是一个基于房间的临时消息信道,主要功能包括消息收发、在线状态统计等。

微信聊天室形态最早出现在2017年,当时主要用于电竞直播间,支持了高性能、高实时、高可扩展的消息收发。

59b6214e45cb2f2eb9241c6b1b2bfe3c.png

聊天室消息的一大特点就是要做到高实时,基于上面架构图,思考下如何实现用户消息的实时同步呢?

可以采用长轮询方案。

e00aa138139392382ce6d669a43bd03d.png

用户读请求进来之后,在RecvSrv进行消息轮询。当发现新消息之后会给接入层发送重新获取的请求,这样增量的消息就被读取到客户端了。

这里为什么没有考虑websocket呢?

首先ws的推模式有可能丢消息,这样端上还需要一个拉模式兜底。同时推模式需要维护一个精准的在线列表,成本变大。这种长轮询对于客户端来说其实是个短连接,客户端实现起来更简单。

因为聊天室更适合采用消息的读扩散模型,这样会给读盘造成压力,所以需要有个cache解决这个问题。

dbf469de860a8b4d0e1e3b4c640f68d9.png

SendSvr接收到用户消息时,写入消息列表,然后向RecvSvr集群发送通知。当RecvSvr接收到新消息通知后,异步线程拉取消息。

当RecvSvr收到某个聊天室的写消息请求时,触发该聊天室的异步轮询,为了避免通知失效导致无法更新消息,需要有个兜底,比如1s内触发一次轮询拉取。同时需要考虑机器异常重启时的数据自动恢复能力。

d6058e5d67074c516588064d7f790e38.png

读写队列过程中可能存在并发的情况,可以采用COW思想。当有写更新,后续的读到最新写的队列读。当再写队列时,比对下读队列,看看是否需要从新读补全到主数据上。

以上是聊天室1.0的架构是否可以支持千万级同时在线的直播呢?

还存在一些问题,比如消息信道不能保证所有消息都下发。一个房间内用户的状态信息聚合到同一台statSvr,存在单点瓶颈,并发变更时导致部分房间在线数跳变。缺少历史在线人数统计。

43d8023597eda3fd4614703e66006596.png

那2.0架构如何解决以上问题的呢?

cddc6e6e598d7765d5e951831b62731b.png

一些信令丢失的原因是因为,在RecvSvr的cache中只保留了最近2000条消息,有些消息在客户端还没来得及收到,就被cache淘汰了。

所以目前看起来,写扩展和简单的读扩散都不可行。

还有一种方法是拆分,比如消息分级,缓存在两个cache里面。但也会有问题,比如recevSvr需要同时读两个消息表,这样会消耗recvSvr对cache的两倍访问。

在1.0架构上,每个消息发送到服务端之后,recvSvr其实是都可以感知到notify的,所以每次拉取的消息数量都不会很多,这一过程不会丢消息。

为充分利用cache,同时保证消息的下发可以这样设计。

在消息表上对重要消息打标。在recvSvr拉到消息后,将消息分为普通消息和重要消息。这样在消息收取时,先收取重要消息,再收取普通消息,实现了重要消息的优先下发。

聊天室在线状态统计实现,参考了微信设备在线的设计。

每个直播房间对应一个sect。按照房间id,选择一台机器作为master,该房间的请求读写此机器。为解决单点问题,将master下这个房间的数据同步到其他sect下的机器上,这样即使master挂了,仍然可以通过其他机器进行读写。

微信团队还是用机器维度的分布式方案,简单点用分布式缓存就行了。

记录聊天室活跃用户,可以在服务端记录两个值:用户id、活跃时间。

通过定期心跳,更新用户在线状态。

之前提到过,一个直播间有上千万人,以1000w为例,如果10s心跳一次,请求就是6000w/min,考虑到缓存穿透、并发性能,需要从磁盘回拉数据,可能导致拉齐到一些需要延迟删除的用户状态信息,以至于获取到的在线用户数远大于实际用户数。

怎么解呢?

可以考虑拆key+读写分离+异步聚合落盘方案解决。

每台机器负责统计部分在线数据。每台机器内按照uid做哈希,打散到多个shard。每个shard对应一个key。

74968d717942a5de68456b582ff1c11a.png

在数据聚合查询时,每台机器拉取自己所有的key,最终组合出一个完整的在线列表。

这样通过拆机器、hash等方式将原有6000w/min的请求打散了,降低了并发问题。在聚合查询时,每个机器负责自己的查询聚合,降低了穿透的问题和数据不一致的问题。

大小直播间的流量差异很大的,所以需要考虑大小直播间不要相互影响。

大直播间需要更多的机器保证体验,小直播间用小规模机器即可。

这里微信团队考虑了微信支付对于大小商户的隔离做法,做了流量隔离。

  • 对于可预测的大直播间加白,直接走vip;

  • 其他直播间走普通;

  • 大直播间在线人数多,需要拆在线列表key;

在longpolling的机制下,直播间一直有消息的话,100w的在线每分钟至少会产生6kw/min的请求,而1500w更是高达9亿/min。

logicsvr是cpu密集型的服务,按30w/min的性能来算,至少需要3000台。所以这个地方必须要有一些柔性措施把控请求量,寻找一个体验和成本的平衡点。

这个措施一定不能通过logicsvr拒绝请求来实现,原因是longpolling机制下,客户端接收到回包以后是会马上发起一次新请求的。logicsvr拒绝越快,请求量就会越大,越容易造成滚雪球。

5cc572b8aa0da432a8f5173319ec47a0.png

上图可以看到,正常情况下,当recvSvr没有收到新消息时,可以让请求在proxy层hold住,等待连接超时或是notify。

所以可以通过在proxy的柔性控制,让请求或者回包在proxy hold一段时间,来降低请求频率。

6ff7cace4cd4de719e2db9a2e9f06401.png

根据不同的在线数,设置不同的收取间隔。在客户端上下文里面记录下上次收取的时间。成功收取消息后,让请求在proxy层hold一段时间。

微信这么多年在微信支付、微信红包、小游戏、在线聊天产品形态下积累了业界最顶尖的最佳实践,所以业界老有人说微信技术差,人家能随意支持千万级、亿级流量,技术水平肯定是业界最强的。可能少的是在业界的一些曝光吧。

希望对你有用。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值