我们从一个Python Monolith到托管平台的旅程

2021-03-17 01:10:05

Dropbox对我们的客户来说,需要是可靠和响应的服务。作为公司,自从我们的开始以来,我们必须不断扩展,今天在行星的行星上的每个时区服务于700多家注册用户,他们每秒生成至少300,000个请求。适用于初创公司的系统没有缩放,因此我们需要为我们的内部系统设计一个新的模型,以及在不扰乱我们产品的情况下到达那里的方式。

在这篇文章中,我们将解释为什么和我们如何开发和部署的Atlas,一个平台,该平台为服务导向架构提供了大多数优势,同时最大限度地降低了拥有服务的运营成本。

Dropbox的大多数软件开发人员贡献给服务器端后端代码,并且所有服务器侧开发都会在我们的服务器Monorepo中进行。我们主要使用Python为我们的服务器端产品开发,拥有超过300万行代码,属于我们的单片Python服务器。

它有效,但我们意识到雄芯也在我们长大的时候拿着我们。开发人员每天都会带来整体的意想不到的后果。他们写道的每一行代码是,无论他们是否想要,共享代码 - 他们没有选择智能分享的东西,以及最好地将孤立的终端分享到一个端点。同样,在生产中,他们的终点的命运与其他终点的稳定性,临界或所有权水平相关联。

在2020年,我们运行了一个项目来分解整料并将其发展成为一个无服务的管理平台,这将减少纠结的代码和解放服务以及他们的底层工程团队彼此缠绕。为此,我们必须创新架构(例如,在GRPC上标准化,并使用SEVOY的G RPC-HTTP转码)和操作(例如,引入自动阶层和金丝雀分析)。此博客文章从我们的旅程中捕获了关键的想法和学习。

Dropbox的内部服务拓扑可以被认为是“太阳系”模型,其中纪念碑提供了许多产品功能,但是平台级组件如身份验证,元数据存储,文件系统和同步已分开进入不同的服务。

大约一半的所有提交到我们的服务器存储库修改我们的大型单片Python Web应用程序,MetaServer。

Metaerver是我们最古老的服务之一,由我们的联合创始人其中一个创建。它良好地提供了Dropbox,但随着我们的工程团队在多年来送出新功能,Codebase的有机增长导致了严峻的挑战。

Metaerver的代码最初是以简单的模式组织的代码,可能期望在一个小开源项目库,型号,控制器中看到 - 没有集中策策或护栏,以确保Codebase的可持续性。多年来,MetaServer CodeBase增长成为公司最具禁用和纠结的码条之一。

因为Codebase有多个团队工作,因为没有单一团队对CodeBase质量的拥有强有力。例如,要取消阻止产品功能,团队将将导入周期引入代码库而不是重构代码。即使这让我们在短期内发货代码速度更快,它留下了要较少的码级,并将问题变得更加复杂。

我们将MetaServer推向每天所有用户的生产。不幸的是,随着数百种开发人员有效贡献相同的代码库,每天至少增加一个危急错误的可能性已经变得相当高。这将需要回滚和樱桃拣选整体雄性料,并对开发人员带来不一致和不可靠的推动力。常见的最佳实践(例如,从加速)指向快速,一致部署为开发人员生产力的关键。我们无处可见在这个维度上的理想。

不一致的推动力导致发展经验中不确定的不确定性。例如,如果开发人员正在逐天开发产品发布,则它们不确定是否应按日x-1,x-2甚至更早地将其代码提交给我们的存储库,因为另一个开发人员的代码可能导致a在DAY X上的不相关组件中的关键错误,并将整个群集的回滚与其自己的代码完全无关。

用数百万码代码的单片,基础设施改进需要更长或从未发生过。例如,只有在仅在非关键途径上逐步推出新版HTTP框架或Python的推出。

