ONE SENTENCE SUMMARY:
本文探讨了构建类似WhatsApp的可扩展聊天应用程序的高层设计,包括需求收集、容量估算和关键组件。
MAIN POINTS:
- 聊天应用需支持实时消息、在线状态和群组对话等功能。
- 采用WebSockets实现低延迟和高效的双向通信。
- 设计数据库以处理用户、消息和群组数据的高效存储。
TAKEAWAYS:
- 实时聊天应用需具备高可用性和可扩展性。
- 消息队列可提高消息处理效率和系统性能。
- 采用CDN可减少多媒体内容的延迟和带宽使用。
几乎每个人都使用 聊天应用程序 来发送消息并保持联系。
拥有超过 25亿 活跃用户 和每天超过 1000亿条消息 的交换量, WhatsApp 是全球最受欢迎的消息应用程序。
但要构建这样一个能够实时连接全球数十亿人的平台需要什么呢?
在本文中,我们将深入探讨构建这样一个 可扩展 的 聊天应用程序 的高层设计。
📣 更好地设计、开发和管理分布式软件(赞助)

Multiplayer 自动记录您的系统,从高层逻辑架构到各个组件、API、依赖关系和环境。非常适合希望加快工作流程并整合技术资产的团队。
1. 需求收集
功能需求
-
支持用户之间的 1:1 实时消息传递 。
-
显示用户的 在线/离线 状态和 最后一次在线时间 。
-
显示 消息传递状态 (已发送、已送达、已读)。
-
允许用户分享 图片 、 视频 和 音频片段 。
-
支持最多100人的 群组对话 。
-
如果接收者离线,发送 推送通知 以提醒新消息。
-
存储 和 检索 每个用户的聊天记录。
非功能需求
-
可扩展性 :处理数百万并发用户。
-
高可用性 :确保最小的停机时间和对服务器故障的弹性。
-
低延迟 :以最小的延迟实时传递消息。
-
可靠性 :确保消息不丢失。
2. 容量估算
让我们假设我们的聊天应用程序具有以下流量特征:
总用户数 :假设有10亿注册用户。
日活跃用户(DAU) :大约5亿用户每天活跃使用该应用程序。
峰值并发连接 :大约5000万用户在高峰时段连接。
平均每日消息数 :如果每个活跃用户每天平均发送10条消息,这将导致每天50亿条消息。
存储需求(针对消息):
假设每条消息大约为1 KB。
-
每日存储 :1 KB × 50亿条消息 = 5 TB
-
年度存储 :365 × 5 TB ≈ 1.8 PB(拍字节)
带宽估算(针对实时通信):
在高峰时段有1000万用户同时连接。
-
每个连接的平均带宽 :假设每个连接平均为10 KB/s,我们需要总共100 GB/s的带宽来支持高峰时段的实时消息传递。
3. 高层设计

聊天服务器
聊天服务器 管理大量并发连接,促进实时通信,并确保消息在用户之间高效传递,延迟最小。
为了支持无缝的双向消息传递,像 WebSockets 这样的协议是理想的选择,因为它专为客户端和服务器之间的原生双向通信而设计。(我们将在后面更详细地探讨这一点。)
负载均衡器
负载均衡器 有效地将来自用户的流量分配到多个聊天服务器实例和用户服务实例,如媒体服务。
以下是用户通过负载均衡器与聊天服务器建立连接的过程:
-
初始连接 :客户端发起一个HTTP(S)请求以建立WebSocket连接。该请求通过负载均衡器传递,负载均衡器根据用户的位置和所用的负载均衡算法(例如,轮询、最少连接)将其路由到适当的聊天服务器。
-
连接升级 :一旦请求到达选定的服务器,连接就会从HTTP升级到WebSocket,从而通过负载均衡器在客户端和选定的聊天服务器之间建立持久的双向WebSocket链接。
-
会话持久性 :为了确保客户端始终连接到同一聊天服务器,负载均衡器使用 粘性 会话。这可以通过 IP哈希 实现,负载均衡器根据用户IP地址的哈希值始终将其路由到同一服务器。
使用服务发现的替代方案
另一种方法是使用服务发现,使用户可以直接连接到聊天服务器。
在这种情况下,用户首先连接到服务发现层以识别他们应该连接的聊天服务器,然后直接与该服务器建立WebSocket连接。
用户连接缓存
用户连接缓存
是一个快速的内存缓存(例如,Redis),用于存储每个用户的活动连接详细信息,如他们连接的聊天服务器和
last_active
时间戳。

