听说你会架构设计?来,弄一个群聊系统

开发 架构
群聊系统是社交应用的核心功能之一,每个社交产品几乎都有着群聊系统的身影:包括但不限于 QQ、微信、抖音、小红书等。上述介绍的技术细节可能只是群聊系统的冰山一角,像常见的抢红包、群内音视频通话这些核心功能也充斥着大量的技术难点。

大家好,我是小❤,一个漂泊江湖多年的 985 非科班程序员,曾混迹于国企、互联网大厂和创业公司的后台开发攻城狮。

1. 引言

前些天所在部门出去团建,于是公司行政和 HR 拉了一个微信群,发布一些跟团和集合信息。

当我正在查看途径路线和团建行程时,忽然一条带着喜意的消息扑面而来,消息上赫然带着八个大字:恭喜发财,大吉大利。

图片图片

抢红包!!原来是公司领导在群里发了个红包,于是引得群员哄抢,气氛其乐融融。

毕竟,团不团建无所谓,不上班就很快乐;抢多抢少无所谓,有钱进就很开心。

打工人果然是最容易满足的生物!</手动狗头 🐶>

我看着群里嬉戏打闹的聊天,心中陷入了沉思:微信这个集齐了陌生人聊天、文件分享和抢红包功能的群聊设计确实有点意思,如果在面试或者工作中让我们设计一个群聊系统,需要从哪些方面来考虑呢?

群聊系统设计

面试官:微信作为 10 亿用户级别的全民 App,有用过吧?

我:(内心 OS,说没用过你也不会相信啊~)当然,亲爱的面试官,我经常使用微信来接收工作消息和文件,并且经常在上面处理工作内容。

面试官:(内心 OS:这小伙子工作意识很强嘛,加分!)OK,微信的群聊功能是微信里面核心的一个能力,它可以将数百个好友或陌生人放进一个群空间,如果让你设计一个用户量为 10 亿用户的群聊系统,你会怎么设计呢?

2. 系统需求

2.1 系统特点与功能需求

我:首先群聊功能是社交应用的核心能力之一,它允许用户创建自己的社交圈子,与家人、朋友或共同兴趣爱好者进行友好地交流。

以下是群聊系统常见的几个功能:

图片图片

  • 创建群聊:用户可以创建新的聊天群组,邀请其他好友用户加入或与陌生人面对面建群。
  • 群组管理:群主和管理员能够管理群成员,设置规则和权限。
  • 消息发送和接收:允许群成员发送文本、图片、音频、视频等多种类型的消息,并推送给所有群成员。
  • 实时通信:消息应该能够快速传递,确保实时互动。
  • 抢红包:用户在群聊中发送任意个数和金额的红包,群成员可以抢到随机金额的红包。

2.2 非功能需求

除了功能需要,当我们面对 10 亿微信用户每天都可能使用建群功能的情景时,还需要处理大规模的用户并发。

这就引出了系统的非功能需求,包括:

  • 高并发:系统需要支持大量用户同时创建和使用群组,以确保无延迟的用户体验。
  • 高性能:快速消息传递、即时响应,是数字社交的关键。
  • 海量存储:系统必须可扩展,以容纳用户生成的海量消息文本、图片及音视频数据。

面试官:嗯,不错,那你可以简要概述一下这几个常用的功能吗?

3. 核心组件

我:好的,我们首先做系统的概要设计,这里涉及到群聊系统的核心组件和基本业务的概要说明。

3.1 核心组件

群聊系统中,会涉及到如下核心组件和协议。

图片图片

  • 客户端:接收手机或 PC 端微信群聊的消息,并实时传输给后台服务器;
  • Websocket传输协议:支持客户端和后台服务端的实时交互,开销低,实时性高,常用于微信、QQ 等 IM 系统通信系统;
  • 长连接集群:与客户端进行 Websocket 长连接的系统集群,并将消息通过中间件转发到应用服务器;
  • 消息处理服务器集群:提供实时消息的处理能力,包括数据存储、查询、与数据库交互等;
  • 消息推送服务器集群:这是信息的中转站,负责将消息传递给正确的群组成员;
  • 数据库服务器集群:用于存储用户文本数据、图片的缩略图、音视频元数据等;
  • 分布式文件存储集群:存储用户图片、音视频等文件数据。

3.2 业务概要说明

在业务概要说明里,我们关注用户的交互方式和数据存储......

面试官:稍等一下,群聊系统的好友建群功能比较简单,拉好友列表存数据就可以了!你用过面对面建群吧,可以简要说一下如何设计面对面建群功能吗?

我:(内心 OS,还好之前在吃饭时用过面对面建群结账,不然就G了),好的,群聊系统除了拉好友建群外,还支持面对面建群的能力。

4. 面对面建群