此外,Metaserver使用大多数其他Dropbox服务中未使用的传统Python框架或外部其他任何地方。虽然我们的内部基础架构堆栈演变为使用GRPC等行业标准开源系统,但Metaserver陷入了不推出的遗产框架上,以至于不出所知的性能差,导致由于深度漏洞导致维护头痛。例如,遗留框架仅支持HTTP / 1.0,而现代库已将HTTP / 1.1移动到最低版本。

此外,我们在内部基础架构中开发或集成的所有好处,如我统一的指标和追踪,必须为Metaserver重做,这是在不同的内部框架内建造的。

在过去的几年里,我们削弱了几个工作流来打击我们所面临的问题。并非所有人都成功,但即使是那些我们放弃的人铺平了我们目前的解决方案。

我们试图将Metaserver分解为围绕服务面向服务的架构(SOA)主动的一部分。 SOA的目标是建立更好的抽象和对Doplbox的功能的关注 - 我们想要在Metaserver中解决的所有问题。

执行计划很简单:使团队能够在生产中运营独立服务,将碎片雕刻成独立服务。

可以易于在Metaserver提取核心功能之外的易于构建服务,如来自Monolith的身份管理,并通过RPC公开它们,以允许在Metaserver外建立新功能

建立最佳实践和生产准备过程,用于顺利,可伸缩地换行新的多项服务,即面向客户的流量,即我们的实时服务

SOA努力证明是漫长而艰巨的。经过一年多,一半,我们进入了第一个里程碑。然而,执行第一个里程碑的经验暴露了第二个里程碑的缺陷。随着更多的团队和服务被引入客户交通的关键路径,我们发现维持高可靠性标准越来越困难。当我们向核心功能迁离堆栈并要求产品团队运行服务时,此问题只会复制。

通过这种洞察力,我们重新评估了这个问题。我们发现Dropbox的产品功能可分为两种广泛类别:

例如,“共享”服务涉及围绕访问控制,速率限制和配额的状态逻辑。另一方面,主页是我们元数据存储/文件系统服务周围相当简单的包装器。它通常不会改变,并且日常运行负担和失败模式非常有限。事实上,Dropbox提供的大多数路线的运营问题都有共同的主题,如意外的外部流量尖峰,或在基础服务中的中断。

小型,自包含的功能不需要独立操作的服务。这就是我们建造了地图集的原因。

对于产品团队来规划容量,建立良好的警报和多宿主(在多个数据中心自动运行)是不必要的,以实现小型简单功能。团队主要想要一个他们可以编写一些逻辑的地方,当用户击中某个路线时,它会自动运行,并在其路由中有太多错误获取一些自动基本警报。他们提交给存储库的代码应始终如一地部署,快速和不断地部署。

我们的大多数产品功能都属于此类别。因此,Atlas应该优化此类别。

大型系统可由可持续管理其系统健康的大型团队运营。团队应管理自己的推送计划,并设置专用的警报和验证。

凭借HESAServer的基本可持续性问题,以及将Metaserver迁移到许多较小的服务的学习不是对所有内容的正确解决方案,我们提出了ATLAS,一个用于自包含功能用例的托管平台。

阿特拉斯是一种混合方法。它提供了用户界面和经验,就像AWS Fargate的“无服务器”系统到Dropbox产品开发人员,同时在幕后自动配置服务时备份。

正如我们所说,地图集的目标是为SOA提供大部分福利,同时最大限度地减少与运营服务相关的运营成本。

ATLAS是“管理”,这意味着开发人员在Atlas中编写代码只需要编写界面和实现它们的端点。然后,Atlas负责创建生产群集以提供这些端点。地图集团队拥有推动并监控这些集群。

代码结构改进Metaerver在代码共享上没有真正的抽象,从而导致耦合代码。高度耦合的代码可能是最难理解和重构,并且最有可能在修改时发出错误。我们希望引入结构并减少耦合,以便新的代码更易于阅读和修改。

独立的,一致推动Metaerver推动体验很大。产品开发人员只需要担心检查代码,这些代码将自动推动到生产。然而,上述缺乏推动隔离导致了不一致的经验。我们想创建一个平台,因为由于不相关的代码中的错误,团队没有被阻止,并为团队推动他们将来推动自己的代码的基础。