客户端定期向其连接的服务器发送心跳信号,每次心跳都会更新缓存中的用户
last_active
时间戳。
这种设置可以有效支持
在线/离线
状态和
最后上线
功能。
如果当前时间与
last_active
时间戳之间的差异在定义的阈值内(例如,3秒),则用户显示为在线;否则,他们被标记为离线。
通知服务
通知服务 负责向用户发送实时通知,尤其是在他们离线或未积极使用应用程序时。
当用户离线时,聊天服务器将消息转发给通知服务。
为了提高效率,聊天服务器可以将此消息发送到消息队列,而不是直接与通知服务交互并等待响应。
通知服务与外部推送通知提供商集成,如 Firebase Cloud Messaging (FCM) 和 Apple Push Notification Service (APNS) ,以将消息作为推送通知发送给离线用户。
消息队列
消息队列 是一个分布式的高吞吐量队列(例如,Kafka,RabbitMQ),在消息被消息存储服务消费之前暂时存储消息。
通过充当中介,消息队列将消息存储与聊天服务器的实时消息处理解耦,降低了延迟并增强了应用程序的可扩展性。
消息存储服务
消息存储服务 负责可靠存储、快速检索和高效归档聊天消息。
它从 消息队列 中消费传入的消息,并将其持久化到 消息数据库 中,以实现高效的存储和检索。
消息数据库
消息数据库 以可靠和高效的方式存储所有聊天消息,确保用户可以访问过去的消息。
该数据库设计用于 高写入 吞吐量和高效检索(例如, Cassandra ),以处理实时聊天应用程序中的大量消息。
群组服务
群组服务 负责处理所有与群组相关的功能,包括创建群组、更新群组详情和管理群组成员。
当需要将消息发送到群组对话时,聊天服务器会查询群组服务以获取当前的群组成员列表。
群组数据库
群组数据库 存储和检索与群组聊天相关的所有数据,包括群组ID、成员列表、管理员角色和群组设置。
媒体服务
媒体服务 负责上传和管理多媒体内容,如图像、视频和音频文件。
它将媒体文件安全地存储在一个blob存储系统中,同时在一个单独的数据库中维护元数据,如文件类型、大小和上传时间戳,以便于访问和组织。
通过将媒体存储从主聊天服务器中卸载,媒体服务减少了聊天服务器的带宽使用,并提高了整体应用性能。
Blob存储
Blob存储 是聊天应用程序多媒体内容的存储后端,包括图像、视频、音频文件和文档。
它被设计用于处理大量的媒体内容,同时确保快速、安全和可靠的访问。
媒体存储通常利用基于云的对象存储解决方案(如Amazon S3、Google Cloud Storage或Azure Blob Storage),提供高耐久性、可扩展性和成本效益。
CDN
为了减少上传或下载多媒体内容时的延迟,文件通过 内容分发网络(CDN) 分发到地理位置更接近用户的地方。
当用户分享多媒体文件时,客户端应用程序直接将其上传到CDN,存储在接近接收者的位置。
客户端将文件的URL作为消息的一部分发送到聊天服务器,而不是发送文件本身,允许其他用户快速高效地从最近的CDN位置下载和访问内容。
一旦文件上传到CDN,媒体服务会检索它们并将其存储在blob存储中以进行长期存储。
这种方法减少了聊天服务器的负载,最小化了延迟,并显著提高了用户的媒体传输速度。
4. 数据库设计
对于聊天应用程序,数据库需要处理核心实体,如 用户 、 消息 和 群组 。
以下是支持可扩展和高效聊天应用程序的数据库设计的细分。

