构建功能齐全的电子邮件服务器

2020-10-29 10:28:32

欢迎收看“信号与线索”栏目,我们将深入探讨简街的每一层技术。我是罗恩·明斯基。好的,今天我很高兴能坐下来和多米尼克·洛布雷科谈谈电子邮件的问题。特别地,我们将讨论由Dominick设计并领导开发的一个名为Mailcore的系统,它是Jane Street自己开发的邮件服务器。我认为这本身就很有趣,因为电子邮件是一个有趣的话题和它背后的整个体系结构,但我认为它也是一个镜头,让我们了解一些关于软件设计和如何管理基础设施的有趣问题,一些关于你如何选择何时构建自己的东西和何时使用标准的现有工具的问题,以及一些关于编程语言如何在系统设计中发挥作用的有趣问题。

嘿,DLO。那么,在开始之前,你能告诉我们一些关于电子邮件是如何工作的吗?

好的。嗯。因此,电子邮件是基于互联网上一种古老而古老的协议,称为简单邮件传输协议,即smtp和smtp…。你可以在某种程度上认为它扮演着邮政服务在递送普通邮件方面所扮演的角色。它是一台想要将消息传递到某个地方的服务器将该消息传递给另一方的一种方式,该另一方可以将消息带到其最终目的地,无论那是最终目的地服务器本身还是可以帮助您更近一点的某个中介。正如我们今天所知,电子邮件本身是在互联网的早期实现的,协议本身非常简单。您基本上有了消息本身的实际正文,它有自己单独的格式和规范,然后您有一组指令来表示该消息的目的地是谁,它来自谁,因此一台服务器连接到另一台服务器。上面写着,“我有一条信息。它是从某某传来的,而且是要送到其他人手中的。这是消息的正文,“接收服务器可以随心所欲地处理它。它可以说,“太好了,我接受了,从现在开始我要负责这件事。”它可以说,“不,我对那个人一无所知。你必须找到其他人把它送到“或者因为其他任何原因拒绝它,比如,”这看起来像是有病毒的“,或者”你不允许连接到我“,或者”我现在不能接收邮件“,”我现在不能接收邮件“,或者”这看起来像是有病毒的“,或者”你不允许连接到我身上“,或者”我现在无法接收邮件。“。

电子邮件一直给我留下深刻印象的一件事是,它是来自早期互联网的一种奇妙的艺术品,互联网是一个真正开放的社交网络。人们谈论的事情很多,对吧?我们能不能让现有的社交网络变得更好、更开放,所有这些,以及电子邮件,都是从它最初的设计开始的;它的完整历史是非常开放的,正如你所指出的,核心协议和传输相对简单,尽管RFC实际上有惊人的复杂性告诉你如何解析一封特定的电子邮件。整个系统非常简单,但所有构建实际管理和传输电子邮件的系统的不同参与者,以及他们如何处理发生的各种问题,比如垃圾邮件和通过电子邮件攻击系统的人等等,都有很大的复杂性。所以基础是相对简单的,但是系统的紧急复杂性实际上是相当高的。

与旧互联网的许多协议一样,它是在世界比今天简单得多的时代设计的,特别是在连接互联网的世界。你知道,当时可能有50家机构有互联网连接或ARPANET连接,你真的不必担心会有人发垃圾邮件,因为几乎没有人知道电子邮件是什么。

当你开始并构建一个新的东西时,你构建的东西的早期属性通常非常粘性,以一种难以预测的方式真正起到重要作用。因此,开放的这一早期属性一直停留在那里。电子邮件是任何人都可以参与的事情。组织可以构建自己的基础设施来连接它,并且通过电子邮件系统经历的所有相当大的变革,这种开放性仍然是一项核心属性。这是设计来建造新东西的可怕之处,当你想设计新东西时,你必须做出一系列选择,显然,你不应该太担心它们,因为你建造的东西可能会失败,也不会成功,即使它成功了,你也会在以后了解到更多的问题,所以你不应该太担心早期的决定。但也有一些早期的决定,你不知道哪些会被证明是很难改变的。

事实上,你知道,今天电子邮件领域的主要参与者-显然是谷歌和Gmail-在电子邮件市场中占据了相当大的比例