最小化的运营频繁工作,我们旨在保持元旦的运营益处,同时提供服务的一些灵活性。我们设置了自动容量管理,自动警报,自动金丝雀分析和自动推送过程,以便从单线迁移到托管平台的迁移对于产品开发人员来说是平滑的。

基础设施统一我们希望统一所有服务于GRPC等标准开源组件。我们不需要重新发明轮子。

隔离有些功能,如主页比其他功能更重要。我们想独立服务于这些,以便一个功能中的过载或错误无法泄露到其余的Metaserver。

我们使用现成的解决方案进行评估以运行平台。但是为了使我们的迁移和确保低的工程成本降低,我们对我们继续托管服务的其他部署编排平台,它是有意义的。

但是,我们决定删除自定义组件,例如我们的自定义请求代理绑带,并用符合我们需求的特使等开源系统替换它们。

每个组件强制执行单个所有者,因此无法通过非所有者将新功能加到组件上

自动将每个组件配置为在我们的部署编排平台中的服务中,其中包含< 50行的样板代码

配置代理(特使)以向正确服务发送特定路由的请求,而不是简单地向元旦节点发送每个请求

自动配置每天运行的部署管道,然后按下每个组件的生产

设置自动警报和自动分析每个推送管道的回归,以便在任何问题的情况下自动暂停和回滚

自动分配其他主机以通过基于流量的每个组件的自动播放器来扩展容量

通过Servlet Atlas逻辑分组路由介绍Atlasservlet(发音为“atlas servlets”)作为路由的逻辑原子分组。例如,HomeAtlasservlet包含用于构造主页的所有路由。 NAV Atlasservlet包含Dropbox网站上导航栏中使用的所有路由。

为准备atlas,我们与产品团队合作,将Atlasservlet分配给Metaserver中的每条路线,导致超过5000个路线的200多个atlasservlet。 Atlasservlets是分解MetaServer的重要工具。

每个atlasservlet都在codebase中给出了一个私有目录。 atlasservlet的所有者具有此目录的完全所有权;他们可能会组织它,但他们希望,没有其他人可以从中导入。 Atlasservlet代码结构固有地打破了MetaServer代码纪念碑,要求每个端点都处于私有目录中,并使代码共享显式选择,而不是为巨大的贡献的意外结果。

编码到我们的目录路径中的Atlasservlet还允许我们自动生成通常伴随生产服务的生产配置。 Dropbox使用Bazel Build System for Server Side Code,我们通过Bazel功能强制预防导入,称为可见性规则,允许库所有者控制哪些代码可以使用其库。

进口周期的分解为了分解我们的代码库,我们不得不打破大部分Python导入周期。这花了几年的时间来实现一堆剧本和大量的咕噜声,重构。我们通过相同的Bazel可见性规则防止了回归和新的进口循环。

默认情况下,默认情况下,不正确的atlasservlet的行为不当路由只会影响与同一团队拥有的相同的atlasservlet。

独立推动每个atlasservlet都可以单独推送,将产品开发人员置于他们推动的一致性方面控制自己的命运。

一致性每个atlasservlet都会看起来像Dropbox的任何其他内部服务一样。因此,我们的基础架构团队提供的任何工具 - 例如。定期性能分析 - 将为所有其他团队的Atlasservlets工作。

GRPC服务堆栈我们与阿特拉斯的目标之一是统一我们的服务基础设施。我们选择在GRPC上标准化,在Dropbox中得到广泛采用的工具。为了继续为HTTP流量提供服务,我们使用了在特使,我们的代理和负载均衡器中提供的GRPC-HTTP转码功能。您可以在各自的博客文章中阅读更多有关Dropbox的Grpc和Emenoy的信息。

为了促进我们对GRPC的迁移,我们编写了一个适配器,该适配器采用现有端点,并将其转换为GRPC预期的接口,即端点预期的内存状态的任何传统的内存状态。这使我们可以自动执行大多数迁移代码。它还具有在中间迁移期间将端点与Metaserver和Atlas保持兼容的好处,因此我们可以安全地移动实现之间的流量。

