Source: https://atscaleconference.com/calling-relay-infrastructure-at-whatsapp-scale/?
WhatsApp 历史和规模
自2015年推出 WhatsApp 的通话功能以来,它的通话中继基础架构一直负责在用户之间高可靠、低延迟地传输语音和视频数据。从一对一语音通话开始,接着是视频通话,再到群组通话,WhatsApp 的使用量随着时间呈指数级增长。该应用现在已成为世界上最大的通话产品之一,每天传输超过10亿次通话。由于规模如此惊人,我们遇到了很少有通话产品会遇到的独特问题。我们不断发展中继架构,以跟上不断增长的规模和容量需求。
WhatsApp 原则
WhatsApp 建立在隐私、简单和可靠性这些核心原则之上。
为确保不可被破坏的隐私,WhatsApp 早期就决定对所有通信(包括语音和视频通话)强制执行端到端加密。这意味着中继服务器无法访问流经其中的媒体内容,因此中继无法对媒体进行升级/降级、转码或任何其他修改。
为确保简单性,我们专注于通过聚焦并完善最重要的功能为用户提供无干扰体验。从用户界面到服务器架构,一切都遵循这一原则。我们自定义的 WASP(WhatsApp STUN 协议)就是一个例子。用户设备与中继服务器通信的标准协议是 TURN。TURN 是一种相当复杂的协议,使用多个临时端口、无法很好地与防火墙配合使用,并且在分布式架构中无法很好地扩展。WASP 的核心与 TURN 相似,但 WASP 仅使用一个端口进行所有网络通信,并更多地依赖用户设备来做出决策和跟踪连接状态,这对于中继服务器故障转移来说效果很好。
为确保可靠性,我们的目标一直是确保任何人都可以拨打 WhatsApp 通话——无论是在高速网络上使用最新设备,还是在网络状况不佳时使用低端设备。我们希望通话功能可以可靠运行,即使在极端负载时,因为那可能是人们最需要与亲朋好友联系的时候。
什么是通话中继?
两个关键的基础设施可以让 WhatsApp 用户进行通话:信令服务和通话中继服务。
信令服务器协助设置通话的初始过程。当有人拨入时,它负责让设备响铃;当用户接听时,它负责连接通话。
一旦通话建立,中继服务在整个通话期间起到维护连接的重要作用。通话中继在用户设备之间来回传输音频和视频数据包。
通话中继解决了与建立 WhatsApp 通话相关的一些关键技术挑战。
改善网络延迟和数据包损失问题
最好的通话应当像亲自交谈一样对话自然流畅,没有延迟或中断。这是我们为每次通话所追求的终极目标。两个关键的技术挑战会影响通话的临场感:网络延迟和数据包损失。
网络延迟会导致对话滞后,干扰对话的自然流畅度。延迟还会大大降低通话算法的最优运行,因为它们最终会在过时的信息上运行。
数据包损失会导致视频冻结、音频机器人化等问题,严重影响通话体验。
延迟
改善延迟的最佳方式是通过尽可能最短的路径路由通话——这意味着将中继服务托管在离终端用户尽可能近的位置。
为此,我们设计了WhatsApp中继基础设施,使其可在Meta在全球各地建立的数千个接入点(PoP)上运行。这些PoP最初是为了服务于Meta的内容交付网络(CDN),能够满足Facebook和Instagram等产品对其提出的大规模需求。
由于这些接入点分布在比数据中心更多的区域,中继服务距离终端用户更近,从而降低了WhatsApp通话的延迟。另外,这些接入点集群通过极高质量和专用主干链路互联。
仅靠近终端用户是不够的;我们还必须为每次WhatsApp通话选择最佳集群。为此,我们对历史延迟数据应用复杂的定位算法,计算出每次通话的最佳集群。通话过程中,最优集群可能会发生变化——例如,当用户从WiFi切换到蜂窝网络时。如果我们检测到正在进行的通话条件发生显著变化,我们会重新计算最优集群。
数据包损失
解决数据包损失的最佳方式是防患于未然。大多数数据包损失源于网络拥塞,而拥塞是由于带宽使用不当(发送太多数据)造成的,通常是由于不准确地估计网络链路带宽所致。因此,准确估计带宽和调节比特率是防止拥塞的关键。
然而,带宽是高度可变的,在通话过程中会发生波动。许多数据包丢失和带宽问题发生在最后一公里,比如用户的WiFi网络中。中继服务器协助准确估计每一段通话的带宽。它通过测量数据包延迟和数据包丢失,并将这些作为反馈共享给用户设备。快速准确地估计带宽有助于设备调整比特率,减少网络拥塞和数据包丢失。
某些网络无论是否拥塞都会导致数据包丢失。在这种丢包网络中,中继服务使用主动数据包丢失缓解技术。其中一种技术称为NACK(否定确认)。中继服务器会缓存几秒钟的媒体数据包,并响应设备发送的NACK重传这些数据包。与端到端重传相比,从中继服务重传效率更高,因为中继位于通话参与者之间的中间位置,减少了重传的延迟。另外,中继更有效率,因为它能够只在丢包的链路上重传数据包。
最小化设备资源使用
音频和视频流数量的增加,尤其是在大型群组通话中,会导致更高的带宽使用。更糟糕的是,某些国家的移动数据套餐仍然昂贵,我们不能假设用户即使在WiFi上也有无限的数据流量。此外,编码/解码和加密/解密等操作是CPU密集型的,更多的CPU使用意味着更多的电池使用。因此,我们希望节约设备资源,在最大限度延长设备电池寿命的同时,最小化用户的带宽使用。
在音频通话中节省带宽的一个显而易见的解决方案是,只为正在说话的用户传输流。这种解决方案称为主导说话人检测。
然而,端到端加密使得主导说话人检测变得更加复杂,因为它阻止了中继服务检查音频内容和确定谁在说话。为解决这个问题,WhatsApp中继服务器会从通话中的用户设备请求音量信息。通过仅共享音量信息而不是音频内容,中继服务器获得了足够的信息来识别主导说话人,同时仍然保护了通话的隐私。
这种解决方案还存在一个额外的挑战:当新的参与者开始说话时,通常会存在一定延迟,然后主导说话人算法才会比较所有音量水平,中继才会开始为这个新的发言人转发音频。这可能导致他们最初的话语被剪掉。为消除这种延迟,我们允许设备在音量突然增加时覆盖主导说话人算法。
优化网络条件
我们希望为用户提供网络所能允许的最佳体验。在这里我们面临的最大技术挑战往往不是中继服务所遇到的各种网络,而是单个通话中网络条件的不对称性。
考虑一种情况:一个视频群组通话,其中有些用户网络状况良好,而另一些用户网络状况不佳。网络状况良好的设备传输高比特率的视频流,导致网络状况不佳的设备发生拥塞,进而导致它们获得糟糕的体验。但由于端到端加密,中继服务器无法访问媒体内容,因此无法选择性地为较差网络降低视频质量。
中继服务器可以指示通话中的所有设备降低其比特率。这将允许网络状况较差的用户参与通话,但网络状况良好的设备将接收低分辨率视频,尽管它们所在的高容量网络允许获得更佳体验。
为解决这个问题,我们使用了一种叫做"视频模拟发送"的技术。当服务器检测到通话中用户网络质量参差不齐时,它会指示网络状况良好的用户"拆分"他们的流,并发送一路高比特率流和一路低比特率流。然后中继服务选择性地将高比特率流转发给网络状况良好的用户,将低比特率流转发给网络状况较差的用户。
这种方式允许通话中的所有用户都获得最佳体验,同时仍然保留了端到端加密。
除了这种解决方案,我们还解决了用户对视频的不同需求。虽然每个人在通话中都期望听到相同的音频,但并非每个人都期望看到相同的视频。大多数参与者可能希望观看正在讲话的用户的视频,而另一些人可能希望在用户列表中"滚动"浏览。因此,服务器需要根据参与者的视频是否需要,对其"打开"或"关闭"视频。为实现这一点,我们使用了一种称为"视频订阅"的功能,其中每个参与者都会通知服务器他们感兴趣的视频。结合带宽估计,这些信息有助于中继服务器决定哪些用户应该传输视频以及以何种比特率传输。
确保可扩展性、可靠性和可用性
可扩展性
每天服务数十亿次通话绝非易事。为实现这种规模,我们不仅在数千个PoP上运行WhatsApp中继服务,而且在每个PoP内运行数百个容器。
考虑一下单个PoP集群的结构。每个集群运行着数百个中继容器。单个连接会均衡负载到这些容器上。事实上,同一通话中的多个参与者往往由完全不同的容器提供服务。这有助于我们均匀地在集群的所有容器之间平衡负载,即使单个通话的持续时间和群组大小有所不同。
可靠性
没有什么比在重要对话的中途通话掉线更让人沮丧的了。
可靠提供通话服务的一个挑战是处理计划内和计划外的维护事件。当通话运行在数十万个容器上时,至少服务器的一部分将持续进行某种形式的维护。
计划内的维护事件(如软件升级)将重启容器,而为应对容量变化而进行的集群调整也会使部分容器离线。计划外事件如断电或软件崩溃也可能导致容器重启或离线。
为处理这些情况,我们专门为中继服务设计了弹性容错机制。当一个中继容器发生故障时,负载均衡器会检测到并将连接重新分发到健康的容器。新容器将接管故障容器之前在服务的连接。为继续为那些连接提供服务,新容器需要恢复所有关于这些连接的上下文。这种上下文称为"通话状态"。
我们有两种通话状态:
关键通话状态,对通话至关重要,包括群组大小和设备网络地址等参数,没有这些参数通话将无法运行。新参与者加入通话或参与者从WiFi切换到蜂窝网络都是会改变关键通话状态的事件示例。
临时通话状态,不断变化,例如带宽估计和当前哪个参与者正在讲话。新参与者开始讲话是会改变临时状态的事件示例。
我们使用状态服务器存储这些状态,以便其他容器可以检索。每当关键通话状态发生变化时,都会将其保存到状态服务器。如果通话状态丢失,几乎不可能从容器切换或重启中恢复过来。
临时状态则更加宽容。如果临时状态丢失,通话质量可能会暂时下降,但可以很快恢复。临时状态可能根本不会保存到状态服务器,或者只会不频繁地检查点保存。
通过选择性地存储哪些状态以及何时存储,我们提高了 WhatsApp 通话的可靠性,而不会影响可扩展性。
虽然媒体数据包的转发可以完全分布式处理,但每次通话都需要做出许多中央决策。一个很好的例子是中继服务器的"主导说话人检测",它需要比较通话中所有参与者的音频音量水平。另一个例子是服务器的"带宽分配",它需要比较一次通话中参与者的带宽估计值,并告知设备应以何种比特率传输。为做出这些中央决策,我们将中继服务的网络层与决策层分离了。网络层仍然是分布式的,处理媒体数据包的火力流,而决策层针对每次通话集中化,利用来自网络层的元数据更新做出集中决策。
可用性
有时中继服务处于极端负载之下,因为许多人正试图拨打电话。这在体育赛事或自然灾害期间很普遍,人们会试图与亲朋好友取得联系。在那些时候,通话服务必须具有很高的可靠性和可用性,让用户在最需要时能够连接。只要互联网可用,我们就希望 WhatsApp 通话对用户来说是可能的。
WhatsApp 每年都要应对的一个最有趣的"极端事件"就是除夕——在这一天支持通话是 WhatsApp 最独特的工程经历之一。这些庆祝活动之所以特别,是因为它们几乎在世界各地的每个国家都会举行,而且都会在每个地区的午夜时分开始。随着每个国家的钟声敲响午夜,我们都会看到该地区的通话量出现巨大的峰值。每个峰值刚好在午夜开始、上升非常陡峭,并持续约20分钟。
由于峰值上升如此之快,一旦观察到故障,几乎无法人工采取任何响应措施。在极端高峰期间我们所看到的大部分恢复能力都已融入到中继架构之中。
为确保可用性,我们会在除夕前几周做大量规划工作。我们使用数据科学来预测每个地区的预期流量,并运行性能测试来衡量中继服务器能够承载的最大流量。
每年流量模式都会发生变化,这会导致瓶颈位置发生转移。这些变化可能是无法预测的,从而使预测变得困难。例如,在2022年除夕,我们看到欧洲地区的群组通话数量比往年大幅增加,这给该地区的中继基础设施带来了压力。
由于中继服务器处理通话的能力是有物理极限的,我们明确定义了优先级,并采取防御式运营方式。这里的主要目标是防止灾难性故障,如果中继服务完全超出容量,则以预定的、渐进式的方式(而非一次性)优雅地失效。
随着通话量增加和中继服务接近容量限制,资源如CPU和内存会自动进行监控,并启动节流。我们的优先级会指导系统按特定顺序允许功能失效。例如,正在进行的通话比新通话更重要。我们的系统会限制新通话的建立,以允许基础设施维护正在进行通话的质量。同样,一对一通话比群组通话更重要。
我们在确保可用性方面还面临着区域容量的挑战。由于通话对延迟如此敏感,只有区域容量才是关键。当我们在某个国家遇到通话量峰值时,它会压垮当地接入点的容量。其他地区的接入点可能还有足够的剩余容量,但由于会引入太多额外延迟,因此无法用于中继通话。我们在这里的解决方案是将非延迟敏感型的Meta应用程序和流量重新定位到其他地区,从而为诸如 WhatsApp 通话等实时应用程序创建临时的区域容量。
经验总结
通过多年来在 WhatsApp 构建这种中继架构的经验,我们获得了关键见解,并证明了在不影响通话隐私和质量的前提下,创建一个可扩展到数十亿次通话的极其可靠的通话产品是可行的。我们的一些关键经验教训包括:
中继架构的简单性在实现高可靠性、性能和可扩展性方面起到了至关重要的作用。 每个用户的最佳体验都取决于诸如设备和网络等各种因素,而对于同一通话中的不同参与者,这些因素可能存在显著差异。
虽然我们为可靠性设计系统,但仍会存在组件可能发生故障的情况。我们需要设计系统以可预测的方式失效,从而防止灾难性故障。 在解决每一个技术挑战和构建每一个解决方案时,将用户需求放在核心位置是非常重要的。