写点什么

即时通讯系统架构设计 - 如何设计一款 WhatsApp

用户头像
OpenIM
关注
发布于: 3 小时前

本文译自 tech takshila 经 OpenIM 技术人员整理修订后发布。

写在前面

Open-IM 是由前微信技术专家打造的开源的即时通讯组件。Open-IM 包括 IM 服务端和客户端 SDK,实现了高性能、轻量级、易扩展等重要特性。开发者通过集成 Open-IM 组件,并私有化部署服务端,可以将即时通讯、实时网络能力快速集成到自身应用中,并确保业务数据的安全性和私密性。

了解更多原创文章:

【OpenIM原创】开源OpenIM:轻量、高效、实时、可靠、低成本的消息模型


【OpenIM原创】C/C++调用golang函数,golang回调C/C++函数


【OpenIM原创】简单轻松入门 一文讲解WebRTC实现1对1音视频通信原理


【OpenIM扩展】OpenIM服务发现和负载均衡golang插件:gRPC接入etcdv3


【开源OpenIM】高性能、可伸缩、易扩展的即时通讯架构

如果您有兴趣可以在文章结尾了解到更多关于我们的信息,期待着与您的交流合作。

免责声明:这是设计 WhatsApp 等即时通讯系统的一种方法。然而,我们不能保证 WhatsApp 是以这种方式设计的。这是我们设计类似系统的想法。

问题陈述

设计一个即时通讯平台,如 WhatsApp 或 Signal,用户可以利用该平台相互发送消息。应用程序的一个重要方面是聊天信息不会永久存储在应用程序中。


有趣的事实: 一些聊天信使(如 FB Messenger)存储聊天信息,除非用户明确删除它。然而,像 WhatsApp 这样的即时通讯工具不会将消息永久保存在服务器上。

系统需求

instant messenger 应用程序应满足以下要求。


  • 它应该能够支持用户之间的一对一对话。

  • 它应该能够向其他用户显示发送/交付/读取确认。

  • 它应该能够提供关于用户上次活动时间的信息。

  • 它应该允许用户共享图像。

  • 它应该能够向其他用户发送推送通知。

容量规划

我们需要建立一个高度可扩展的平台,能够支持 WhatsApp 规模的流量。此外,在进行容量规划时,我们需要确保考虑高峰流量的最坏情况。下面列出了我们可以用来估算应用程序容量的一些数字(如 WhatsApp)。


-每月应用程序上的用户数:10 亿


-高峰流量时每秒的活动用户数:650000


-高峰流量时每秒的邮件数:4000 万


整个应用程序将由几个微服务组成,每个微服务执行特定的任务。处理聊天信息流量的数据平面(网站管理员包括到分布式系统章节的链接)(我们称之为 chat microservice)中所需的服务器数量可以使用以下等式进行估计。


