我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版,欢迎购买。点击进入详情
在这个系统设计面试场景中,我们被要求设计一个类似于 WhatsApp 的消息应用程序。
虽然真正的面试可能会关注应用程序的一个或多个功能,但在本文中,我们将对系统架构进行概述,然后您可以根据需要更深入地探索特定领域。
目录
明确功能需求
澄清非功能性需求
估算:数据数学
高级 API 设计
高层系统设计
结论和当前系统瓶颈
明确功能需求
让我们通过向面试官提出一些问题来缩小范围,因为在一小时内设计整个 WhatsApp 平台是不现实的:
- 主要用例: 该应用程序的主要目的是发送、检查和接收消息,以及阅读和将消息标记为已读。
- 群组:我们不涵盖群组消息,仅涵盖一对一消息。
- 内容类型:我们仅支持文本消息,不支持图像或视频。
澄清非功能性需求
规模:首先,我们来谈谈规模,即系统有多大,可以处理多少条消息。假设每天发送的消息量为 100 亿条,我们的目标是在一年内将这一数字翻一番。
可用性:在可用性方面,我们希望系统高度可用且始终可运行。
延迟:至于系统延迟,我们希望它几乎立即发生,因此大多数 API 请求应在 100 毫秒内完成。
估算:数据数学
每天有 100 亿条消息,我们大约有 100 亿条消息 / 每天 86,400 秒 =每秒 115,740 条消息 (MPS)。一年内翻倍意味着我们应该计划 115,740 * 2 = 231,480 MPS。
假设每条消息 200 字节,则每日存储量为 100 亿条消息 * 200 字节 = 2 兆兆字节 (TB)。每年存储量(含增长)约为 2 TB * 365 天 * 2 = 1.5 PB。
值得注意的是,我们计算的是平均值,但系统需要处理峰值流量,这可能远高于平均 MPS。我们可能需要根据高峰时段进行扩展。
高级 API 设计
我们可能会使用 RESTful API 样式来实现更广泛的兼容性。以下是可能的端点的细分:
- 发送消息(POST /messages):请求正文包含收件人 ID 和消息内容。成功响应 (200) 返回唯一消息标识符。错误代码 (400、500) 处理缺少参数或服务器问题。
- 检查新消息(GET /messages):响应为 200(带有未读消息数组)或 204(如果没有未读消息)。
- 获取特定消息(GET /messages/:messageId):返回特定消息(200)或 404(如果未找到)。
- 将消息标记为已读(PUT 或 PATCH /messages/:messageId):成功响应(200)确认更改,而 404 表示未找到消息。
其他注意事项:我们将集成 WebSockets 以实现实时更新。API 将处理身份验证和初始连接建立。并且可能需要对“检查新消息”端点进行分页。输入验证等安全措施也是必要的。
高层系统设计
移动应用程序:用户的主要界面将是移动应用程序(iOS、Android)。此应用程序负责发送和接收消息、联系人管理和对话。
负载均衡器:为了高效处理传入请求,我们将使用负载均衡器在多台服务器之间分配流量。这提高了应用程序的可靠性。
API 服务器:所有请求都将发送到 API 服务器,该服务器 处理我们之前概述的 RESTful API,管理消息传递逻辑。API 服务器本身可以是无状态的;这样,我们可以随着流量的增长而水平扩展(添加更多服务器)。
WebSocket 连接:类似 WhatsApp 的应用程序严重依赖 WebSocket 进行实时通信。聊天服务器将与移动应用程序保持持久的 WebSocket 连接。当消息到达时,可以立即将其推送到收件人的设备。
消息分发器:接下来,我们将有一个消息分发器服务,该服务的主要目的是将 API 服务器与直接数据库写入分离,这对于处理高写入量尤为重要。
消息队列(例如 Kafka 或 RabbitMQ)非常适合。其工作原理如下:
- API 服务器收到“发送消息” POST 请求。
- 它将消息放在队列上并及时向客户端返回成功/确认。
- 单独的工作进程异步地从队列中读取消息并将消息写入数据库。
数据库(NoSQL):我们同意最终的一致性是可以接受的,这使得 NoSQL 成为高消息量的可扩展选择。
让我们考虑两个强有力的选择:
- Cassandra:宽列存储,以可扩展性、高可用性和写入性能而闻名。如果我们预期写入量较大,且读取模式更简单(主要通过 ID 获取消息),那么这种存储就特别有用。
- DynamoDB: AWS 提供的完全托管的键值和文档数据库。如果我们想要一个维护成本低且易于扩展的数据库解决方案,那么它是非常有利的。
分片和分区:对数据进行分片(水平分区)至关重要,因为没有一个数据库可以满足我们 1.5 PB 的存储需求。
但是我们如何对这些数据进行分片和分区,以及这些 API 服务器如何知道从哪里请求这些数据?
我们可以根据进行分区userId
。涉及用户的所有消息将驻留在同一个分片/分区中。我们的 API 服务器有两种潜在的方式来定位数据:
- 一致性哈希环:可以根据分区键确定数据位置,从而允许 API 服务器将请求直接路由到正确的数据库分片。
- 元数据服务:单独的服务保存分区键到分片位置的映射。API 服务器首先查询此服务,然后进行数据库调用。
结论和当前系统瓶颈
这概述了类似 WhatsApp 应用程序的主要架构。现在,让我们检查一下当前系统中的潜在瓶颈以及需要改进的领域:
- 数据库写入:高写入量是潜在的瓶颈。分片、消息队列和优化的数据库选择至关重要。
- 端到端加密: WhatsApp 模型非常强调安全性。实施端到端加密将是一个重要的讨论。
- 群聊:此功能给消息路由和存储带来了额外的复杂性。
- 媒体处理:我们可以实现一个处理图像和视频上传的系统,在这里使用压缩并为缩略图提供多种存储大小。
职场攻略与副业指南,成就你的IT人生。快扫描下面二维码关注吧!