CHAPTER 12: 《DESIGN A CHAT SYSTEM》 第12章 《设计一个聊天系统》

发布时间:2024年01月20日

在本章中,我们将探讨聊天系统的设计。几乎每个人都使用聊天应用进程。图 12-1 显示了市场上一些最受欢迎的应用。
在这里插入图片描述
聊天应用进程为不同的人执行不同的功能。钉子非常重要降低具体要求。例如,您不想设计一个专注于当面试官想要一对一聊天时进行群聊。探索很重要功能要求。

第 1 步 - 了解问题并确定设计范围

就聊天应用进程的设计类型达成一致至关重要。市场上有一对一的Facebook Messenger、微信和 WhatsApp 等聊天应用进程,专注于办公聊天应用进程Slack 等群组聊天,或 Discord 等游戏聊天应用进程,专注于大型群组互动和低语音聊天延迟。

第一组澄清问题应该明确面试官的想法就在她要求你设计一个聊天系统的时候。至少,弄清楚你是否应该专注于一对一聊天或群聊应用进程。您可能会问的一些问题如下:
候选人:我们要设计什幺样的聊天应用进程? 1对1还是以小组为基础?
面试官:应该同时支持1对1和群聊。
应聘者:这是一个移动应用进程吗?或者网络应用进程?或两者?
面试官:两者都有。
应聘者:这个应用进程的规模有多大?初创应用进程还是大规模应用进程?
面试官:应该支持5000万日活跃用户(DAU)。
应聘者:群聊的人数限制是多少?
面试人数:最多100人
应聘者:对于聊天应用进程来说,哪些功能很重要?可以支持附件吗?
面试官:1对1聊天、群聊、在线指标。系统仅支持文本消息。
应聘者:邮件大小有限制吗?
面试官:是的,文本长度应该小于10万字符。
应聘者:需要端到端加密吗?
面试官:暂时不需要,但是如果时间允许的话我们会讨论一下。
应聘者:聊天记录要保存多久?
面试官:永远。
在本章中,我们重点关注设计一个像 Facebook Messenger 这样的聊天应用进程,重点是具有以下特点:

  • 一对一聊天,传输延迟低
  • 小组聊天(最多 100 人)
  • 在线状态
  • 多设备支持。同一账号可以登录多个账号同时。
  • 推送通知

就设计规模达成一致也很重要。我们将设计一个支持50个的系统百万日活跃用户。

第 2 步 - 提出高级设计并获得认可

为了开发高质量的设计,我们应该对客户和客户如何进行基本了解服务器进行通信。在聊天系统中,客户端可以是移动应用进程或 Web应用进程。客户端之间不直接通信。相反,每个客户连接到聊天服务,该服务支持上述所有功能。让我们重点关注基本操作。聊天服务必须支持以下功能:

  • 接收来自其他客户端的消息。
  • 为每条消息找到正确的收件人并将消息转发给收件人。
  • 如果收件人不在线,请在服务器上保留该收件人的消息,直到她在线为止。在线的。

图12-2显示了客户端(发送者和接收者)和聊天之间的关系服务。
在这里插入图片描述
当客户端打算开始聊天时,它会使用一个或多个网络连接聊天服务协议。对于聊天服务,网络协议的选择很重要。让我们讨论一下与面试官。

对于大多数客户端/服务器应用进程,请求由客户端发起。也是如此聊天应用进程的发件人端。在图 12-2 中,当发送方向 发送消息时接收者,它使用久经考验的HTTP协议,这是最通用 Web 协议。在此方案中,客户端打开与聊天的 HTTP 连接service 并发送消息,通知服务将消息发送给接收方。这
keep-alive 对此非常有效,因为 keep-alive 标头允许客户端维护 a与聊天服务保持连接。它还减少了 TCP 握手的次数。HTTP在发送端是一个很好的选择,许多流行的聊天应用进程,如Facebook [1] 最初使用 HTTP 发送消息。

但是,接收器端有点复杂。由于 HTTP 是客户端发起的,因此它不是从服务器发送消息很简单。多年来,许多技术被用来模拟服务器启动的连接:轮询、长轮询和 WebSocket。这些是系统设计面试中广泛使用的重要技术,因此让我们来研究一下每个技术他们。