用户发起面对面建群后,系统支持输入一个 4 位数的随机码,周围的用户输入同一个随机码便可加入同一个群聊,面对面建群功能通常涉及数据表设计和核心业务交互流程如下。

4.1 数据库表设计

  1. User 表:存储用户信息,包括用户 ID、昵称、头像等。
  2. Group 表:存储群组信息,包括群 ID、群名称、创建者 ID、群成员个数等。
  3. GroupMember 表:关联用户和群组,包括用户 ID 和群 ID。
  4. RandomCode 表:存储面对面建群的随机码和关联的群 ID。

4.2 核心业务交互流程

图片图片

用户 A 在手机端应用中发起面对面建群,并输入一个随机码,校验通过后,等待周围(50 米之内)的用户加入。此时,系统将用户信息以 HashMap 的方式存入缓存中,并设置过期时间为 3min。

{随机码,用户列表[用户A(ID、名称、头像)]}

用户 B 在另一个手机端发起面对面建群,输入指定的随机码,如果该用户周围有这样的随机码,则进入同一个群聊等待页面,并可以看到其它群员的头像和昵称信息。

此时,系统除了根据随机码获取所有用户信息,也会实时更新缓存里的用户信息。

图片图片

成员A进群

当第一个用户点击进入该群时,就可以加入群聊,系统将生成的随机码保存在 RandomCode 表中,并关联到新创建的群 ID,更新群成员的个数。

然后,系统将用户信息和新生成的群聊信息存储在 Group、GroupMember 表中,并实时更新群成员个数。

成员B加入

然后,B 用户带着随机码加入群聊时,手机客户端向服务器后端发送请求,验证随机码是否有效。后台服务检查随机码是否存在于缓存中,如果存在,则校验通过。

然后,根据 Group 中的成员个数,来判断当前群成员是否满员(目前普通用户创建的群聊人数最多为 500 人)。

如果验证通过,后台将用户 B 添加到群成员表 GroupMember 中,并返回成功响应。

面试官:如果有多个用户同时加入,MySQL 数据库如何保证群成员不会超过最大值呢?

我:有两种方式可以解决。一个是通过 MySQL 的事务,将获取 Group 群成员数和插入 GroupMember 表操作放在同一个事务里,但是这样可能带来锁表的问题,性能较差。

另一种方式是采用 Redis 的原子性命令incr 来记录群聊的个数,其中 key 为群聊ID,value 为当前群成员个数。

当新增群员时,首先将该群聊的人数通过 incr 命令加一,然后获取群成员个数。如果群员个数大于最大值,则减一后返回群成员已满的提示。

使用 Redis 的好处是可以快速响应,并且可以利用 Redis 的原子特性避免并发问题,在电商系统中也常常使用类似的策略来防止超卖问题。

位置算法

同时,在面对面建群的过程中相当重要的能力是标识用户的区域,比如 50 米以内。这个可以用到 Redis 的 GeoHash 算法,来获取一个范围内的所有用户信息。

由于篇幅有限,这里不展开赘述,想了解更多位置算法相关的细节,可以看我之前的文章:听说你会架构设计?来,弄一个公交&地铁乘车系统。

面试官:嗯不错,那你再讲一下群聊系统里的消息发送和接收吧!

5. 消息发送与接收

我:当某个成员在微信群里发言,系统需要处理消息的分发、通知其他成员、以及确保消息的显示。

在群聊系统中保存和展示用户的图片、视频或音频数据时,通常需要将元数据和文件分开存储。

其中元数据存储在 MySQL 集群,文件数据存储在分布式对象存储集群中。

5.1 交互流程

消息发送和接收的时序图如下所示:

图片图片

  1. 用户A在群中发送一条带有图片、视频或音频的消息。
  2. 移动客户端应用将消息内容和媒体文件上传到服务器后端。
  3. 服务器后端接收到消息和媒体文件后,将消息内容存储到 Message 表中,同时将媒体文件存储到分布式文件存储集群中。在 Message 表里,不仅记录了媒体文件的 MediaID,以便关联消息和媒体;还记录了缩略图、视频封面图等等。
  4. 服务器后端会向所有群成员广播这条消息。移动客户端应用接收到消息后,会根据消息类型(文本、图片、视频、音频)加载对应的展示方式。
  5. 当用户点击查看图片、视频或音频缩略图时,客户端应用会根据 MediaID 到对象存储集群中获取对应的媒体文件路径,并将其展示给用户。

5.2 消息存储和展示

除了上述建群功能中提到的用户表和群组表以外,存储元数据还需要以下表结构:

  1. Message表: 用于存储消息,每个消息都有一个唯一的 MessageID,消息类型(文本、图片、视频、音频),消息内容(文字、图片缩略图、视频封面图等),发送者 UserID、接收群 GroupID、发送时间等字段。
  2. Media表: 存储用户上传的图片、视频、音频等媒体数据。每个媒体文件都有一个唯一的 MediaID,文件路径、上传者 UserID、上传时间等字段。
  3. MessageState表: 用于存储用户消息状态,包括 MessageID、用户 ID、是否已读等。在消息推送时,通过这张表计算未读数,统一推送给用户,并在离线用户的手机上展示一个小数字代表消息未读数。