为了存储用户、群组和会话数据,我们可以使用像PostgreSQL这样的SQL数据库。
对于消息数据,建议使用像Cassandra这样的NoSQL数据库,因为它具有高写入吞吐量。
对于媒体文件,像AWS S3这样的对象存储提供了可扩展和安全的存储。
5. 深入了解关键组件
5.1 为什么选择WebSockets而不是HTTP?
为了理解为什么WebSockets是实时消息传递的理想选择,让我们来看看其他潜在解决方案及其局限性:
轮询
在轮询中,客户端定期向服务器发送HTTP请求以检查新消息。
缺点 :轮询可能会消耗大量资源,尤其是在高频率轮询时。由于服务器大多数时候响应“没有新消息”,这种方法会增加大量开销并浪费服务器资源。
长轮询
在长轮询中,客户端与服务器保持一个开放连接,直到有新消息可用或发生超时。当服务器有新数据时,它会响应,客户端立即重新建立连接,重新开始这个过程。
虽然这减少了像标准轮询中那样的重复请求需求,但长轮询有几个限制:
-
连接开销 :每次消息交换都需要重新建立连接,产生显著的开销,并对服务器和网络资源造成沉重负担。
-
资源消耗 :服务器必须维护许多开放连接,即使没有活跃的数据交换,也会消耗内存和容量。
-
延迟问题 :如果超时时间长,消息可能会被延迟。如果短,频繁的重置会增加与标准轮询相同的开销。
总体而言,长轮询的连接和资源需求使其不太适合实时聊天应用程序。
WebSockets
另一方面,WebSockets消除了重复的HTTP握手、头信息和响应的需求,减少了开销并提高了性能。
客户端和服务器建立一次连接,该连接在整个聊天会话中保持开放,支持无缝的数据传输。

这种持久连接使得 WebSocket 成为实时通信的理想选择,因为客户端和服务器需要频繁且及时地交换数据。
5.2 实时消息传递
建立连接
当用户打开聊天应用时,它会与其中一个聊天服务器建立 WebSocket 连接。这个持久连接在整个聊天会话中保持打开状态,使客户端和服务器之间能够进行实时通信,而无需重复的 HTTP 请求。
发送消息

一旦连接建立,当用户 A 向用户 B 发送消息时,消息通过用户 A 的 WebSocket 连接传输到管理该连接的服务器(服务器 A)。
服务器 A 然后查找 用户连接缓存 ,以确定用户 B 是否在线,如果在线,当前哪个服务器持有用户 B 的连接。
-
如果用户 B 在线 :服务器 A 将消息转发给服务器 B,服务器 B 通过其开放的 WebSocket 连接将消息传递给用户 B。
-
如果用户 B 离线 :服务器 A 将消息发送到 通知服务 ,触发推送通知以通知用户 B 有新消息。
5.3 消息传递指示器
WebSockets 支持消息的实时状态更新(例如,“消息已发送”,“消息已送达”,“消息已读”),为用户提供即时的消息状态反馈。
消息已发送
当用户 A 发送消息时,它通过其 WebSocket 连接传输到处理其连接的服务器(服务器 A)。
-
服务器 A 接收到消息,将其推送到消息队列进行存储,并向用户 A 发送确认。
-
收到确认后,用户 A 的应用程序将消息状态更新为“已发送”。
如果用户 A 在尝试发送消息时离线,消息将不会发送,直到他们重新上线。消息在用户 A 的设备上保持待定状态,直到重新连接并成功将消息发送到服务器 A。
消息已送达
一旦用户 B 收到消息,它会向服务器 B 发送确认。
-
服务器 B 向服务器 A 发送送达确认。
-
服务器 A 将消息状态“已送达”发送到消息队列进行永久存储,然后将此更新转发给用户 A 的应用程序,显示消息为“已送达”。
如果用户 B 离线,服务器 A 将不会收到来自服务器 B 的送达确认,因此对于用户 A 来说,消息将保持在“已发送”状态,直到用户 B 重新连接。
当用户 B 上线时,客户端应用程序将更新发送到服务器 B,此时它会向服务器 A 发送“已送达”确认。用户 A 的应用程序随后更新以反映“已送达”状态。
消息已读
当用户 B 打开聊天窗口并查看消息时,其应用程序会向服务器 B 发送“已读”确认。
-
服务器 B 记录此事件到消息队列,并将“已读”状态转发给服务器 A。
-
服务器 A 将此更新推送到用户 A 的设备,使用户 A 的应用程序显示消息为“已读”。
如果用户 B 离线,他们无法查看消息,因此不会触发“已读”状态。当用户 B 重新连接并打开聊天时,其应用程序将向服务器 B 发送“已读”确认。
服务器B在消息队列中记录“已读”状态,并将其转发给服务器A。用户A的应用程序随后接收到此更新,将消息标记为“已读”。
5.4 群组消息

