今天我想跟大家分享一下Facebook messenger/ discord/ WhatsApp的设计。我想聊聊这些应用的高层次系统设计。Messenger是一个可以即时发送和接收消息的应用程序。你可以通过手机应用或网站来使用它。
1. 需求功能需求:
- 一对一聊天功能。
- 应支持聊天记录的保存。
- 应能查看在线/离线状态。
- 应支持多设备使用。
非功能性的需求
- 用户应该享有低延迟的实时聊天体验。
- 我们的系统应该具有高度的一致性。他应该在所有设备上看到相同的消息。
额外的要求
- 推送通知。
- 支持最多100人的小群组聊天功能。
2. 粗略估算
如果每个用户每天发送40条消息,那么总消息数每天就是20亿条。
存储估算:假设消息大小为100字节,100B × 20个消息 = 每天2TB
存储5年,即2TB x 365 x 5,大约3.5PB
带宽估算:即每秒传输速度为 20 Mbps。我们需要这个速度既用于上传也用于下载。
3:通信协议
对于基于网页的通信,HTTP/HTTPS 非常合适。它提供加密功能,并且易于实现,与网页浏览器和服务器高度兼容。我们需要使用 keep-alive 来维持持久连接。这是一条单向通道,非常适合发送方。由于 HTTP 是客户端发起的,因此不适合用于服务器发送消息。我们可以考虑一些由服务器发起的协议,如轮询、长轮询和 WebSocket,这些协议可以提供实时通信,而 HTTP 则会引入延迟。在轮询中,客户端会不断向服务器询问是否有消息。大多数时候会是空响应,这会增加带宽和计算成本。在长轮询中,客户端可以保持连接打开直到收到消息或超时。一旦收到消息,客户端将立即发送下一个请求。缺点是发送方和接收方不能连接到同一台服务器。即使是不活跃的用户,也需要定期在超时后重新建立连接。
WebSocket — WebSocket是一种双向通信方式,这意味着与HTTP遵循请求-响应模型不同,WebSocket允许全双工通信。这意味着客户端和服务器可以异步地互相发送消息,从而实现实时交互,无需频繁发起HTTP请求。同时保持持久连接,服务器可以随时向客户端发送更新。因此,无论是发送还是接收,双方都能很好地配合。比如推送通知,WebSocket允许服务器实时向客户端推送更新,从而消除了客户端不断轮询或长轮询服务器以获取新数据的需要。这使得消息、通知和更新可以即时送达连接的客户端,从而提高了整体用户体验。
然而,访问历史消息时,带有分页功能的传统HTTP API(如REST或SOAP)表现得很有效。
4. 数据库架构
消息应用会产生大量的流量,读写的比例是1:1。在实时聊天中,用户也很少会反复阅读相同的消息。
该应用程序具有多种功能,提供了采用混合数据库方案的可能性。对于用户的资料信息,关系型数据库是一个不错的选择,因为它们提供了ACID(原子性、一致性、隔离性、持久性)事务,并支持复杂的查询。关系型数据库提供了强一致性保证,这对于维护用户数据的完整性非常重要。它们还支持强制实施数据约束,例如对用户名或电子邮件地址的唯一性要求。对于消息,需要快速读写大量数据,因此选择类似键值存储的NoSQL数据库会是个不错的选择。
为什么使用键值存储系统?_键值存储系统_设计用于处理大量数据和高写入吞吐量,是可扩展且分布式的,非常适合需要高可扩展性的应用程序。
可能的选项:比如 Cassandra, HBase, DynamoDB
Cassandra 是一个广义列式存储数据库,其基础架构是键值存储模型。数据被组织成列,每条记录都有一个唯一的键来标识,相关的数据则存储在该键下。因此,通过这种方式,我们可以同时利用这些优势。我们还知道,Cassandra 以其高可用性和分区容错性而闻名,同时它还提供了可调整的一致性。我们可以为读取和写入操作配置不同的一致性级别。我们可以通过 QUORUM/ALL 共识来实现这一点。在较高的一致性要求下,Cassandra 确保在确认写入操作之前,数据已复制到多数副本节点,并且在返回查询结果之前,它等待多数副本节点的响应。这以牺牲潜在的延迟增加和网络分区期间的可用性降低为代价,提供了更严格的一致性。
为什么不选择HBase?优点是,一致性好,能水平扩展,容错能力强,读写速度快。不过,部署、配置和维护需要懂Hadoop生态,在需要亚秒级延迟的实时消息场景中表现不佳。
为了生成唯一的消息ID的,让我们用另一个服务来生成唯一ID号。
{
"消息ID": "新消息ID值",
"聊天ID": "新聊天ID值",
"发送者ID": "新发送者ID值",
"内容": "新消息内容",
"创建时间": "2024-05-03T12:00:00.000"
}
5. 高级设计
高层设计的基本消息系统
无状态服务例如登录、注册、用户资料等,都是请求响应服务,如服务发现功能。
有状态的:聊天服务是我们唯一的有状态服务。它是有状态的,因为它必须维持持久的网络连接。即使客户端离线,WebSocket(Web Socket)连接也保持不变,除非出现网络中断。服务发现可以帮助找到正确的聊天服务。
第三方:通知功能,在客户不在时提醒他是否有新消息。
6. 设计深挖
- 服务发现:服务发现主要是为了根据服务器的负载、地理位置、服务器容量等因素,推荐最合适的聊天服务器连接。可以使用 Apache Zookeeper 来实现这一点。它会维护所有可用的聊天服务器,并选择最适合的一个。
- 消息流程: 消息流程如下:
a. 1-1 消息流程
- 用户A与聊天服务器建立持久连接并发送消息。
- 消息经过验证,并从ID生成器获取ID。
- 消息被写入键值数据存储,并被发送到接收者的消息队列,同时发送方收到消息已接收的确认。
- 如果用户B在线,消息将被转发到用户B连接的聊天服务器2。如果用户B离线,则从推送通知(PN)服务器发送推送通知以实现尽力而为的交付(没有传递保证,因为用户可能没有互联网连接)。
- 即便有双消息传递系统,消息也可能从未被客户端接收。因此,所有客户端在重新连接时或定期请求网关服务获取权威聊天记录。
群聊功能
步骤与一对一聊天相同,如果是一个小群组,当发送者发送消息时,消息会被写入所有群成员的接收队列。如果是大群组时,消息只会被发布到群组的队列中一次,所有群成员都会订阅该队列并从中拉取消息。这也减少了在不同地方重复存储相同消息的冗余。
消息同步功能:
在支持多个设备的情况下,,为了解决消息顺序问题,我们可以在每条消息上添加一个 prevMsgID 字段。如果发现不一致,他会检查本地日志并发起历史消息同步。
在线形象:
这些负责管理在线状态,并通过WebSocket连接与客户端进行通信。当客户端与聊天服务器之间的WebSocket连接建立时,用户状态会被设置为在线并记录登录时间。当用户登出时,状态会变为离线。每当服务器断开连接时,我们也需要更新状态,但是有时用户并非离线,而只是出现了短暂的中断,在这种情况下更改状态会降低用户体验,影响不太好。因此,我们需要一种心跳机制来解决这个问题。比如客户端每5秒发送一次心跳信号,如果连续发送了三次心跳后,客户端在接下来的30秒内(这个时间是随便定的,只是为了说明逻辑)既没有发送新的心跳信号,也没有重新连接,那么在线状态就会变成离线。
7. 优化措施:
- 在群聊中使用推送和拉取模型发送消息。
- 引入并行处理:通过在聊天服务器间引入消息队列来搭建松散耦合的系统。
8. 故障容错:跨多个节点和数据中心复制数据以确保冗余和容错性。使用一致哈希来缓解热点键问题。引入负载均衡以确保流量均匀分配,从而避免服务器过载导致的瓶颈。通过配置自动故障转移机制来检测故障并将流量无缝切换到备用组件。在故障期间,优雅降级也有帮助,通过从缓存提供有限的数据,直到服务器恢复。实现断路器来检测和处理外部依赖中的故障。断路器监控外部服务的健康,并在检测到故障时暂停请求。这可以防止级联故障,并允许系统优雅地恢复。实现强大的监控和警报系统,以实时检测异常、性能问题和故障。快速恢复服务并从故障中恢复的自动恢复机制。使用自动化脚本或编排工具自动重启失败的组件、重新配置负载均衡器并执行故障转移。
9. 表现
- 尽可能缓存数据。
- 异步处理:使用像 RabbitMQ 或 Apache Kafka 这样的消息队列系统来解耦组件以实现异步处理。使用发布/订阅机制来通知订阅者有关新事件或数据变化的信息。
- 优化 API 设计:实现分页、过滤和排序机制,以限制每次 API 响应中的数据量并提高查询效率。
10. 可扩展性。 我们通过水平扩展技术来增加服务器的数量。
11. CAP 定理: 根据需求,我选择了一致性和网络分区而不是可用性。
共同学习,写下你的评论
评论加载中...
作者其他优质文章