面试官:我们时常看到群聊有 n 个未读消息,这个是怎么设计的呢?

我:MessageState 表记录了用户的未读消息数,想要获取用户的消息未读数时,只需要客户端调用一下接口查询即可获取,这个接口将每个群的未读个数加起来,统一返回给客户端,然后借助手机的 SDK 推送功能加载到用户手机上。

面试官:就这么简单吗,可以优化一下不?

我:(内心 OS,性能确实很差,就等着你问呢)是的,我们需要优化一下,首先 MySQL 查询 select count 类型的语句时,都会触发全表扫描,所以每次加载消息未读数都很慢。

为了查询性能考虑,我们可以将用户的消息数量存入 Redis,并实时记录一个未读数值。并且,当未读数大于 99 时,就将未读数值置为 100 且不再增加。

当推送用户消息时,只要未读数为 100,就将推送消息数设置为 99+,以此来提升存储的性能和交互的效率。

面试官:嗯,目前几乎所有的消息推送功能都是这么设计的。那你再说一下 10 亿用户的群聊系统应该如何在高并发,海量数据下保证高性能和高可用吧!

我:我想到了几个点,比如采用集群部署、消息队列、多线程、缓存等。

集群部署:可扩展

在群聊系统中,我们用到了分布式可扩展的思想,无论是长连接服务、消息推送服务,还是数据库以及分布式文件存储服务,都是集群部署。

一方面防止单体故障,另一方面可以根据业务来进行弹性伸缩,提升了系统的高可用性。

消息队列:异步、削峰

在消息推送时,由于消息量和用户量很多,所以我们将消息放到消息队列(比如 Kafka)中异步进行消费和推送,来进行流量削峰,防止数据太多将服务打崩。

多线程

在消息写入和消费时,可以多线程操作,一方面节省了硬件开销,不至于部署太多机器。另一方面提升了效率,毕竟多个流水线工作肯定比单打独斗更快。

其它优化

缓存前面已经说到了,除了建群时记录 code,加群时记录群成员数,我们还可以缓存群聊里最近一段时间的消息,防止每个用户都去 DB 拉取一遍数据,这提升了消息查阅的效率。

除此之外,为了节省成本,可以记录流量的高峰时间段,根据时间段来定时扩缩节点(当然,这只是为了成本考虑,在实际业务中这点开销不算什么大问题)。

6. 小结

后续

面试官:嗯不错,实际上的架构中也没有节省这些资源,而是把重心放在了用户体验上。(看了看表)OK,那今天的面试就到这,你有什么想问的吗?

我:(内心 OS,有点慌,但是不能表现出来)由于时间有限,之前对系统高并发、高性能的设计,以及对海量数据的处理浅尝辄止,这在系统设计的面试中占比如何?

面试官:整体想得比较全,但是还不够细节。当然,也可能是时间不充分的原因,已经还不错了!

我:(内心 OS,借你吉言)再想问一下,如果我把这些写出来,会有读者给我点赞、分享、加入在看吗?

面试官:……

结语

群聊系统是社交应用的核心功能之一,每个社交产品几乎都有着群聊系统的身影:包括但不限于 QQ、微信、抖音、小红书等。

上述介绍的技术细节可能只是群聊系统的冰山一角,像常见的抢红包、群内音视频通话这些核心功能也充斥着大量的技术难点。

但正是有了这些功能,才让我们使用的 App 变得更加有趣。而这,可能也是技术和架构的魅力所在吧~

责任编辑:武晓燕 来源: xin猿意码
相关推荐

2023-11-01 18:10:45

架构设计技术

2023-12-14 17:27:28

架构设计数据表

2023-12-29 11:32:27

2023-10-08 22:38:52

2022-12-25 18:58:53

架构RabbitMQ

2024-03-01 18:55:54

内存调试Go 语言

2024-08-28 08:38:51

2021-04-28 08:52:22

高并发架构设高并发系统

2019-08-22 10:54:05

分布式系统架构

2022-05-17 20:37:41

MyPick泛型对象类型

2019-01-28 11:46:53

架构运维技术

2024-04-24 10:38:22

2023-07-05 08:00:52

MetrAuto系统架构

2024-06-21 08:15:25

2023-09-02 21:22:36

Airbnb系统

2013-07-24 10:49:04

架构设计师商业价值

2013-07-15 13:36:29

架构设计

2022-02-17 08:57:18

内存设计进程

2024-03-19 13:51:31

JavaScript插件

2018-11-22 14:09:45

iOS架构组件开发
点赞
收藏

51CTO技术栈公众号