-
当用户A在群聊中发送消息时,消息通过用户A的WebSocket连接传输到管理该连接的服务器(服务器A)。
-
服务器A 查询 群组服务 以获取群组中所有活跃成员的列表。
-
服务器A 检查 用户连接缓存 以确定哪些群组成员当前在线以及他们连接的具体服务器。
-
对于每个在线成员 :
-
如果成员连接到 服务器A ,则直接通过现有的WebSocket连接传递消息。
-
如果成员连接到不同的服务器(例如, 服务器B ), 服务器A 将消息转发给 服务器B ,然后由服务器B通过其WebSocket连接将消息传递给用户。
-
-
对于离线的群组成员, 服务器A 将消息发送到 通知服务 。通知服务会为每个离线群组成员触发推送通知,提醒他们有新消息。
在群聊中,每条发送的消息都必须分发(或“扇出”)给每个群组成员。
随着群组规模的增加,扇出工作量也会增加。例如,在一个有500名成员的群组中,每条消息需要500次单独的消息传递,这可能会迅速使服务器不堪重负。
因此,大多数聊天应用程序会限制群组的成员数量(WhatsApp目前的限制是1024人)。
消息持久化 :
为了确保所有群组成员都能访问消息历史记录, 服务器A 将消息推送到 消息队列 进行存储。
消息存储服务 从队列中消费消息并将其存储在 消息数据库 中,供群组成员稍后检索。
确认和状态更新
-
当每个在线群组成员收到消息时,他们各自的服务器会向 服务器A 发送传递确认(或在需要时直接更新 消息数据库 中的状态)。
-
一旦用户打开并查看消息,已读确认也会类似地传播回 服务器A 并存储以供将来参考,使用户A可以看到哪些群组成员已收到并阅读了消息。
5.5 如何生成消息ID?
为了高效地检索最近的消息,我们可以使用 基于时间的消息ID。
基于时间的消息ID通常将时间戳与每条消息的唯一标识符结合在一起。
这使我们能够按时间戳排序消息,在特定时间范围内检索消息,并支持分页(“加载更多”),即在不重新排序数据集的情况下检索特定时间戳之前或之后的消息。
常见的格式可能包括:
[毫秒级时间戳][唯一序列/随机组件]
-
时间戳(以毫秒为单位) :ID的第一部分是消息创建时的时间戳。这允许消息按其创建时间按时间顺序排序。
-
唯一序列或随机化组件 :附加一个唯一组件(如随机或递增序列)确保每个消息ID都是唯一的,即使在同一毫秒内发送了多条消息。
例如,发送于
2024-11-05 12:34:56.789
的消息,序列号为
001
,其ID可能为:
20241105123456789001
。
6. 解决关键问题和瓶颈
6.1 聊天服务器故障
在聊天服务器故障的情况下,所有连接到该服务器的客户端将失去连接。
为了恢复,客户端会自动尝试重新连接,这次与另一个可用服务器建立新连接。
负载均衡器通过定期健康检查持续监控每个聊天服务器的健康状况。
如果某个服务器宕机,负载均衡器会立即停止将流量导向该服务器,确保新连接仅路由到健康的服务器。
6.2 分片
为了支持水平扩展和高效的数据访问,我们可以在不同的数据类型上实现分片:
-
用户数据分片 :根据
user_id
分片用户数据。这将允许我们将用户记录分布在多个服务器上,并在用户基数增长时实现扩展。 -
消息数据分区 :根据
message_id
分区消息,使用基于时间戳的message_id
以实现高效的基于时间的搜索。此结构允许快速访问最近的消息,并根据时间戳定位较旧的消息。
6.3 优化存储成本
随着大量消息和多媒体内容的增加,优化存储成本至关重要。
以下是一些有效的策略:
-
压缩多媒体文件 :压缩大文件(例如,图像、视频)可以减少存储需求并显著降低成本。
-
归档旧消息 :大多数用户只访问最近的消息,这些消息可以在他们的设备上本地缓存。旧消息可以移至低成本的冷存储(例如,Amazon Glacier),在需要时仍可访问,从而降低费用。
-
去重文件 :通过实现去重,避免存储相同文件的多个副本,当相同的媒体在多个用户或群组中共享时,可以节省大量空间。
-
高效的元数据存储 :将元数据(例如,文件类型、大小、时间戳)与媒体本身分开存储,以减少主存储的负载并使搜索更快、更高效。
非常感谢您的阅读。
如果您觉得有价值,请点赞❤️并考虑订阅以获取每周更多此类内容。
如果您有任何问题或建议,请留言。
这篇文章是公开的,欢迎分享。
订阅以接收每周的新文章。
查看我的 YouTube频道 以获取更深入的内容。
查看我的 GitHub仓库 以获取免费的面试准备资源。
希望您有美好的一天!
再见,
Ashish
文章来源:Design a Chat Application like WhatsApp - System Design Interview
关键问题与行动计划
关键问题 1: 如何评估和识别新兴聊天应用市场中的潜在投资机会?
行动计划:
- 市场分析:研究团队将对当前聊天应用市场进行全面分析,识别主要竞争者、市场份额、用户增长趋势及用户需求变化,特别关注新兴市场和未被充分服务的用户群体。
- 用户调研:数据团队将通过社交媒体、用户评论和在线调查收集用户对现有聊天应用的反馈,分析用户对功能、隐私、安全性和用户体验的需求,以识别潜在的市场空白和投资机会。
关键问题 2: 如何利用技术创新提升聊天应用的用户体验和安全性?
行动计划:
- 技术趋势研究:研究团队将深入分析当前聊天应用中使用的技术(如加密技术、AI驱动的聊天机器人、实时翻译等),评估这些技术如何提升用户体验和安全性,并识别可以投资的初创公司或技术。
- 原型开发:数据团队将与技术团队合作,开发一个基于最新技术的聊天应用原型,测试其在用户体验和安全性方面的表现,以便为潜在投资提供实证支持。
关键问题 3: 如何评估聊天应用的可扩展性和长期盈利能力?
行动计划:
- 财务模型构建:研究团队将建立一个财务模型,评估不同规模的聊天应用在用户增长、运营成本和收入来源(如广告、订阅、增值服务等)方面的可行性和盈利能力。
- 案例研究分析:数据团队将收集并分析成功聊天应用的案例研究,评估其商业模式、市场策略和用户留存率,以提炼出可复制的成功因素,为未来投资决策提供依据。
请告诉我们你对此篇总结的改进建议,如存在内容不相关、低质、重复或评分不准确,我们会对其进行分析修正