投票
如图 12-3 所示,轮询是客户端定期询问服务器是否有可用的消息。根据轮询频率,轮询的成本可能很高。它可能会消耗宝贵的服务器资源来回答一个提供“否”作为答案的问题大多数时候。
在这里插入图片描述
长轮询
由于轮询可能效率低下,因此下一个进程是长轮询(图 12-4)。
在这里插入图片描述
在长轮询中,客户端会保持连接处于打开状态,直到实际有新消息可用或已达到超时阈值。一旦客户端收到新消息,它立即向服务器发送另一个请求,重新启动进程。长轮询有一个
缺点少:

  • 发送方和接收方不得连接到同一聊天服务器。基于 HTTP 的服务器是通常是无国籍的。如果使用轮促机制进行负载平衡,则接收消息可能与接收消息。
  • 服务器无法很好地判断客户端是否断开连接。
  • 效率低下。如果用户聊天不多,长时间轮询仍会定期进行超时后的连接。
    WebSocket
    WebSocket 是将异步更新从服务器发送到客户。图 12-5 显示了其工作原理。
    在这里插入图片描述
    WebSocket 连接由客户端发起。它是双向的和持久的。它开始它的作为 HTTP 连接,可以通过一些定义明确的握手“升级”到WebSocket 连接。通过这种持久连接,服务器可以将更新发送到客户。即使有防火墙,WebSocket 连接通常也能正常工作。这是因为它们使用端口 80 或 443,这些端口也由 HTTP/HTTPS 连接使用。
    之前我们说过,在发送端,HTTP是一个很好的协议,但是由于WebSocket是双向的,没有很强的技术理由不将其也用于发送。图 12-6显示了如何将 WebSockets (ws) 用于发送端和接收端。
    在这里插入图片描述
    通过使用 WebSocket 进行发送和接收,它简化了设计并使在客户端和服务器上实现更简单。由于 WebSocket连接是持久的,高效的连接管理在服务器端至关重要。
    高级设计
    刚才我们提到 WebSocket 被选为主要的通信协议在客户端和服务器之间进行双向通信时,需要注意的是其他一切都不一定是 WebSocket。事实上,大多数功能(注册、登录、用户聊天应用进程的配置文档等可以使用传统的请求/响应方法HTTP的。让我们深入研究一下,看看系统的高级组件。
    如图 12-7 所示,聊天系统分为三大类:无状态服务、有状态服务和第三方集成。
    在这里插入图片描述

无状态服务
无状态服务是传统的面向公众的请求/响应服务,用于管理登录、注册、用户个人资料等。这些是许多网站和应用进程的常见功能。无状态服务位于负载均衡器后面,其工作是将请求路由到正确的位置基于请求路径的服务。这些服务可以是整体的或单独的微服务。我们不需要自己构建许多无状态服务,因为那里是市场上可以轻松集成的服务。我们将讨论的一项服务更深入的是服务发现。它的主要工作是给客户端一个 DNS 列表客户端可以连接到的聊天服务器的主机名。
有状态服务
唯一有状态的服务是聊天服务。该服务是有状态的,因为每个客户端维护与聊天服务器的持久网络连接。在此服务中,客户端通常只要服务器仍然可用,就不会切换到另一个聊天服务器。服务发现与聊天服务密切配合以避免服务器过载。我们会去深入探讨细节。
第三方集成
对于聊天应用进程来说,推送通知是最重要的第三方集成。这是一种方法当新消息到达时通知用户,即使应用进程未运行。恰当的推送通知的集成至关重要。参考第10章设计通知系统了解更多信息。
可扩展性
在小范围内,上面列出的所有服务都可以容纳在一台服务器中。即使按照我们设计的规模因为,理论上可以将所有用户连接容纳在一台现代云服务器中。号码服务器可以处理的并发连接数很可能是限制因素。在我们的场景,在 1M 并发用户下,假设每个用户连接需要 10K 内存在服务器上(这是一个非常粗略的数字,非常依赖于语言选择),它仅需要大约 10GB 内存来容纳一台机器上的所有连接。

如果我们提出一种设计,将所有内容都放入一台服务器中,这可能会在面试官的心思。没有技术人员会在单个服务器中设计如此规模的产品。单身的由于多种因素,服务器设计是一个破坏性因素。单点故障是最大的他们之中。

然而,从单一服务器设计开始是完全可以的。只需确保面试官知道这是一个起点。将我们提到的所有内容放在一起,如图12-8显示了调整后的高层设计。
在这里插入图片描述
在图12-8中,客户端维护与聊天服务器的持久WebSocket连接实时消息传递。

  • 聊天服务器促进消息发送/接收。
  • 状态服务器管理在线/离线状态。
  • API 服务器处理一切事务,包括用户登录、注册、更改个人资料等。
  • 通知服务器发送推送通知。
  • 最后,键值存储用于存储聊天历史记录。当离线用户到来时在网上,她会看到她以前的所有聊天记录。
    存储
    至此,我们已经准备好服务器、运行服务和第三方集成完全的。技术堆栈的深处是数据层。数据层通常需要一些努力使其正确。我们必须做出的一个重要决定是选择正确的类型使用数据库:关系数据库还是NoSQL数据库?为了做出明智的决定,我们将检查数据类型和读/写模式。典型的聊天系统中存在两种类型的数据。第一个是通用数据,例如用户个人资料,设置,用户好友列表。这些数据存储在强大且可靠的关系数据库中。复制和分片是满足可用性和可扩展性的常用技术
    要求。第二个是聊天系统特有的:聊天历史数据。重要的是要了解读/写模式。
  • 聊天系统的数据量非常巨大。之前的一项研究 [2] 表明Facebook Messenger 和 Whatsapp 每天处理 600 亿条消息。
  • 仅经常访问最近的聊天记录。用户通常不会查找旧聊天记录。
  • 尽管在大多数情况下都会查看最近的聊天历史记录,但用户可能会使用以下功能:需要随机访问数据,例如搜索、查看您的提及、跳转到特定的消息等。这些情况应该得到数据访问层的支持。
  • 1 对1 聊天应用进程的读写比率约为1:1。

选择支持我们所有用例的正确存储系统至关重要。我们
推荐键值存储的原因如下:

  • 键值存储允许轻松水平扩展。
  • 键值存储提供极低的数据访问延迟。
  • 关系数据库不能很好地处理长尾数据[3]。当指数增长时大的随机访问是昂贵的。
  • 其他经过验证的可靠聊天应用进程采用键值存储。例如,Facebook Messenger 和 Discord 都使用键值存储。 Facebook Messenger 使用HBase [4],Discord 使用 Cassandra [5]。

数据模型
刚才,我们讨论了使用键值存储作为我们的存储层。最重要的是data 是消息数据。让我们仔细看看。1对1聊天的消息表
图12-9显示了1对1聊天的消息表。主键是message_id,其中有助于决定消息顺序。我们不能依靠created_at来决定消息顺序,因为可以同时创建两条消息。
在这里插入图片描述
群聊消息表
群聊消息表如图12-10所示。复合主键是(channel_id,message_id)。频道和组在这里表示相同的含义。channel_id是分区键,因为群聊中的所有查询都在频道中运行。
在这里插入图片描述
消息ID
如何生成message_id是一个有趣的话题,值得探讨。Message_id携带确保消息顺序的责任。为了确定消息的顺序,Message_id必须满足以下两个条件。

  • id必须唯一。
  • id应该按时间排序,这意味着新行的id比旧行高。

我们如何才能实现这两个保证呢?首先想到的是“auto_increment”是MySql中的关键字。然而,NoSQL数据库通常不提供这样的特征。

第二种方法是使用全局64位序列号生成器,如Snowflake[6]。这将在“第7章:在分布式系统中设计唯一的ID生成器”中讨论。最后一种方法是使用本地序列号生成器。Local意味着id是唯一的组内唯一。本地IDs工作的原因是维护消息序列一对一的渠道或团体渠道就足够了。这种方法更容易实现实现与全局ID实现的比较。

步骤3 -深入设计

在系统设计面试中,通常你会深入了解一些高层设计中的组件。用于聊天系统,服务发现,消息传递流量和在线/离线指标值得深入探索。
服务发现
服务发现的主要作用是为基于客户端推荐最佳聊天服务器在地理位置、服务器容量等标准上。Apache Zookeeper[7]是一个流行的开源服务发现解决方案。它注册了所有可用的聊天服务器并根据预定义的标准为客户端选择最佳聊天服务器。service discovery (Zookeeper)的工作原理如图12-11所示。
在这里插入图片描述

  1. 用户A尝试登录应用。
  2. 负载均衡器将登录请求发送到API服务器。
  3. 后端对用户进行身份验证后,服务发现将为其找到最佳聊天服务器
    以选择服务器2为例,将服务器信息返回给用户A。
  4. 用户A通过WebSocket连接到聊天服务器2。

消息流
了解聊天系统的端到端流程很有趣。在本节中,我们将探索一对一的聊天流,跨多个设备的消息同步和群聊流。
1对1聊天流程
图12-12展示了用户A发送消息给用户B的过程
在这里插入图片描述
5. 用户A向聊天服务器1发送聊天消息。
6. 聊天服务器1从ID生成器获取消息ID。
7. 聊天服务器1将消息发送到消息同步队列。
8. 消息存储在一个键值存储中。
9. a.。如果用户B在线,消息将转发到用户B所在的聊天服务器2连接。
10. b。如果用户B离线,则会从PN服务器收到推送通知。
11. 聊天服务器2将消息转发给用户b2.用户B与聊天服务器的连接。

多设备间的消息同步
许多用户有多个设备。我们将解释如何在多个消息之间同步消息设备。图12-13展示了消息同步的示例。
在这里插入图片描述
如图12-13所示,用户A有话机和笔记本电脑。当用户A登录使用她的手机聊天应用,它与聊天服务器1建立一个WebSocket连接。同样的,笔记本电脑和聊天服务器1之间有一个连接。

每个设备都维护一个名为cur_max_message_id的变量,用于记录最新的消息设备上的消息ID。满足以下两个条件的消息将被考虑作为新闻消息:

  • 收件人ID与当前登录的用户ID相等。
  • 键值存储中的消息ID大于cur_max_message_id。
    由于每个设备上的cur_max_message_id不同,因此消息同步很容易设备可以从KV存储中获取新消息。

小群聊天流程
与一对一聊天相比,群聊的逻辑要复杂得多。数据12-14和12-15解释了流程。
在这里插入图片描述
图12-14展示了用户A在群聊中发送消息的过程。假设群组中有3个成员(用户A、用户B、用户C)。首先是用户的留言
将A复制到每个组成员的消息同步队列:一个用于用户B,另一个用于用户B用户c。你可以把消息同步队列想象成收件人的收件箱。这个设计选择适合小组聊天,因为:

  • 简化了消息同步流程,因为每个客户端只需要检查自己的收件箱即可获取新消息。
  • 当群组数量较少时,在每个收件人的收件箱中存储一份副本并不划算贵了。

微信使用类似的方法,它将一个组限制为500个成员。然而,对于对于有大量用户的组,为每个成员存储消息副本是不可接受的。

在接收方,一个接收方可以接收来自多个用户的消息。每个收件人有一个收件箱(消息同步队列),其中包含来自不同发件人的消息。数字。12-15展示了这个设计。
在这里插入图片描述
在线状态
在线状态指示器是许多聊天应用程序的基本功能。通常,你可以看到一个绿色的点旁边的用户的资料图片或用户名。本节解释什么是发生在幕后。

在高层设计中,在线状态服务器负责管理在线状态和通过WebSocket与客户端通信。会触发一些流在线状态改变。让我们逐一研究它们。
用户登录
“服务发现”部分解释了用户登录流程。在WebSocket之后客户端与实时业务、用户A的在线状态、用户A的在线状态建立连接last_active_at时间戳保存在KV存储中。状态指示灯显示用户状态在她登录后在线。
在这里插入图片描述
用户注销
用户注销时,将经历如图12-17所示的用户注销流程。的在KV store中,“在线状态”变为“离线”。在位状态指示灯显示当前用户为
离线。
在这里插入图片描述
用户断开
我们都希望我们的互联网连接是一致和可靠的。然而,这并不总是情况;因此,我们必须在设计中解决这个问题。当用户从Internet,客户端和服务器之间的持久连接丢失。一种天真的处理方式断开用户连接是将用户标记为离线,并将状态更改为在线连接重建。然而,这种方法有一个重大缺陷。对于用户来说,这是很常见的在短时间内频繁断开和重新连接互联网。例如,网络当用户通过隧道时,连接可以打开或关闭。在线状态更新为每次断开/重新连接都会使presence指示灯变化太频繁,从而导致用户体验差。

我们引入心跳机制来解决这个问题。定期在线客户端向在位服务器发送心跳事件。在位服务器是否接收到心跳事件在一定的时间内,例如客户端发送的x秒内,用户被认为是在线的。否则,它离线了。

在图12-18中,客户端每5秒向服务器发送一次心跳事件。后发送3次心跳事件,客户端在x = 30内断连且未重新连接seconds(这个数字是任意选择的,以说明逻辑)。在线状态为:改为离线。
在这里插入图片描述
在线状态
用户A的朋友是如何知道状态改变的?图12-19解释了它的工作原理。存在服务器使用发布-订阅模型,其中每个朋友对维护一个通道。当用户A的在线状态改变时,它将事件发布到三个通道,A-B, A-C和A-D通道。这三个频道被用户B、C和D订阅,分别。因此,朋友可以很容易地获得在线状态更新。的通信客户端和服务器之间通过实时WebSocket连接。
在这里插入图片描述
上述设计对于小用户群体是有效的。例如,微信使用类似的因为其用户组上限为500。对于较大的团体,通知所有成员关于在线状态是昂贵和耗时的。假设一个组有100 000名成员。每个状态更改将生成100 000个事件。为了解决性能瓶颈,a可能的解决方案是仅当用户输入组或手动输入时才获取在线状态刷新好友列表。

步骤4 -打包

在本章中,我们介绍了一个既支持一对一聊天又支持一对一聊天的聊天系统架构小组聊天。WebSocket用于客户端和服务器。聊天系统包括以下组件:实时聊天服务器消息,用于管理在线状态的状态服务器,用于推送通知的服务器发送推送通知,保存聊天记录的键值存储和API服务器其他功能。
如果面试结束时你有额外的时间,下面是一些额外的谈话要点:

  • 扩展聊天应用程序,以支持照片和视频等媒体文件。媒体文件是明显大于文本的大小。压缩、云存储和缩略图都是有趣的话题。
  • 端到端加密;Whatsapp支持消息端到端加密。只有发件人和收件人可以阅读消息。有兴趣的读者可以参考一下参考资料[9]。
  • 在客户端缓存消息可以有效地减少数据传输客户端和服务器。
  • 优化加载时间。Slack建立了一个地理分布的网络来缓存用户的数据,通道等,以获得更好的加载时间[10]。
  • 错误处理。
    • 聊天服务器错误。可能有数十万个,甚至更持久连接到聊天服务器。如果聊天服务器离线,服务发现(Zookeeper)将为客户端提供一个新的聊天服务器来建立新的连接与。
    • 消息重发机制。重试和排队是常见的技术重新发送消息。

恭喜你走到了这一步!现在给自己一点鼓励吧。好工作!

参考资料
[1] Erlang在Facebook: https://www.erlangfactory.com/upload/presentations/31/EugeneLetuchy-ErlangatFacebook.pdf
[2] Messenger和WhatsApp每天处理600亿条消息:
https://www.theverge.com/2016/4/12/11415198/facebook-messenger-whatsapp-number 消息- vs - sms - f8 - 2016
[3]长尾:https://en.wikipedia.org/wiki/Long_tail
[4]消息基础技术:https://www.facebook.com/notes/facebook工程/消息基础技术/454991608919/
[5] Discord如何存储数十亿条消息:https://blog.discordapp.com/how-discord存储- billion- of- Messages -7fa6ec7ee4c7
[6]宣布雪花:https://blog.twitter.com/engineering/en_us/a/2010/announcing Snowflake .html
[7] Apache ZooKeeper: https://zookeeper.apache.org/
[8]从无到有:微信后台系统的演变(中文文章):
https://www.infoq.cn/article/the-road-of-the-growth-weixin-background
[9]端到端加密:https://faq.whatsapp.com/en/android/28030015/
[10] Flannel:应用级边缘缓存,使Slack规模扩大:
https://slack.engineering/flannel-an-application-level-edge-cache-to-make-slack-scale b8a6400e2f6b

文章来源:https://blog.csdn.net/weixin_43178103/article/details/135683695
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。