所以你可能会认为,与其他任何公司相比,简街使用电子邮件的方式真的没有太多特别之处,在很大程度上,这是真的。我想我们有一些特殊的要求,因为我们是在一个受监管的行业,所以我们有一些要求,为了合规的目的,记录简街某人发送或接收的每一条消息。但除此之外,我们的电子邮件系统看起来非常类似,或者说过去看起来非常类似于任何组织中的电子邮件系统,粗略的总结是,我们在网络外部有一些邮件网关,用于接收来自外部服务器的电子邮件,您知道,来自外部各方的电子邮件,然后我们有一些邮件服务器,或者一组服务器,在我们的网络内部,处理关于如何处理这些消息的所有复杂的业务逻辑。因此,在某些情况下,如果我们是预期的收件人,只需接收邮件并将其发送到用户的邮箱即可。在其他情况下,它对垃圾邮件、病毒和其他我们可能希望在传递邮件之前从邮件中提取内容进行过滤,对邮件列表进行扩展。因此,如果您向简街的某个群发电子邮件,您希望能够将群名扩展到实际的收件人邮箱列表,以确保该电子邮件最终进入收件人的收件箱。然后是确保我们使用所有正确的元数据记录所有正确的消息的额外遵从性含义。在我开始的时候,这里的邮件基础设施都是基于开放源码的邮件服务器,它有自己的配置语言,在互联网上被广泛使用,在最复杂的情况下,我们有大约400或500行配置,我想,这个系统要让它做我们想让它能够做的所有这些不同的事情。

太棒了。因此,就如何构建自己的邮件系统而言,这听起来是一个合理的方法。我们用它遇到了什么问题?

是的,归根结底,这里最大的问题是配置这个系统来做我们需要它做的所有事情所需的复杂性。所以,现在,我说的是400或500行配置-这听起来可能不是一个很大的数字,但当它是一种定制的配置语言时,它不同于任何其他系统的配置,也不像简街的开发人员或工程师熟悉的任何编程语言,400或500行外语的复杂性相当大,处理起来可能有点麻烦。特别是,我们有一些可怕的险些,我们意识到我们在归档一些本应归档的电子邮件方面做了错误的事情,幸运的是,在每一种情况下,都有一些缓解因素,最终不会是什么大问题,但这种险些让我们有点害怕,因为我们查看了配置,想要了解我们是如何让自己陷入这种境地的,这比我们感觉到的更难理解哪里出了问题,以及如何修复它。

也许还值得一提的是,出于合规目的记录所有消息的问题听起来可能很容易,但简街是一家在许多不同监管制度下运营的公司,实际上对它运营的一些不同的地方有不同的规则,这一事实让问题变得更加复杂。因此,即使是那种看似简单的“让我们把一切都写下来”,也比乍看起来要复杂得多。

是的,是的。我们有不同的要求,关于必须写下什么,我们需要存储什么类型的元数据,额外的副本需要在世界各地的物理位置等等,当你考虑到人的方面时,这听起来很合理,你知道,当你思考,好的,是的,你需要一个副本来做这个和那个,但是实际执行规则最终会非常复杂。

所以,激励你尝试和尝试新事物的其中一件事就是这种险些失手的情况,事情几乎走上了可怕的歧途。你想尝试不同的东西还有其他原因吗?

正如我所说的,其中一个方面当然是意识到系统的复杂性已经到了我们实际上不敢对其进行更改的地步。另一个原因是它需要这种专业知识。我们有一个团队--在当时,我们比现在小得多--但你知道,即使在今天,我们的团队也主要由多面手组成,他们能够解决许多不同类型的问题,并且在某一技术领域拥有广泛的背景。了解的配置

我认为这是一个相当常见的模式的示例,您可以在许多旨在高度灵活和可配置的系统中看到这种模式。它们从处理基本功能的相对简单的核心开始,随着时间的推移,当它们试图向系统添加更多功能时,它们会添加越来越多的旋钮可供您转动,并在配置语言中添加越来越多的配置参数或元素,以便能够表达您可能希望表达的所有不同内容。在这种特殊情况下,配置语言是专门为该系统开发的定制的、特定于领域的语言。在某些地方,它有点像老式的.INI格式,它有一个键,然后是一个等号,然后是一个值和用括号中的标题分隔的部分,诸如此类的东西,但是当你仔细看的时候,你会意识到它上面有所有这些额外的功能。因此,它特别支持这些看起来有点像函数调用的高级宏,在这些函数调用中,您可以使用一些参数集调用宏,然后它会扩展到其他东西,这些配置元素有不同的扩展阶段,在这些阶段中,您可以进行这种元编程,或者您可以使用宏来生成宏,然后将这些宏扩展为一些结果值,最重要的是,配置中所需的字段集以及这些元素之间的交互并不是非常清楚,而且也不是非常一致。因此,例如,您可能有一个部分定义了路由消息的方式、决定特定传入消息应该发送到何处的方式、是将其发送到邮箱还是将其中继到某个其他服务器,并且您可以定义多个路由器,并且配置语言并没有明确说明为给定消息选择哪个路由器的语义。还有很多其他类似的例子,其中有一些您定义的元素集,系统如何选择在给定情况下应用这些元素的语义,从表面上看,从配置上看并不是明确和清晰的。你只需要知道。您必须阅读文档,了解这些东西是如何相互作用的。