𝑠𝑒𝑟𝑣𝑒𝑟𝑠 𝑖𝑛 𝑐ℎ𝑎𝑡 𝑚𝑖𝑐𝑟𝑜𝑠𝑒𝑟𝑣𝑖𝑐𝑒= (#聊天信息/秒)__∗ 延迟)/#每台服务器的并发连接


假设每台服务器的并发连接数为 100K,发送消息的延迟为 20 毫秒。在这种情况下,聊天服务器机队中所需的服务器的估计数量(使用上述等式)将为 8(即 4000 万*20 毫秒/100K)。在标准实践中,建议再添加几个服务器,以处理这些服务器可能的故障。在接下来的一节中,我们将看到这些聊天服务器对总体基础设施成本的影响。


有趣的事实:在本次演讲中,Rick Reed(软件工程师 @WhatsApp)谈到了优化基于 Erlang 的服务器应用程序,以及调整 FreeBSD 内核以支持每台服务器数百万个并发连接。这在很大程度上帮助了他们保持尽可能小的服务器占用空间。

高级设计

此即时通讯应用程序所需的功能可以使用两种微服务建模:聊天服务和临时服务。聊天服务将为活跃用户发送的在线聊天信息流量提供服务。该服务将检查接收消息的用户是否在线。如果用户在线,则消息将立即转发给该用户。否则,消息将由临时服务处理。此服务将负责维护发送给脱机用户的所有消息(文本或图像)。数据将暂时存储在临时存储器中,直到脱机用户恢复联机。我们将在后面的一节中提供有关各个组件的更多详细信息。



有趣的事实:WhatsApp 实际上使用了一种非常类似的方法,正如同一位 WhatsApp 工程师(Rick Reed)在另一次演讲中所讨论的。

API 设计

我们可以公开一个 REST 端点来与聊天服务交互。下面介绍了用于发送消息的 API 端点的定义。


sendMessage(String fromUser、String toUser、ClientMetaData ClientMetaData、String message)


请求:


_fromUser:\u 发送请求的用户 ID


_toUser:\u 向其发送请求的用户 ID


clientMetaData:用于存储客户端信息(如设备详细信息、位置等)的元数据。


消息:作为通信的一部分发送的消息。

数据模型

我们将存储详细信息,例如用户连接到的服务器以及用户上次活动的时间。我们可以使用基于文档的数据库(如 MongoDB)来存储用户信息,如用户的最后活动时间(也称为心跳时间)。数据模型将类似于下表。



表 1:数据模型-用户信息

组件图

在本节中,我们将讨论在一对一通信中发送消息的两种不同场景。之后,我们将讨论需要支持的其他功能,例如推送通知和用户活动状态。最后,我们将研究进行优化和处理故障场景的不同机制。

一对一通信

这里,我们将讨论与向另一个用户发送消息相关的两种不同场景。第一种情况涉及向在线用户发送文本消息。在第二个场景中,我们描述了向脱机用户发送图像所涉及的操作序列。

场景 1:向在线用户发送文本


下面介绍了向在线用户发送文本消息的序列图中每个步骤的详细信息。


  • 步骤 1:Alice 向 Bob 发送一条消息,该消息被定向到 Alice 连接的聊天服务器。

  • 步骤 2:Alice 从其连接的聊天服务器(即聊天服务器 A)获得确认,消息标记为 Alice 端发送。

  • 步骤 3:聊天服务器向数据存储器发出请求,以获取有关 Bob 连接的聊天服务器的信息。

  • 步骤 4:聊天室存储返回 Bob 连接到聊天室服务器的信息。

  • 步骤 5:聊天室服务器 A 将消息转发给聊天室服务器 B。

  • 步骤 6:使用推送机制将消息传递给 Bob。

  • 步骤 7:Bob 将 ACK 发送回聊天服务器。

  • 步骤 8:ACK 被转发到 Alice 连接的聊天服务器 A。

  • 步骤 9:ACK 被传递给 Alice,并被标记为已传递。

  • 步骤 10:当 Bob 阅读消息时(假设 15 分钟后),另一个 ACK 被发送到 Chat_Server_B。

  • 步骤 11:聊天室服务器请求获取 Alice 连接的服务器。

  • 步骤 12:聊天室存储返回 Alice 连接到聊天室服务器的信息。

  • 步骤 13:聊天室服务器 B 将读取确认转发给聊天室服务器 A。

  • 步骤 14:将 ACK 转发给 Alice,将请求标记为已读。

场景 2:向脱机用户发送媒体文件


下面介绍了序列图中向脱机用户发送图像的每个步骤的详细信息。


  • 步骤 1:Alice 向 Bob 发送一个图像,该图像被转发到聊天服务器 A,Alice 与之连接的服务器。

  • 步骤 2:聊天服务器将图像上传到文件服务器,文件存储在目录结构中

  • 步骤 3:文件服务器将上传文件的图像 url 返回给聊天室服务器。

  • 步骤 4:图像 url 返回给 Alice,用于在 Alice 的设备上呈现图像。图像被标记为在 Alice 端发送。

  • 步骤 5:聊天服务器向 Bob 连接的服务器发出请求。

  • 步骤 6:聊天室存储返回 Bob 离线的信息。

  • 步骤 7:聊天服务器将包含图像 url 的消息转发给临时服务器

  • 步骤 8:临时服务器将包含图像 url 的消息存储在临时存储器中。

  • 第九步:Bob 上线并与 Chat_Server_B 进行心跳(网站管理员包括链接)。

  • 步骤 10:聊天服务器从临时服务器获取 Bob 的临时消息。

  • 步骤 11:聊天服务器将临时消息转发给 Bob。

  • 步骤 12:Bob 从文件服务器获取图像。此时,映像将被传送到 Bob 的设备,所有对瞬态消息的引用都将从系统中删除。

  • 步骤 13:Bob 的设备向聊天服务器发送 Alice 图像的确认

  • 步骤 14:获取 Alice 连接到的服务器的信息;i、 e.聊天室服务器

  • 步骤 15:将确认转发至聊天室服务器

  • 步骤 16:将 ACK 传递给 Alice,将消息标记为已传递。

瞬态数据存储

我们可以使用基于 FIFO 的策略实现基于队列的机制来存储和检索瞬态消息。为此,我们可以使用现有的基于云的技术,如 AmazonSQS 或 WindowsAzure 队列服务。我们可以使用这些队列来存储发送给脱机用户的临时消息。一旦消息传递给脱机用户,所有对这些临时消息的引用都将从系统中删除。

推送通知

使用推送技术向用户传递消息有两种方法:客户端推送或服务器推送。如果我们沿着客户机请求的路线走下去,我们可以在长轮询和短轮询之间做出决定。另一方面,有两种方法可以实现服务器推送方法:WebSocket 和服务器发送事件(SSE)。Websockets 已经成为聊天应用程序事实上的通信协议。我们在下面的部分中提供了有关它的更多详细信息。


使用轮询技术,客户机定期向服务器请求新数据。选择轮询技术的权衡决定可以使用下面提到的数据点。


  • 短轮询:(例如,基于 AJAX 的计时器)

  • 优点: 如果请求之间的时间间隔较长,则更简单且不太消耗服务器

  • 缺点:不适用于需要以最小延迟通知服务器事件的情况

  • 长轮询:(例如,基于 XHR 的 Comet)

  • 优点: 服务器事件的通知不会延迟

  • 缺点: 更复杂,消耗更多服务器资源


将服务器消息推送到客户端的方法主要有两种类型。第一种是 WebSocket,它是一种通信协议。它通过单个 TCP 连接提供双工通信信道。由于双向通信,它非常适合聊天应用程序等场景。另一种称为服务器发送事件(SSE),它允许服务器在建立初始客户机-服务器连接后异步向客户机发送“新数据”。SSE 更适合发布者-订阅者模型,如实时流式股票价格;twitter 提供更新和浏览器通知。

用户活动状态

用户最后一次处于活动状态是可以在即时消息中找到的标准功能。我们在上面的表 1 中展示了存储相关信息的数据模型



在图 4 中,我们展示了使用 WebSocket 在客户端和服务器之间维护连接的机制。一旦在客户端和服务器之间建立了初始连接,通信就会切换到双向二进制协议。客户端和服务器之间的连接使用心跳保持活动状态。我们将上次从用户接收心跳的时间存储在数据库中。然后可以查询数据存储以获取用户上次活动的时间。

优化

我们可以使用下面列出的参数来建议系统中的优化。在本文中,我们提供了建议系统优化所应遵循的方法的详细信息。


  • 延迟:我们可以使用分布式缓存(包括指向分布式缓存的链接),例如 Redis,在内存中缓存用户活动状态及其最近聊天的信息。这可能有助于减少应用程序的总体延迟,并提供更好的客户体验。甚至一些数据库解决方案也提供了内存缓存解决方案,如 Amazon DynamoDB Accelerator。

  • 基础设施成本: 从系统中可以明显看出,聊天服务器对基础设施成本的重要贡献。如果不控制聊天服务器的足迹,那么聊天服务器产生的成本可能会迅速增加。一种方法是增加每个主机的连接数。这将大大减少维护服务所需的服务器数量。我们可以通过调整服务器配置和选择合适的技术来完成这项任务。例如,WhatsApp 的工程师通过优化基于 Erlang 的服务器应用程序和调优 FreeBSD 内核,能够在每台主机上实现数百万个连接

  • 可用性: 我们可以维护临时消息的多个副本,这样即使其中一个副本中的消息丢失,也可以从另一个副本中检索。这意味着要维护这些临时消息的副本。客户端将负责从两个队列获取消息,并将它们合并。我们将在下一节中讨论更多内容。


解决瓶颈


系统中更容易发生故障的主要瓶颈是聊天服务器和临时存储解决方案。在下面的部分中,我们推荐了一些处理此类故障的方法。


  • 聊天服务器故障:系统中的聊天服务器将保持与用户的连接。有两种处理聊天服务器故障的方法。一种方法是将这些 TCP 连接传输到另一台服务器;然而,这种故障转移的实现并非微不足道。第二个相对简单的方法是让用户客户端在连接丢失的情况下自动启动连接。用户连接到的服务器的信息需要在数据库中更新,这与我们采取的方法无关。例如,在图 5 中,我们展示了处理此故障场景的示例。我们可以看到 User1 连接到 Server1,当该服务器关闭时,将重新建立与另一台服务器(即 Server2)的连接,并在数据库中更新此信息。



  • 瞬态存储故障:瞬态存储是另一个容易发生故障的组件,可能会导致脱机用户在传输过程中丢失消息。我们可以复制每个用户的临时存储,以防止在他们脱机时发送给他们的消息丢失。当用户重新联机时,将查询并合并用户临时存储的原始实例和副本实例。在图 6 中,我们展示了一种处理瞬时存储故障的机制,该故障在用户重新联机时启动。


监测

我们希望确保我们的服务能够以高可用性和低延迟满足用户需求。我们可以为这些指标定义服务级别协议(SLA),并创建中度和重度监控器,当违反这些 SLA 时,这些监控器会触发警报。对于此应用程序,我们可以为 sendMessage API 定义以下 SLA。


  • 可用性 SLA:p99.999

  • 延迟 SLA:p99.99,共 5 毫秒


可用性 SLA 意味着,如果 1000 个请求中有 1 个以上失败,监控器将触发警报。同样,延迟 SLA 意味着,如果服务器对其接收的 100 个请求中超过 1 个请求的响应时间超过 5 毫秒,则会触发警报。


此外,我们可以在不同的错误场景中设置故障警报。当聊天服务器无法从临时存储的所有副本中为用户获取临时消息时,可能会出现这种情况。这映射到上面图 3 所示的步骤 #10,其中聊天服务器 #B 请求临时服务器在 Bob 脱机时获取发送给 Bob 的消息。让我们假设我们在临时存储器中维护 Bob 消息的两个副本,以使系统更加健壮。但是,由于临时存储的临时问题,临时服务器无法从两个副本检索消息。这是一个需要调试的错误场景,因此需要足够的监控警报。

扩展要求

我们可以扩展系统以支持群组聊天,通过群组聊天我们可以将消息传递给多个用户。我们可以创建一个数据模型来存储组数据实体,该实体将由 GroupChatID 标识,并将用于维护属于该组的人员列表。上述系统可扩展以支持向在线和离线用户发送消息的场景。我们可以构建一个组件,该组件将负责确保根据组中所有用户的活动状态将消息传递给他们。


作为此问题的扩展,可以涵盖的另一个方面是安全性,特别是端到端加密,其中只有通信用户可以读取消息。每个用户都有一个公钥,该公钥与该用户正在通信的所有其他用户共享。例如,两个用户 Alice 和 Bob 正在相互通信。Alice 拥有 Bob 的公钥,反之亦然;但是,它们的私钥不共享。当 Alice 向 Bob 发送消息时,该消息使用 Bob 的公钥加密并通过网络发送。服务器将加密的消息定向给 Bob,Bob 使用私钥解密消息。这样,服务器只能访问加密的消息,只有 Alice 和 Bob 才能读取他们交换的实际消息。



OpenIM github 开源地址:


https://github.com/OpenIMSDK/Open-IM-Server


OpenIM 官网 : https://www.rentsoft.cn


OpenIM 官方论坛: https://forum.rentsoft.cn/


我们致力于通过开源模式,为全球企业/开发者提供简单、易用、高效的 IM 服务和实时音视频通讯能力,帮助开发者降低项目的开发成本,并让开发者掌控业务的核心数据。


IM 作为核心业务数据,安全的重要性毋庸置疑,OpenIM 开源以及私有化部署让企业能更放心使用。


如今 IM 云服务商收费高企,如何让企业低成本、安全、可靠接入 IM 服务,是 OpenIM 的历史使命,也是我们前进的方向。


如您有技术上面的高见请到我们的论坛联系沟通,用户也可与我们的技术人员谈讨使用方面的难题以及见解

用户头像

OpenIM

关注

还未添加个人签名 2021.08.30 加入

还未添加个人简介

评论

发布
暂无评论
即时通讯系统架构设计-如何设计一款WhatsApp