阿特拉斯的秘密酱是托管经验。开发人员可以专注于写作功能,而无需担心运行生产服务的许多操作方面,同时仍然保留了独立服务的大多数福利,如隔离。

显而易见的缺点是,一支球队现在承担了所有200多个集群的操作负荷。因此,作为阿特拉斯项目的一部分,我们建立了几个工具,以帮助我们有效地管理这些集群。

自动金丝雀分析元旦(和扩展Atlas)无状态。结果,其中最常见的方法之一被引入系统是通过代码更改。如果我们可以确保我们的推送护栏尽可能通过密封,这消除了大多数失败情景。

我们通过与Netflix的Kayenta非常相似的简单金丝雀分析服务自动检查我们的故障检查。每个地图集服务都包含三个部署:金丝雀,控制和生产,金丝雀和控制只接收小随机的流量。在推送过程中,用新版本的代码重新启动金丝雀。使用旧版本的代码重新启动控件,但同时为金丝雀,以确保从同一起点操作。

我们自动比较CPU利用率等度量标准,从金丝雀和控制部署路由可用性,寻找金丝雀可能相对于控件的回归度量。在一个良好的推动中,金丝雀将以等于或更好地执行,并且将允许推动进行。将自动停止糟糕的推送,并且所有者通知所有者。

除了金丝雀分析外,我们还有警报设置,在整个过程中检查,包括在金丝雀,控制和生产推动的单个集群之间。如果出现问题,这让我们自动暂停并回滚推送管道。

错误仍然发生。糟糕的变化可能会滑过。这是Atlas的默认隔离的地方,方便。破碎的代码只会影响其一个群集,并且可以单独回滚,而不会阻止代码为组织的其余部分推动。

自动阶乘和容量规划Atlas' S群集策略导致大量的小簇。虽然这很好的隔离,但它显着减少了每个群集必须处理流量的增加。巨石是大型共用集群,因此路线上的小RPS增加很容易被共享集群吸收。但是,当每个atlasservlet都是自己的服务时,路线流量的10倍增加更难处理。

200多个集群的容量规划将使我们的团队瘫痪。相反,我们构建了一个自动播放系统。 AutoStoPer实时监视每个集群的利用,并自动分配机器,以确保我们每群集保持高于40%的可用容量净额。这允许我们处理流量增加以及删除需要执行容量规划的需要。

自动播放系统从Senvoy的加载报告服务中读取指标,并使用请求队列长度来决定群集大小,并且可能值得自己的博客文章。

由于CodeBase的大小和复杂性,因此许多以前的改善Metaser的努力没有成功。这一次,即使我们没有成功地用地图集地完全取代Metaserver,我们也希望为产品开发人员提供价值。

Atlas的执行计划是使用踏脚石设计的,而不是里程碑(以前的Dropbox工程师James Cowling典雅地描述),因此每个增量步骤都会提供足够的价值,以便在项目的下一部分出于任何原因。

通过加快MetaServer中的测试框架,我们开始了,因为我们知道测试中的堆栈可能导致测试时间的回归。

当我们从MetaServer迁移到Atlas时,我们有一个约束,可以显着提高内存效率,并减少OOM杀戮,因为我们能够在迁移过程中收集更多的进程并在迁移期间消耗更少的容量。我们专注于纯粹地将内存效率提供给Metaserver,而不是将改进绑定到Atlas Rollout。

我们设计了一个负载测试,以证明Atlas MVP能够处理MetaServer流量。我们重中使用了负载测试,以验证Metaserver在新硬件上的性能作为不同项目的一部分。

我们向工作流程为代替的工作流程简化。例如,我们将Atlas的一些工作流程改进为Metaserver中的Web工作流程。

MetaServer开发工作流程分为三类基于协议:Web,API和Internal GRPC。 我们首先将地图集聚焦在内部GRPC上,使新的服务堆栈失败,而不需要更有风险的部分 ......