在Discord用Elixir进行大规模的实时通信

642 阅读7分钟

欢迎来到我们关于公司在生产中使用Elixir的系列案例研究。请看我们迄今为止发表的所有案例

Discord由Jason Citron和Stan Vishnevskiy于2015年创立,是一个为你的社区和朋友提供的永久性的、只接受邀请的空间,人们可以在语音、视频和文本之间跳转,这取决于他们想如何交谈,让他们以一种非常自然或真实的方式进行对话。今天,该服务拥有来自全球各地的1亿多月度活跃用户。每天人们在Discord服务器上花费40亿分钟进行对话,涉及670万个活跃服务器/社区。

从第一天起,Discord就使用Elixir作为其聊天基础设施的骨干。当Discord第一次采用这种语言时,他们还在努力建立一个可行的业务,有许多问题和挑战摆在他们面前。Elixir在为他们提供所需的技术灵活性以发展公司方面发挥了关键作用,同时也成为让他们的系统大规模运行的基石。

起始技术

早在2015年,Discord选择了两种主要语言来构建他们的基础设施。Elixir和Python。Elixir最初被选来驱动WebSocket网关,负责中继消息和实时复制,而Python则驱动他们的API。

现在,Python API是一个单体,而Elixir堆栈包含20多个不同的服务。这些架构选择并不代表语言之间的对立,而是一个务实的决定。来自Discord团队的Mark Smith简明扼要地解释了这一点。"鉴于Elixir服务将处理更大的流量,我们以一种可以单独扩展每个服务的方式来设计它们。"

Discord也一路探索了其他技术,Go和Rust是两个例子,结果截然不同。虽然Discord在短时间内完全淘汰了Go,但事实证明Rust是他们工具箱中的一个很好的补充,因为它能够很好地与Elixir和Python配合。

规模化的沟通

在同时处理数以百万计的连接用户时,有效的沟通起着至关重要的作用。从这个角度来看,Discord最受欢迎的一些服务器,如专门用于Fortnite和Minecraft的服务器,已经接近60万用户。在某一时刻,在这些服务器中遇到超过二十万活跃用户也不是不可能的。如果有人改变他们的用户名,Discord必须向所有连接的用户广播这一变化。

总的来说,Discord的通信运行数字令人印象深刻。他们在所有服务器上的并发用户数已超过1200万,每秒向客户端发送的WebSocket事件超过2600万个,而Elixir正在为这一切提供动力。

在实时通信方面,Erlang虚拟机是最适合的工具。

- Jake Heinz,首席软件工程师

当我们问他们的团队 "为什么是Elixir?"时,Jake Heinz给出了一个直截了当的答案。"在实时通信方面,Erlang VM是最好的工具。它是一个非常通用的运行时,有很好的工具和推理来构建分布式系统"。从技术上讲,这种语言是一种自然的适合。然而,Elixir在2015年时仍是一个赌注。"当时Elixir v1.0刚出来,所以我们不确定这门语言会朝哪个方向发展。幸运的是,我们对这门语言的发展和社区的形成都很满意。"

聊天基础设施团队

为了给他们的聊天消息系统提供动力,Discord运行一个有400-500台Elixir机器的集群。也许,最令人印象深刻的壮举是,Discord的聊天基础设施团队由五名工程师组成。没错:五名工程师负责20多个Elixir服务,能够处理数百万的并发用户,每秒推送数千万条信息。

Discord还使用Elixir作为其音频和视频服务的控制平面,也被称为信令,它在用户之间建立通信。然后,C++负责媒体流,这个组合总共在1000多个节点上运行。

Elixir服务之间使用分布式Erlang进行通信,这是作为Erlang虚拟机一部分的通信协议。默认情况下,Distributed Erlang构建了一个完全网状的网络,但你也可以通过设置恰当的-connect_all false 标志,要求Erlang虚拟机将勾勒拓扑结构的工作留给你。Discord团队设置这个选项是为了组建一个部分网格化的网络,由etcd负责服务发现和托管共享配置。

聊天基础设施开发人员并不是唯一接触Elixir代码库的人。根据马克-史密斯的说法,这是Discord文化的一个重要部分。"我们不在孤岛上工作。因此,在构建一个新功能时,Python开发人员可能不得不在Elixir服务上工作。我们会一起规范该功能,弄清楚可扩展性要求,然后他们会在拉动请求上工作,我们会审查并帮助他们迭代。"

社区和挑战

为了在这种规模下运行,Discord学会了如何利用Erlang VM的力量和它的社区,以及何时认识到需要他们自己去解决的挑战。

例如,Discord使用Cowboy来处理WebSocket连接和TCP服务器。为了管理数据突发和提供负载调节,如反压和甩负荷,他们使用GenStage,他们在过去详细讨论过

其他时候,公司和社区的努力携手并进。当Discord使用Rustler项目时就是如此,该项目在Elixir和Rust之间提供了一个安全的桥梁,将规模扩大到1100万并发用户。他们使用Rustler将一个在Rust中构建的自定义数据结构直接挂到他们的Elixir服务中。

然而,该团队非常清楚地表示,动力源是Erlang平台。每当他们不得不推进他们的堆栈时,他们从来没有感觉到被技术束缚住了。恰恰相反,他们的工程师总是能够建立高效的解决方案,以Discord的规模运行,往往只需几百行代码。Discord经常将这些项目回馈给社区,如ManifoldZenMonitor

当事情出错时,Discord团队也迅速适应。例如,他们曾两次尝试在生产中使用Mnesia--一个作为Erlang标准库的一部分的数据库。他们尝试了Mnesia的持久化和内存模式,而数据库节点在故障情况下经常会落后,有时甚至无法赶上。最终,他们完全放弃了Mnesia,用Erlang的内置结构(如GenServer和ETS)来构建所需的功能。现在,他们在2-3秒内就能解决这些相同的故障情况。

掌握Elixir

在加入公司之前,没有一个聊天基础设施工程师有Elixir的经验。他们都是在工作中学习的。团队成员Matt Nowack和Daisy Zhou报告说,他们最初很难理解所有的服务是如何沟通的。马特补充说。"一开始,我很难接受Erlang VM提供的所有保证。我担心数据竞赛和并发问题是不可能发生的"。最后,他们把这些保证放在心上,发现自己的工作效率更高,更有能力依赖这个平台和它的工具。马特继续说。"Erlang VM提供的自省工具是同类中最好的。我们可以查看集群中的任何虚拟机进程,看到它的消息队列。我们可以使用远程shell连接到任何节点并调试一个实时系统。所有这些已经帮助我们无数次了。"

以Discord的规模运行,为掌握语言增加了自己的维度,因为他们需要熟悉提供并发、分布和容错的抽象。现在,Nerves和Phoenix等框架为开发者处理这些问题,但底层的构件总是可以为组装自己的堆栈的工程师所用,例如Discord团队。

最后,Jake总结了Elixir和Erlang VM在Discord的重要性,以及它对他个人的影响。"如果没有Elixir,我们在Discord做的事情是不可能的。在Node或Python中也不可能做到。如果它是一个C++代码库,我们就不可能用五个工程师来构建这个代码库。学习Elixir从根本上改变了我思考和推理软件的方式。它给了我新的见解和处理问题的新方法"。