在本章中,我们将探讨聊天系统的设计。几乎每个人都使用聊天应用进程。图 12-1 显示了市场上一些最受欢迎的应用。
聊天应用进程为不同的人执行不同的功能。钉子非常重要降低具体要求。例如,您不想设计一个专注于当面试官想要一对一聊天时进行群聊。探索很重要功能要求。
就聊天应用进程的设计类型达成一致至关重要。市场上有一对一的Facebook Messenger、微信和 WhatsApp 等聊天应用进程,专注于办公聊天应用进程Slack 等群组聊天,或 Discord 等游戏聊天应用进程,专注于大型群组互动和低语音聊天延迟。
第一组澄清问题应该明确面试官的想法就在她要求你设计一个聊天系统的时候。至少,弄清楚你是否应该专注于一对一聊天或群聊应用进程。您可能会问的一些问题如下:
候选人:我们要设计什幺样的聊天应用进程? 1对1还是以小组为基础?
面试官:应该同时支持1对1和群聊。
应聘者:这是一个移动应用进程吗?或者网络应用进程?或两者?
面试官:两者都有。
应聘者:这个应用进程的规模有多大?初创应用进程还是大规模应用进程?
面试官:应该支持5000万日活跃用户(DAU)。
应聘者:群聊的人数限制是多少?
面试人数:最多100人
应聘者:对于聊天应用进程来说,哪些功能很重要?可以支持附件吗?
面试官:1对1聊天、群聊、在线指标。系统仅支持文本消息。
应聘者:邮件大小有限制吗?
面试官:是的,文本长度应该小于10万字符。
应聘者:需要端到端加密吗?
面试官:暂时不需要,但是如果时间允许的话我们会讨论一下。
应聘者:聊天记录要保存多久?
面试官:永远。
在本章中,我们重点关注设计一个像 Facebook Messenger 这样的聊天应用进程,重点是具有以下特点:
就设计规模达成一致也很重要。我们将设计一个支持50个的系统百万日活跃用户。
为了开发高质量的设计,我们应该对客户和客户如何进行基本了解服务器进行通信。在聊天系统中,客户端可以是移动应用进程或 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)。
在长轮询中,客户端会保持连接处于打开状态,直到实际有新消息可用或已达到超时阈值。一旦客户端收到新消息,它立即向服务器发送另一个请求,重新启动进程。长轮询有一个
缺点少:
无状态服务
无状态服务是传统的面向公众的请求/响应服务,用于管理登录、注册、用户个人资料等。这些是许多网站和应用进程的常见功能。无状态服务位于负载均衡器后面,其工作是将请求路由到正确的位置基于请求路径的服务。这些服务可以是整体的或单独的微服务。我们不需要自己构建许多无状态服务,因为那里是市场上可以轻松集成的服务。我们将讨论的一项服务更深入的是服务发现。它的主要工作是给客户端一个 DNS 列表客户端可以连接到的聊天服务器的主机名。
有状态服务
唯一有状态的服务是聊天服务。该服务是有状态的,因为每个客户端维护与聊天服务器的持久网络连接。在此服务中,客户端通常只要服务器仍然可用,就不会切换到另一个聊天服务器。服务发现与聊天服务密切配合以避免服务器过载。我们会去深入探讨细节。
第三方集成
对于聊天应用进程来说,推送通知是最重要的第三方集成。这是一种方法当新消息到达时通知用户,即使应用进程未运行。恰当的推送通知的集成至关重要。参考第10章设计通知系统了解更多信息。
可扩展性
在小范围内,上面列出的所有服务都可以容纳在一台服务器中。即使按照我们设计的规模因为,理论上可以将所有用户连接容纳在一台现代云服务器中。号码服务器可以处理的并发连接数很可能是限制因素。在我们的场景,在 1M 并发用户下,假设每个用户连接需要 10K 内存在服务器上(这是一个非常粗略的数字,非常依赖于语言选择),它仅需要大约 10GB 内存来容纳一台机器上的所有连接。
如果我们提出一种设计,将所有内容都放入一台服务器中,这可能会在面试官的心思。没有技术人员会在单个服务器中设计如此规模的产品。单身的由于多种因素,服务器设计是一个破坏性因素。单点故障是最大的他们之中。
然而,从单一服务器设计开始是完全可以的。只需确保面试官知道这是一个起点。将我们提到的所有内容放在一起,如图12-8显示了调整后的高层设计。
在图12-8中,客户端维护与聊天服务器的持久WebSocket连接实时消息传递。
选择支持我们所有用例的正确存储系统至关重要。我们
推荐键值存储的原因如下:
数据模型
刚才,我们讨论了使用键值存储作为我们的存储层。最重要的是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必须满足以下两个条件。
我们如何才能实现这两个保证呢?首先想到的是“auto_increment”是MySql中的关键字。然而,NoSQL数据库通常不提供这样的特征。
第二种方法是使用全局64位序列号生成器,如Snowflake[6]。这将在“第7章:在分布式系统中设计唯一的ID生成器”中讨论。最后一种方法是使用本地序列号生成器。Local意味着id是唯一的组内唯一。本地IDs工作的原因是维护消息序列一对一的渠道或团体渠道就足够了。这种方法更容易实现实现与全局ID实现的比较。
在系统设计面试中,通常你会深入了解一些高层设计中的组件。用于聊天系统,服务发现,消息传递流量和在线/离线指标值得深入探索。
服务发现
服务发现的主要作用是为基于客户端推荐最佳聊天服务器在地理位置、服务器容量等标准上。Apache Zookeeper[7]是一个流行的开源服务发现解决方案。它注册了所有可用的聊天服务器并根据预定义的标准为客户端选择最佳聊天服务器。service discovery (Zookeeper)的工作原理如图12-11所示。
消息流
了解聊天系统的端到端流程很有趣。在本节中,我们将探索一对一的聊天流,跨多个设备的消息同步和群聊流。
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。满足以下两个条件的消息将被考虑作为新闻消息:
小群聊天流程
与一对一聊天相比,群聊的逻辑要复杂得多。数据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可能的解决方案是仅当用户输入组或手动输入时才获取在线状态刷新好友列表。
在本章中,我们介绍了一个既支持一对一聊天又支持一对一聊天的聊天系统架构小组聊天。WebSocket用于客户端和服务器。聊天系统包括以下组件:实时聊天服务器消息,用于管理在线状态的状态服务器,用于推送通知的服务器发送推送通知,保存聊天记录的键值存储和API服务器其他功能。
如果面试结束时你有额外的时间,下面是一些额外的谈话要点:
恭喜你走到了这一步!现在给自己一点鼓励吧。好工作!
参考资料
[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