当我那天拿着手机,正在和朋友们的微信群里畅聊着八卦新闻和即将到来的周末计划时,忽然一条带着喜意的消息扑面而来,消息正中间写着八个大字:恭喜发财,大吉大利。
图片
抢红包!!相信大部分人对此都不陌生,那微信的这个群聊系统是如何设计的,让我们可以方便地聊天、分享图片和表情,还有那个神奇的红包功能呢?
这个问题一直困扰着,于是我决定深入了解一下,看看微信的群聊系统背后的设计是怎样的。
微信作为 10 亿用户级别的全民 App,想必大家都用过,微信建群功能是微信里面核心的一个能力,它可以将数百个好友或陌生人放进一个群空间。
图片
或许你已经在微信上体验过很多次群组聊天,但你是否好奇过这个背后的系统是如何设计的呢?
今天我们就来探讨一下。
微信群聊功能是社交应用的核心功能之一,它允许用户创建自己的社交圈子,与家人、朋友或共同兴趣爱好者进行友好地交流。
以下是微信群聊系统的核心功能:
图片
当我们面对 10 亿微信用户每天都可能使用建群功能的情景时,就需要处理大规模的用户并发。这就引出了系统的非功能需求,包括:
在概要设计中,我们考虑了系统的核心组件和基本业务的概要设计。
微信群聊系统中,会涉及到如下核心组件和协议。
图片
除了拉好友建群,微信还实现了面对面建群的能力。
接下来,我们深入探讨了三到四个核心功能的详细设计,包括面对面建群、消息发送与接收及抢红包功能。
用户发起面对面建群,并输入一个 4 位数的随机码,周围的用户输入该随机码后可加入群聊,面对面建群功能通常涉及数据表设计和核心业务交互流程如下。
图片
用户 A 在手机端应用中发起面对面建群,并输入一个随机码,校验通过后,等待周围(50 米之内)的用户加入。此时,系统将用户信息以 HashMap 的方式存入缓存中,并设置过期时间为 3min。
{随机码,用户列表[用户A(ID、名称、头像)]}
用户 B 在另一个手机端发起面对面建群,输入指定的随机码,如果该用户周围有这样的随机码,则进入同一个群聊等待页面,并可以看到其它群员的头像和昵称信息。
此时,系统除了根据随机码获取所有用户信息,也会实时更新缓存里的用户信息。
图片
当第一个用户点击进入该群时,就可以加入群聊,系统将生成的随机码保存在 RandomCode 表中,并关联到新创建的群 ID,更新群成员的个数。
然后,系统将用户信息和新生成的群聊信息存储在 Group、GroupMember 表中
之后 B、C 用户带着随机码加入群聊时,手机客户端向服务器后端发送请求,验证随机码是否有效。服务器后端验证随机码,检查随机码是否存在于缓存中,以及是否在有效期内。
然后,判断当前群成员是否满员(目前普通用户创建的群聊人数最多为 500 人),如果验证通过,服务器后端将用户 B、C 添加到群成员表 GroupMember 中,并返回成功响应。
移动客户端应用收到成功响应后,更新用户 B、C 的群聊列表,展示他们已加入的新群聊。
这样,用户 A 通过创建随机码和周围的用户扫描二维码的方式成功建立了一个面对面建群。这个功能涉及了多个技术组件,包括分布式缓存、数据库、二维码生成和验证等。
同时,在面对面建群的过程中相当重要的能力是标识用户的区域,比如 50 米以内。这个可以用到 redis 的 GeoHash 算法,来获取一个范围内的所有用户信息。
由于篇幅有限,这里不展开赘述,想了解更多和二维码生成及位置算法的细节,可以看我之前的文章:听说你会架构设计?来,弄一个公交&地铁乘车系统。
当某个成员在微信群里发言,系统需要处理消息的分发、通知其他成员、以及确保消息的显示。以下是这一功能的详细交互步骤,以及数据库存储方案。
消息发送和接收时序图如下:
图片
这个流程确保了消息和媒体文件的有效存储和展示。用户可以上传和查看各种类型的媒体数据,而服务器后端通过关联 Message 和对象存储服务器中的信息,实现了有效的消息存储和展示。
在微信群中保存和展示用户的图片、视频或音频数据,通常需要进行数据存储和展示方面的设计。除了上面面对面建群功能中提到的用户表和群组表以外,还需要以下表结构:
我们知道,MySQL 每次查询 select count 类型的语句时,都会触发全表扫描,所以每次加载消息未读数都很慢。
为了查询性能考虑,我们可以将用户的消息数量存入 Redis,并实时记录一个未读数值。并且,当未读数大于 99 时,就将未读数值置为 100 且不再增加。
当推送用户消息时,只要未读数为 100,就将推送消息数设置为 99+,以此来提升存储的性能和交互的效率。
抢红包功能允许用户在群聊中发送任意个数和金额的红包,群成员可以抢到随机金额的红包,但要保证每个用户的红包金额不小于 0.01 元。
图片
抢红包的详细交互流程如下:
抢红包功能需要关注抢红包的数据库设计,抢红包实时性和红包分配算法。
红包表 redpack 的字段如下:
该表用来记录用户发了多少红包,以及需要维护的剩余金额。
红包记录表 redpack_record 如下:
记录表用来存放用户具体抢到的红包信息,也是红包表的副表。
从 2015 年起,微信红包的抢红包和拆红包就分离了,用户点击抢红包后需要进行两次操作。这也是为什么明明有时候抢到了红包,点开后却发现该红包已经被领取完了。
抢红包的交互步骤如下:
红包金额分配时,由于是随机分配,所以有两种实现方案:实时拆分和预先生成。
实时拆分,指的是在抢红包时实时计算每个红包的金额,以实现红包的拆分过程。
这个需要我们设计一个好的拆分算法,让红包拆分时一直保证后续待拆分红包的金额不能为空。
实时拆分时,不容易做到拆分的红包金额服从正态分布规律。
预先生成,指的是在红包开抢之前已经完成了红包的金额拆分,抢红包时只是依次取出拆分好的红包金额。
这种方式对拆分算法要求较低,可以拆分出随机性很好的红包金额,但通常需要结合队列使用,而且需要多设计一个表来存储红包的拆分金额。
综合上述优缺点考虑,以及微信群聊中的人数不多(目前最高 500 人),所以我们采用实时拆分的方式,用二倍均值法来生成随机红包,只满足随机即可,不需要正态分布。
故可能出现很大的红包差额,但这更刺激不是吗