对,选择在特定情况下触发哪些特定规则的规则,我假设这些规则本身并不简单?

它们并不简单,在某些情况下,有很好的理由。我的意思是,值得一提的是,这个系统非常非常灵活,它可以做我们当时想要做的所有事情,但最终,你需要扭曲自己的方式来理解它将如何做到这一点,以及如何将这些不同的部分组合在一起,这需要对特定系统的语义有专家级的知识。

所以你有一个明显的问题摆在你面前。您决定采用什么方法来解决这个问题?

最终,我们决定做的是一件表面上听起来很疯狂的事情,编写我们自己的电子邮件服务器,特别是,我们用OCaml编写了一个新的电子邮件服务器,这是我们在简街使用的函数式编程语言,关键的-也可能是最有趣的部分-是系统总是用OCaml配置的。我们在这里遇到的真正问题是,我们对我们正在使用的旧系统的核心功能感到满意,但是配置语言是我们感觉到的真正限制我们的语言。我们从根本上认识到,最终,电子邮件服务器的角色,你可以认为是一种功能。您可以将其视为实现某种功能的黑盒,该黑盒接受一条消息,并输出一个或多个结果消息,该黑盒负责做出有关如何转换这些消息以及如何将这些消息路由到进一步的服务器或收件箱的所有决策。最终,您可以将所有内容封装在一个大致类似的函数中。就像我说的,OCaml是一种函数式语言,正如你知道的,它真的很适合用这种方式编写函数,你可以把它们组合在一起,实现一些最终需要一些输入和产生一些输出的功能,而不会产生任何副作用,这就是我们意识到我们需要的,所以我们开始了这条道路。

设计中的这个简单的支点,让您可以绕过这种自定义语言的所有复杂性,您只需在OCaml的中间选择一个真正经过深思熟虑、精心设计的抽象(即函数),然后使用您在那里拥有的用于软件组合的普通工具来构建您想要的抽象,然后就可以让您摆脱不得不考虑出现在我们面前的奇怪、复杂、特殊情况的问题

令人尴尬的是。没错,但另一种方法可能被称为配置生成方法,也就是说,好的,有一个简单的核心配置语言,上面有一堆复杂的东西,这是关于增加语言的通用性。让我们忘掉所有那些可怕的东西,然后用另一种语言编写代码,在那里我们有更好的抽象和更好的工具,让它只用这种简单的核心演算来生成东西,这种简单的核心演算暴露在底层的配置生成语言中,然后我们就可以两全其美了。我们可以用一种我们很好理解的高级语言来编写我们的配置,这不是一种特殊用途的技能,我们可以使用由其他人构建和维护的核心引擎,我们不需要重新实现它。那么,为什么这不是您使用Mailcore选择的路径呢?

我认为有三个原因。我认为有两个理由是好的,一个是不好的。我将从充分的理由开始。首先,当时我们真的对我们使用的系统提供给我们的原语不满意。所以配置语言是复杂的,即使是在它最简单的形式下,我们也没有一些好的原语可以用来工作,我们只需要生成这些原语,我们可以用这些原语做任何事情,其他的一切都建立在它的基础上。我们必须生成复杂的宏和一些我在前面谈到的配置元素,而且我们觉得通过生成这些元素而不是手动编写它们并不能节省太多时间。事实上,我们也同样需要理解它。这不像我们可以将我们的理解限制在语言的一个子集上,然后使用它来实现我们需要的所有东西。这是第一个原因。第二个原因是我们确实想要一些运行时的动态性。在某些情况下,我们确实希望能够根据环境中的其他东西、世界上的其他东西以及我们必须生成的配置来实际改变行为,这样我们就会回到完全相同的位置。

.