
Facebook 作为全球最大的社交网络,截止 2019 年 12 月 31 日,Facebook 月活跃用户达到 25 亿,远超中国的 16 亿人。坐拥如此庞大的、可怖的用户数量,Facebook 又是如何保持聊天系统能够及时而无误地运转呢?Shivang 为我们揭开了 Facebook 的实时聊天架构背后的一切。
本文经原作者授权,由 InfoQ 中文站翻译并发布。
在这篇文章中,我将讨论 Facebook 的实时聊天架构,这个架构每天可以发送超过数十亿条消息。它在后端使用的是什么技术?技术栈是什么?系统架构又是什么?像 Facebook 这样拥有如此庞大用户数量的平台,在推出和扩展这样的功能时,面临的主要挑战是什么呢?
1. 介绍
所有这一切都始于那场 Hackathon,当时,有几名 Facebook 工程师编写了一个聊天原型,并向团队展示了这个原型。这个原型的功能非常基础,只有最基本的功能,浮动的聊天框可以在网页上拖动,还可以通过页面重载和其他页面导航过程中保持不变(即持久化)。
Facebook 工程师们采用了这个原型,将其开发成一个成熟的实时聊天功能,成了 Facebook 服务生态系统中使用率最高的功能之一。
它为全世界每天发送的数十亿条信息提供了便利。社交平台的工程团队对其进行了很好的扩展,响应时间不到 100 毫秒。
这个功能一直不断改进,唯一的目标就是为用户提供一流的通信服务。
2. 从零开始编写并维护聊天功能
除了具有通用功能之外,聊天模块还集成了 Facebook 社交图谱(Fb social graph)。用户可以很轻松地展开他们的好友列表,以及其他相关信息,如他们正在玩的游戏和其他东西等。
一般来说,平台上用户可以访问的所有信息,也可以在聊天模块上访问。
它更简单、更干净、也更安全,在从零开始编写代码时提供了更多的控制,而不是让它与第三方代码集成。
3. 实时聊天架构和技术栈
整个系统由几个松散耦合的模块组成,它们相互协同工作,如 Web 层、用户界面、聊天记录器、用户在线状态模块和通道集群。

用户界面
用户界面自然是用 JavaScript 编写的,不过,其中也有一些是用 PHP 编写的,用于服务器端渲染。
借助 Ajax,客户端和服务器之间建立了长时间保持开放的持久连接。
之所以不用 Flash,是出于两个原因。首先,Flash 会要求用户在浏览器中安装插件,这并不是什么好的用户体验;其次,从安全角度来看,Flash 也并非首选。
消息获取流是基于 PULL 和 PUSH 的 HTTP 模型的混合。
最初,客户端发送 PULL 请求来获取消息的第一个快照,同时订阅增量更新,这是一种基于 PUSH 的方法。
一旦用户订阅了更新,每当有新的更新可用时,Facebook 后端就会开始将更新推送到客户端。
后端 Web 层
Web 层是用 PHP 来驱动的。它处理普通的 Web 请求。负责用户认证、好友隐私设置、聊天历史记录、好友更新以及其他平台功能的业务逻辑。
用户在线状态模块
该模块提供用户的联系人 / 好友的在线可用性信息。它使用 C++ 编写的,是系统中 ping 最重的模块。
该模块将用户的在线信息聚合到内存中,并根据请求将信息发送给客户端。
通道服务器
通道服务器负责消息的排队和传递。这一功能是使用 Erlang 编写的。
Erlang 是一种并发函数式编程语言,用于编写是是可扩展和高可用的系统,如即时通讯、金融技术应用、在线电话等。
Erlang 的运行时系统内置了对并发、分发和容错的支持。
通道服务器利用 Mochi Web 库。这是一个用于构建轻量级 HTTP 服务器的 Erlang 库。用户发送的消息在通道服务器中排队。每个消息都有一个序列号,以便在任意两个或多个用户之间进行同步通信。
聊天记录器
聊天元(Chat meta)和其他消息的记录是通过聊天记录器模块完成的。它是用 C++ 编写的,记录用户界面页面加载之间的信息。
4. 服务可扩展性和部署
用户状态和聊天记录数据在 Facebook 的所有数据中心进行复制,而通道服务器数据仅存储在一个专用数据中心,以确保消息的高度一致性。
如上图所示,所有的后端模块都是松散耦合的。它们通过 Thrift 相互通信。
Thrift 是一种通信协议,它促进了在异构技术上运行的服务之间的通信。
它是 Facebook 内部开发的用于服务通信的序列化和 RPC 框架。它可以帮助在 C++、Erlang、PHP、JavaScript 上运行的系统作为一个团队协同工作。
最耗资源的操作
整个系统中最耗费资源的操作并非发送数十亿条消息,而是让用户随时了解他的联系人 / 好友的在线状态。
这一点很重要,因为一个人只有在看到自己的在线连接时才会开始发起会话。
要实现这一点,一种选择是向用户发送他们的连接在线的通知。但是考虑到平台拥有的用户数量,这个过程无论如何都无法进行扩展。
该操作的最坏情况复杂度为 O(用户的平均好友数 * 高峰流量时的用户数 * 用户离线和重新联机的频率)消息 / 秒。
在高峰时段,网站上的并发用户数以百万计。要想保持所有的用户信息都是最新的,从技术上来说是不可行的。
此外,那些甚至不聊天的用户通过异步轮询后端来获取他们的连接活动状态,这也给服务器带来了很大的负担。
实时系统很难扩展规模。扩展它们的后端需要一些相当扎实的工程技能。
另外,请了解如何通过“链接识别”(Linked Identify)来识别在线用户,深入了解其实时消息传递架构。
为了扩展用户在线状态的后端,通道服务器集群会保存可用于聊天的用户记录,并通过定期批量更新将这些记录发送到在线状态服务器。
这个过程的好处在于,只需一个查询,就可以获取可供聊天的用户连接的整个列表。
考虑到模块之间交换的信息量非常庞大,通道服务器会先压缩所有的信息,然后再将其以流式传输到在线状态服务器。
增加了负载平衡器的数量,以管理用户连接的绝对数量。此后,基础设施管理并发用户连接的能力显著增强。这是一个瓶颈,导致聊天服务在高峰时期时断时续。
5. 消息和存储的同步
如前所述,为了管理同步通信,每个消息都有一个序列号。
除此之外,Facebook 还创建了一个信使同步协议(Messenger Sync Protocol),将非媒体数据的使用量减少了 40%。此举减少了它们网络上的拥塞,避免了由此产生的错误。
Facebook 的工程团队开发了一项名为 Iris 的服务,可以将消息更新组织成一个有序的队列。
队列具有指向它的不同指针,这些指针可以帮助跟踪用户已经读取的消息更新和那些仍然挂起尚待处理的消息更新。

最近的消息是从 Iris 的内存中发送的,旧的会话是从传统的存储器中提取的。Iris 是建立在 MySQL 和闪速存储器之上的。
Hbase 最初用做信使存储,但后来该存储被迁移到 MyRocks。这是一个由 Facebook 编写的开源数据库项目,它将 RocksDB 作为 MySQL 存储引擎。
要了解更多信息,请阅读:《Facebook使用哪些数据库?》
原文链接:
评论