[关闭]
@sambodhi 2017-03-02T10:51:36.000000Z 字数 17078 阅读 2192

Evernote的一次云迁移手记

Google Cloud又添大客户。著名的云笔记服务商Evernote在过去的70天里,把他们原来部署于自建数据中心的服务全面迁移至Google云平台。为此,Evernote在他们的官博上发表了5篇博文,详细介绍了这次迁移的动机、过程、结果和对未来的展望。

InfoQ翻译并整理本文。

第一部分:Evernote的服务和迁移至Google云平台的计划

自2008年启动服务以来,Evernote并配置和维护自己的服务器和网络。我们因此能够按照自己的想法构建我们的服务。但这也带来了限制:难以扩展,升级缓慢,维护成本高。虽然我们的基础设施在当时为Evernote提供了很好的支持,但它缺乏我们所需要的速度和灵活性。

迁移到公共云,对Evernote所有人员而言是一个令人兴奋的决定。自从发布第一次公告开始,我们一直在背后默默地工作,完成从我们的物理数据中心迁移到我们在Google云平台(GCP)的新家。我们花了70天的时间,完成了这一伟大的全面迁移过程。

那些想要了解这次迁移过程或者想要进行类似迁移的人,我们希望能够将我们所做的工作分享给你们。这并不是一个完整的迁移运行手册,不过它涵盖了我们的迁移决策和实现过程。

我们先从Evernote服务的概览说起。

Evernote的服务

我们的服务包括以下构件块。

分片(NoteStore)

这是Evernote容量和服务的核心单元。这个构建块为用户笔记提供存储支持。每个分片可以支持最多20万Evernote用户。一个分片包含:

其中的762个分片存储着2亿个用户账号和大约50亿个用户笔记。

用户存储(UserStore)

基于MySQL的中心数据库,集中存储所有的用户信息和他们的认证信息。由于此数据库管理着所有的用户状态和身份验证,因此它是服务中最关键和最复杂的部分,我们对它总是格外地小心。

用户附件存储(资源)

我们有一个单独的文件存储层,用于存储50亿用户的附件(我们称之为资源)。这包括206个自包含的WebDav服务器。用户上传的附件会被保存到本地的两个WebDav服务器上,同时会有一个副本被发送到远程的WebDav服务器上,这些服务器位于离岸的灾备数据中心。

前端负载均衡

我们运行高可用性的负载均衡集群,它加速和终止TLS,并负责将用户的请求路由到他们的笔记所在的后端分片上。

支持服务

最后,我们还有约200台Linux服务器执行缓存和批处理功能,如笔迹和文本识别。

我们的迁移选择

随着Evernote规模的增长,迁移到云端总是一个复杂的工作,需要做出多个相关决策。我们希望能够进行快速的迭代,所以我们采取了基于关键性战略决策建立了一个草案。然后,我们尽可能地对这个草案的可行性进行测试。我们因此能够快速迭代我们的计划。

我们从理解需要作出变更的地方开始,知道会有一些组件不能被简单(或直接)地迁移到云端。我们将我们环境的组件分成两类:

迁移方法

然后,我们需要考虑我们的迁移选项。对于Evernote,有两个明显的选择:

根据我们对当时处境的评估,很显然,一次性切换方案并不适合我们。即使有最好的规划,我们也会承担太多的风险。我们的应用程序没有被设计成可以支持分布式运行,尽管有迹象表明我们的运行环境可能早就支持分布式。

因此,我们需要找到两个极端之间的一个折衷。我们想,如果可能的话,可以实施被我们称为“增速阶段性切换”的方案。整个服务迁移将在不到20天的时间内完成,整个迁移过程包含多个阶段,每个阶段可以把风险降到最低,以最小化每个步骤的风险。这个计划也包含了回滚点,如果迁移工作无法按照预期进行,可以进行回滚。

第2部分:保护GCP上的客户数据

我们的安全团队的宗旨是保护客户数据。当我们开始将项目迁移到云基础设施(特别是Google)上时,我们要确保在迁移到GCP的同时保证客户数据的安全。我们主要面临两个:

  1. 将我们的数据交给Google保管,他们是否有足够成熟的安全控制措施措施,不会给我们的服务带来额外的风险?
  2. GCP是否给予我们同等或更好的安全控制措施,用来保护客户数据?

供应商信任

我们有一个内部供应商审核流程,包括我们的法律和安全团队。总之,我们审查与新服务提供商相关的隐私和安全风险,以帮助我们在Evernote服务中提供功能。

当我们审查一个供应商,我们会检查涉及安全和隐私的60多个条例。这些内容与我们内部程序的结构保持一致,通过这些审查,我们可以发现供应商是否偏离了我们的期望。这些内容主要包含:

我们与Google一起审查他们的审计报告,并讨论一些额外的技术问题。我们发现,不管在哪个方面,他们都符合我们的预期。

接下来,我们将评估工作的重点放在他们是否为我们提供了保护客户数据安全性的措施上。

云安全控制措施

我们的审查从检查我们现有基础设施中保护客户数据的安全措施开始。这些控制措施包括保护功能,如具有双因子认证身份验证的远程访问VPN和允许我们执行流量过滤的防火墙。他们还包括许多物理安全控制措施,如一个良好的物理外围、生物识别身份验证、监控探头和防止物理数据被窃取的报警系统,防止物理数据被窃取。我们在我们的安全主页 上谈论了很多关于这些基础设施的安全保护措施。

我们把问题列在表格里,开始讨论如何将我们数据中心的每一个组件迁移至云端。我列出几个方面的内容作为例子:

我们还考虑到在多租户云环境中运行会引入新的威胁模型。我们现在需要关注内存和存储的使用,这与我们过去有所不同。我们还需要考虑来自同一虚拟层其他用户的威胁。幸运的是,Google已经考虑到了这些威胁模型,并在博客中发表文章讨论他们如何解决问题 。

对于大多数控制措施,我们在云平台上找到了等效的解决方案。对于静态数据加密,我们获得了一个之前没有使用过的安控措施。对于某些控制措施,比如IP白名单,我们不得不调整我们的安全架构,从而不依赖于传统的网络控制措施。我们通过使用包含了Google托管密钥的GCP服务帐户来完成此操作。

如何安全地使用GCP安全账户

当迁移到云端,CIDR块会变成静态公共IP和动态公共IP的混合体。维护IP白名单会变得非常困难,而且Google的其它云平台服务并没有使用IP白名单。

我们建立的安全控制措施,在互联网和客户数据之间至少有两个安全层。在我们以前的架构中,我们有一个定义明确的网络外围,我们将所有内部服务都包含在内。这些内部服务使用API密钥相互通信。我们仍然以安全的方式存储和分发这些密钥,但意识到存在泄漏或被盗的可能。如果真的发生了泄露或被盗,我们还有第二层的控制措施,因为你不能在生产环境之外使用这个密钥。访问该生产环境需要双因子身份验证。

Google的每个GCP服务都是互联网服务,而且这些服务都不具备面向用户的白名单访问控制措施,用于访问Google Compute Engine(GCE)项目中的主机。我们需要找到一种方法,在被盗的API密钥和客户数据之间添加另一层安全层。

我们通过使用GCP服务帐户解决了这个问题。每个GCE项目都会获得默认服务帐户。在GCE中启动的任何实例都可以模拟该服务帐户访问其他服务。在后台,Google管理着一组公钥/私钥对,并且每24小时自动轮换这些密钥。他们对自定义服务帐户执行相同的操作。我们可以为每个计算机角色创建自定义服务帐户,并配置虚拟实例以使用相应的服务帐户。现在,使用GCP软件开发工具包(SDK)在该虚拟实例上运行在这些虚拟实例上的应用可以使用内置的Google托管密钥。

我们的运维工程师从来没有访问这些密钥的必要。由于Google每天自动轮换这些密钥一次,因此访问它们的唯一方法是渗透我们的基础架构,而我们已经做好了足够的保护措施。

总体而言,我们对目前已有的云安全平台充满信心,并将继续寻求扩展该平台,以进一步增强安全性并在与安全威胁的竞赛中保持领先。

第三部分:GCP中的Evernote架构和我们的技术转型

我们的系统架构

我们的下一个重大决定是对我们将要建立的整体系统架构达成一致。我们认为,以下是重要的要求/注意事项:

考虑到所有这些考虑因子,我们解决了以下问题:

我们将在US-West1的两个区域之间进一步对生产服务进行拆分,以进一步防止故障。

我们知道一个事实,就像Google解释的那样“Google将区域设计为彼此独立:区域通常具有与其他区域隔离的电源、冷却、网络和控制面板,大多数单个故障事件将仅影响单个区域”。

这意味着我们将处理来自US-West1之外的流量,并在US-Central1中保留所有客户数据的第二个副本。在灾难击中US-West1的情况下,我们将能够利用US-Central1中的数据恢复服务。

我们的一些未来考虑围绕着如何重新构建应用程序以更有弹性,以及我们如何能够同时服务多个区域的流量,以进一步减少从灾难中恢复所需的时间。我们还在考虑如何更好地利用GCP的全球基础设施来降低访问Evernote服务时的用户延迟。

在这一点上,我们已经提出了需求,并做出了一些战略决策。我们现在需要进入详细的工程阶段。

技术改造

最大化我们的数据中心到GCP的网络连接

我们很早就在项目中确定,我们现有的数据中心和GCP之间的网络连接将是我们成功的关键和约束。为了使我们能够最大限度地灵活迁移数据和服务,网络互连计划需要实现以下目标:

  1. 加密我们当前数据中心和GCP之间的流量。
  2. 能够将任一位置用作客户流量的“前门”,或者根据需要在两个“前门”之间分配流量。
  3. 能够通过使用我们物理数据中心的部分组件和GCP上的部分组件来对每个位置的服务进行分离。
  4. 最大化站点之间的带宽以启用大块数据复制。

我们需要最大的灵活性,以确保我们可以通过现有的数据中心与物理负载均衡平台上运行100%的“前门”流量,与此同时,所有后端的Evernote服务,运行在GCP上,并且还同时将3Pb数据复制到GCP。

我们现有的外部网络连接旨在处理我们的峰值负载,具有足够的余量(峰值的数量级)。然而,它没有设计足够的容量,以便及时清除我们的所有数据。

此外,我们的内部网络结构并不会将大量的请求提供给外部服务(例如Google云端存储)。鉴于当前的状况,上传我们的所有数据将需要一年多的时间,同时对我们的用户体验造成负面影响。我们有很多工作要做。

首先,我们必须建立专用网络互连(PNI),或Evernote的网络和GCP之间的直接连接。这有效地将我们可以使用的带宽量增加了一倍,并为我们提供了独立于用户流量的专用连接。这在Evernote和Google之间建立了一条私有通道。

其次,我们必须确定数据中心在哪里,数据来自哪里。我们存储一切的多个副本,所以我们需要找出我们将使用的副本。我们必须通过我们的网络,以最有效地从数百台服务器移动数据,并将其放在私人高速公路上的GCP,而不影响正常的运行。这涉及服务器数据复制作业的仔细协调和排序,以及通过一系列负载平衡器和代理服务器对请求进行路由,以确保它从网络中获取正确的路径。

在项目的第一个月,我们的网络工程团队正在努力保持领先于即将开始的数据副本(和其他任务)。如果他们没有及时交付,整个项目将面临风险。

我们可以运行拆分站点吗?

到目前为止,我们的应用程序只在单个数据中心内运行。在这种环境下,节点(服务器)之间的延迟(往返延迟)总是亚毫秒级。如果我们要成功地在现有的数据中心和GCP之间分割应用程序的运行,我们需要知道应用程序在节点之间以20~50毫秒的延迟执行。这种延迟的增加,是由于光速的限制以及数据分组必须穿越的数据中心和GCP之间的数百英里所致。

显然,这不是我们在迁移过程中想要遇到的问题。为了减少潜在客户的痛苦,我们想先测试一下。在项目的规划阶段,我们决定使用一个服务器端工具(TC)引入人工网络延迟和模拟预期的地理位置、光速延迟。我们这样做是通过逐步升级我们的NoteStore舰队达到50毫秒的模拟延迟,并留在原地4天。在此期间,我们监控的应用程序的KPI,并与我们的基线数据进行比较。我们的大多数API调用都稍慢,但仍在可接受的范围内,并且不会影响用户体验。

这是项目的一个巨大的里程碑:我们验证了我们的应用程序可以成功运行在一个拆分站点配置。这意味着我们的“加速阶段性切换”的想法可能只是工作。

负载均衡(物理到HAProxy)

在我们的数据中心,我们运行和管理传统负载平衡设备的高可用性集群。当我们进入云环境时,拥有物理负载均衡器不是一个选项,因此我们开始调查虚拟负载均衡器解决方案。

在理想的世界中,我们将部署一个仅基于GCP服务的单一负载平衡(LB)层。但这不是我们的选择,因为我们依赖于检查cookie、标题和URL模式来将请求路由到正确的分片。这种解析不是我们目前只能在Google负载平衡平台中进行的解析。我们对这些选项进行了评估和基准测试,并结算了使用Google的网络负载均衡器产品和基于Linux的HAProxy服务器场构建的解决方案。

Google网络负载平衡器将成为客户流量的入口点,并将平衡流量到我们的HAProxy服务器场,以便将客户流量特定路由到其特定的分片。

完成了所有常规的实验室测试和验证后,我们希望使用真实的流量测试新的解决方案,而不必通过新的前门“摆动”所有的流量。我们需要一种方法来进行部分/分阶段测试,只有在上次测试成功后才增加测试用户集。此时,Evernote服务的后端仍然在我们的旧物理数据中心内运行,分离站点LB流量将通过我们的私有VPN链路路由到那里。我们决定执行以下操作:

  1. 直接Evernote员工通过新的“前门”。我们更新了公司的DNS,将Evernote员工指向新的前门。
  2. 利用Dyn流量管理器逐步增加用户流量以使用新的前门。

使用这两种方法,我们能够在任何其他服务被确认为在GCP中成功运行之前测试我们的新负载平衡平台。与我们的拆分现场测试一样,能够单独完成组件测试,并开始对其工作有信心,这是一种缓解。

RECO服务(UDP → PubSub)

当您将附件或资源添加到Evernote时,如果服务是图像或PDF,服务将尝试从中提取文本信息。执行该操作的服务称为Reco(“识别”一词的缩写)。

由于过去的各种架构限制,Reco服务器使用轮询模式来获取要处理的新资源的列表。然而,正如人们想象的,使多个Reco服务器定期轮询每个NoteStore服务器,可能会导致NoteStore和支持它们的资源数据存储器产生显著的开销。为了支持新增附件的不断增长的速率,将会添加更多的Reco服务器,这情况很复杂。

为了减少开销以及随着添加更多Reco服务器而发生的后续延迟,Reco服务器被重新设计为使用多播来了解NoteStore上的新资源何时可用。

然而,GCP Compute Engine网络不支持多播 。因此,我们将应用程序重新设计为具有不同的通信体系结构。

我们重新构建了应用程序,并删除了跟踪作业的必要性,并通过附件来广播NoteStore的状态以识别。相反,我们决定使用一个有很强的可扩展和可靠的排队机制 PubSub 。NoteStore现在通过在PubSub队列中生成作业来通知Reco服务器要完成的工作。每个Reco服务器通过简单地订阅特定的PubSub队列并确认他们何时完成资源上的识别作业,消耗新的添加。我们还支持通过创建多个队列拥有优先级的能力,并使Reco服务器根据通道的优先级优先处理资源。

这样,我们通过使用基于云的排队机制和重新设计应用程序来显着简化架构,从而依赖于队列中作业的可用性和通知速度。此外,为了仅管理和维护正在使用的资源,我们正在将Reco服务迁移到支持自动扩展

用户附件存储(从多个WebDavs到Google云存储)

我们有120亿个用户附件和元数据文件,可以从原始的WebDavs复制到Google云端存储中的新家。

考虑到我们需要复制的数据量,这是项目的关键路径。我们没有浪费时间在后台启动这个巨大的数据拷贝工作。该服务仍在读取和写入现有的WebDav服务器场,而我们在后台将资源复制到它们的新家。

我们必须解决的第一个障碍是,我们当前的数据中心网络不是设计为每天在数千个节点上复制数百TB。因此,需要工程时间来建立到GCP网络的多条安全出口路径(上文详述)。由于我们一次要从数百个服务器复制,我们还必须小心不要DDoS自己,并确保我们保护我们的用户的服务。

资源迁移器

我们开发了一个可以直接在每个文件服务器(WebDav)上运行的Java应用程序。WebDavs根据其物理RAID阵列分为目录树。资源迁移器会遍历目录树并将每个资源文件上传到Google云端存储(GCS)。

为了确保成功上传给定资源,我们将本地计算的散列以及文件的内容传递给GCS API。GCS具有独立计算其自己的散列并将其与提供的散列进行比较的特征。在不匹配的情况下,GCS API返回HTTP 400 BAD REQUEST错误代码,资源迁移器将重试。如果发生重复的错误,则故障将被记录为稍后要固定的东西,并且资源迁移器将继续移动。

通过性能测试,我们发现我们受到RAID阵列上的IOPS(每秒输入输出操作)以及WebDav的CPU的约束。为了避免影响客户体验,我们找到了资源迁移器的两个并发实例(每个RAID阵列一个实例)的平衡,每个并行实例运行在40个线程。这使我们能够轻松地同时上传80个资源文件,而不会对生产用户流量产生负面影响。

现在我们在资源迁移器中有了我们的工作代码,然后我们需要创建一个控制措施层来管理它,进入资源迁移器。

迁移协调器

资源迁移器本身是一个小应用程序,必须为WebDav集群中的每个目录树启动和停止。考虑到要迁移的数百棵树,我们需要一个控制措施层来编排跨群集的迁移。

使用shell脚本,我们能够与我们现有的库存和群集管理工具集成,以跟踪、启动、停止和恢复整个WebDavs中的资源迁移器实例。

考虑到每个WebDav不超过两个实例,每个物理服务器机柜不超过20个实例(由于网络限制)的约束,迁移协调器必须是数据中心感知的,并且能够智能地启动/停止/恢复n个实例的资源迁移者,给定一组具有最小尺寸的机柜。

在高级别,迁移协调器需要:

在全速下,我们能够并行运行100到120个资源迁移器实例,所有这些实例都由迁移协调器控制措施。

更新我们的应用程序与GCS交谈

然后,我们需要考虑如何更新我们的应用程序代码,以使用GCS读取和写入资源,而不是WebDav。我们决定添加多个开关,允许我们打开和关闭特定的GCS读/写功能。这些开关也可以在分片子集上启用,从而允许我们以安全和受控的方式转出更新的代码。

切割服务

将服务迁移到新的存储后端是一项敏感操作。我们在我们的开发和分级环境中进行了广泛的测试,但是只有这么多,可以验证,直到在系统上承载真实的生产负载。

为了干净、安全地执行此切换,并以最小的用户影响,我们将转换分解为几个独立的步骤。为了理解这一点,首先让我们解释上传资源的原始流程:

  1. 用户向Evernote服务发送保存资源的请求。
  2. 服务接收资源并启动两个进程:
    1. 确保资源写入两个主存储WebDav
    2. 将资源添加到异步作业队列
  3. 只有在这两个操作完成之后,服务才响应用户资源已被成功保存。
  4. 然后在后台处理异步作业,并将资源复制到我们的异地备份设施中的第三个远程WebDav。

阶段1

异步GCS写入

我们采取的第一步是向异步备份队列添加一个作业,以将资源写入GCS。这使我们在几个方面受益:

  1. 新资源将自动上传到GCS,从而使用资源迁移器节省时间。
  2. 此外,这使我们开始将写流量的生产级别推送到GCS API。这对于测试我们自己的代码路径,以确保它们按需要大量工作是至关重要的。这也使我们了解GCS的故障情况和速率。
  3. 最重要的是,所有这些都完全发生在用户的关键路径之外。这里的任何失败将对用户是100%透明的。这为我们提供了灵活性和自由度来迭代和改进我们的代码,而不会对用户体验产生负面影响。

投机GCS阅读

在这一点上,我们有信心提升代码和GCS的写性能和成功率。下一步是要达到与Read性能和成功相同的信心水平。

为了实现这一点,我们以投机方式从GCS读取资源。如果给定资源尚未迁移,或者如果发生任何类型的故障,则没有问题:服务将立即回退到从主WebDav位置读取。

由于这是用户下载资源的关键路径,因此发生回退时发生GCS失败的请求的延迟稍微增加。一般来说,任何这样的延迟都以毫秒为单位进行测量,并且对用户来说不会引人注意。

初步学习

在前两轮中,我们学到了很多关于我们的新的存储后端和代码的执行情况。我们很快发现了一些罕见的故障情况(1:1,000,000左右),并确定GCS Java SDK中的内置重试逻辑不足以满足我们的需求和为用户提供的服务级别的希望。我们能够快速迭代代码来处理这些故障情况,以提供更强大的后端服务。

阶段2

在学习和迭代几个周期后,我们准备好完全提交。下一个阶段是切断我们对旧WebDavs的依赖。我们仍然在迁移资源,所以我们不得不离开投机阅读,但它是时候关闭它们的新资源。

GCS在关键路径中写入

首先我们不得不修改资源上传流。在确认写入之前,资源先前已上传到两个主WebDav位置,我们现在直接上传到GCS,并将WebDav位置计数减少到一个。在此刻:

  1. 如果GCS发生严重故障,并且所有重试失败,这将最终向用户发出“上传失败”的错误。(客户将会自动重试,所以不要担心!)。这反映了我们将在所有事情的发生和完成时所处的最终状态。
  2. 尽管我们减少了WebDav写入的数量,但这并不会降低数据的持久性。GCS提供比我们以前的WebDavs系统更大的数据冗余,所以这是一个伟大的胜利!
  3. 在“立即”的意义上,减少WebDav计数有助于保持请求延迟降低(写入:二对三),并在转换期间维护主WebDav副本使我们得以安心。

禁用WebDAV完全写入

在让系统在此状态下运行一段时间并密切监视性能和运行状况后,我们准备停止WebDav的所有写入:

  1. 禁用异步备份作业到我们的异地位置(请记住,GCS不仅维护给定资源的多个副本,而且将它们存储在地理上不同的区域,从而在灾难性故障或自然灾害的情况下为我们提供更大的弹性)。
  2. 禁用主WebDav写入。

在这一点上,上传流程简单:

  1. 用户向Evernote服务发送保存资源的请求。
  2. 服务接收资源并确保成功写入GCS。
  3. 服务响应用户资源已成功保存。

在后台,GCS完全处理在地理上不同的区域中存储文件的多个副本。

最终验证

在写作本文的时候,这个资源迁移完成了99.2%,它隐喻了迁移帐篷里的长杆。我们说,“刷新”按钮已经得到锻炼!

投机GCS读取与WebDav回退将保持在位,不仅直到迁移完成,还直到我们通过一个最后的健全检查,以确保一切顺利地迁移。

要执行这个检查,我们将迭代遍历资源数据库,并使用“你有这个吗?”请求来查询GCS,以验证资源是否存在,并最后验证存储的文件哈希值。与资源迁移相比,这应该相对较快,大约数周而不是数月,因为我们检查了120亿个文件中的每一个,以确保您的数据安全可靠。

测试在云中运行的生产分片

同样,有多个步骤导致了这一点。在项目的早期,我们已经创建了在GCP中运行的完整Evernote技术栈的多个迭代,以测试功能。在考虑完全迁移之前我们需要做的是证明一个分片可以在服务完全用户负载的同时在云中成功运行。我们想摆脱以前的测试可能错过的任何边缘情况。

就在感恩节周末之前,我们成功地将两个活动用户分片迁移到GCP,并将它们在那里运行了几个小时,然后再将其移回数据中心。我们已经通过了我们的最终、阻止、迁移前的检查,并有一个绿灯的迁移。

第四部分:规划和执行迁移

在规划Evernote服务的迁移时,我们希望尽可能减少用户停机时间,但也知道我们现有的架构不支持零停机迁移。在规划迁移方法和维护窗口时,我们有以下要求作为输入:

  1. 我们希望尽可能减少我们在“拆分站点”配置中运行的时间。
  2. 我们想要开始缓慢迁移单个分片开始,并且并行升级8个分片。
  3. 我们想要一个观察窗口(约48小时),一旦少量分片在Google运行,观察环境小心运行在稳定状态。
  4. 我们想要最多32个分片并行。如果再多的话,我们就会认为太冒险了。如果事情出错,将会一发不可收拾而无法管理。
  5. 我们不得不在一个小时的周末维护窗口内移动UserStore以最小化潜在的客户影响,因为这将是整个服务的完全停机。
  6. 我们希望每个分片迁移的停机时间不超过30分钟(如果可能,则减少)。用户停机总不是好事,我们要努力防范。然而,在这种情况下,我们的应用程序将不支持分片的透明故障转移。最后,由于大多数Evernote客户端在15分钟内同步的事实,对用户的影响减少了。换句话说,这意味着一些用户不会注意到停机时间。
  7. 我们希望有一个计划,使我们能够灵活地应对这种规模的项目中不可避免的未知数。
  8. 我们认识到,当我们运行一个真正的全球服务,永远不会有“工作时间外”的维护窗口。

考虑到这一切以及我们预期的分片迁移率,我们决定了以下初始计划,通过我们的论坛和社交媒体传达给用户:

分片迁移窗口为每天09:00至21:00 PDT(太平洋夏令时):

从12月16日到31日,我们都会密切关注。所有操作和工程人员将24/7全天候快速响应问题和故障。

同时,从12月1日开始,我们也开始迁移所有非客户影响服务,如图像识别和前端流量。

在迁移技术解析

NoteStore

当规划NoteStore的迁移时,我们能够利用我们的大分片迁移的现有技术 。为了在物理数据中心的硬件版本之间移动,我们开发了一个流程和脚本,将分片移动到新硬件上。虽然这不是完全可重复使用的,但它确实构成了我们在这里详细介绍的新的云迁移方法的核心。

我们需要做的第一件事是在Google Cloud中创建新的Shard,然后将初始数据同步到该新的Shard。

创建空的分片

我们开始使用Google的部署管理器和我们现有的Puppet构建脚本创建一个新的笔记存储。这与生产分片具有相同的名称和配置,但没有客户特定的数据,并且未在我们的负载平衡平台中进行配置。

同步数据集

  1. 在包含需要复制的用户特定数据的分片上有两个数据集:
    1. MySQL数据库包含来自该分片的所有用户注释测试
      1. 复制生产的MySQL备份的最新副本
      2. 准备备份
        1. 解压缩
        2. 解密
      3. 启动实例
      4. 在源和目标上设置复制用户
      5. 从备份点开始,在节点之间开始复制
    2. Lucene索引:与MySQL不同,Lucene没有本地方法来复制其数据,因此我们需要使用自定义工具来移动此数据集。我们为每个源主机上的cron运行的无处不在的rsync实用程序编写了一个工具,并将度量/日志记录发送到我们常用的Graphite/Splunk提取管道中。

我们在批量分片的预期服务切换之前约5~10天开始构建和数据同步过程。

我们在完成这个过程时迭代,更新我们的方法:

服务切换

这里的目标是安全,快速地减少服务,以尽可能减少停机时间。

  1. 停止生产分片并将其从负载平衡器中删除,确保没有客户可以对其进行更新。
  2. 运行最终增量数据同步。
  3. 启动新分片(使用同步数据)。
  4. 运行一组工具(我们称之为“信心”)来检查数据是否已成功复制,最新,校验和匹配等。
  5. 在新分片上运行QA验证脚本 - 这将验证分片是否按预期运行。
  6. 启动新分片的备份。
  7. 为用户打开新的分片。
  8. 关闭日志,事务和监视警报的监视。

为了实现我们需要的速度,这不可能是手动过程。一旦我们确认这个过程将工作,我们使用ansible自动化的过程。

我们的脚本允许我们在30分钟内迁移我们的每个分片(平均包含300k个用户),无需人工干预。我们倾斜了并行性,使我们能够并行运行32个分片的迁移。这让我们能够在30分钟内迁移1000万用户。

团队

在这些迁移期间,操作团队分成非常具体的角色,以确保没有错误:

项目经理 :此人协调整个迁移,并对诸如批量大小、何时开始批处理以及如何在团队外部沟通进度等项目做出决定。

首席工程师 :此人完全控制措施迁移批次:他在开始迁移之前确定信心,控制措施运行迁移的工具,并做出所有故障排除决策。

监控工程师 :除了和首席工程的分工一样外,这些工程师还运行我们的QA工具,并对每个迁移的成功做出最终决定。

修复团队 :我们在任何时候都有2~4名工程师来调查和解决领导或监控工程师发现的任何问题。

用户存储

UserStore迁移遵循与NotesStore的MySQL迁移中使用的相同的基本步骤,主要区别是需要更新所有NoteStores上的JDBC URL以指向新主机。

由于用户存储位于整个Evernote服务的中间,我们计划在一个星期天早上提前迁移一个特定的一小时维护时段。由于它是一个支持我们服务中的许多事务的高事务数据库,我们确保我们在办公室有一大组操作和工程资源来监视迁移。我们还有一个来自Google的专门团队,如果我们发现问题,就时刻准备好参加!

这可能是整个迁移的最密集的时段,因为整个服务下降,时钟滴答作响。

其他服务

我们其他服务的迁移路径通常更简单,因为我们可以并行创建新服务,并在大多数实例中逐步切换。我们能够使用一些我们现有的Puppet代码和模式,使构建更容易,但是这是一个清理多年的Puppet代码和简化的机会。

那么我们如何反对我们的原计划呢?

总的来说,我们能够坚持我们的原计划相当好,虽然我们偏离了两次:

这些变化通过论坛和社交媒体传达。

分片迁移的数据

下图中的每个气泡代表一批同时移动的用户,气泡越大,批处理中的用户越多。y轴显示每分钟移动的用户数,x轴只是批次编号。每种颜色代表不同的一天。

批次统计:

第5部分:结论和未来

本系列的第1~4部分探讨了我们准备简历新一代Evernote的规划和流程。因此,我们如何能够如此迅速地实现了迁移?

无情的焦点

为了有任何机会实现目标,我们需要最大限度地减少干扰,因为它们总是随着时间而下沉,并可能导致失败。我们不得不对来自业务的其他不那么重要的请求说“不”,我们需要这样做,以确保整个公司知道我们为什么这样做。我们发现brownbag会议是最好的格式,因为他们为想要了解更多的人提供了一个论坛,并有机会提出问题。

拥有合适的合作伙伴

在项目的早期,当我们讨论能够多快地迁移时,我们不想接受正常,想要设置一个大,毛茸茸,大胆的目标。与Google合作的伟大之处在于他们从来没有逃避这个挑战。每当我们发现可能导致计划失败的事情时,Google小组就会加紧交付。

很高兴看到一个来自两个独立公司的工程师团队为一个共同的共同目标工作。

使其真实,并尽快

传统观点认为,应该开始这些类型的迁移与小和非关键,因为这是你学习的地方。这是真的在一定程度上,但你必须要小心。我们的建议是迅速进入这一阶段,考虑迁移重要的环境部分,因为这是唯一的方式,你可以真正摆脱所有你会遇到的问题。

启动小和关键路径之外有一些基本问题:

从小开始,是熟悉一个新平台并执行基本测试的好方法。这是一个您需要完成的步骤,但不应假设它将确认您的生产服务将运行。

在迁移期间,我们尽可能在可能的情况下尽快加速生产服务的测试迁移。

为了做到这一点,它有助于如果你有一个应用程序/系统,你可以单独迁移单个零件或组件。较新的,更现代的基于微服务的架构适合这种范式,但对于较旧的单片应用程序栈来说可能非常困难。

总之,在用户的关键路径之外,优雅地失败的高流量组件将是一个理想的起点。

自动化

在整个迁移项目中,我们尽可能利用自动化来加速。在某些点,我们利用现有的工具。在其他人,为此迁移的目的创建了自定义工具:

没有办法,我们可以在如此短的时间内完成这个迁移,而不关注自动化。

优化约束

在我们的早期规划中,我们认识到,我们当前的数据中心和GCP之间的网络带宽将以多种方式约束我们。这将限制我们复制大量数据的能力,并限制我们在现有数据中心和Google云之间拆分服务的能力。

我们将此标识为可能对多个工作流产生负面影响的约束。知道这一点,我们在项目早期投入额外的时间和精力,以最大限度地提高站点之间的可用带宽。我们实际上在我们做出最终选择以节省宝贵的天数之前,已经与多个云提供商提供互连订单。

我们学到了什么?

总体而言,我们不能对迁移过程感到满意。话虽如此,我们有一些学习收获的:

您的应用程序将成功运行在云中,但...

云计算已经达到了功能成熟的水平。在大多数情况下,你的应用程序将很快运行。也就是说,你需要'调整'你的应用程序运行在新的家,在那里它会遇到一组不同的约束。提前计划,并允许在迁移后一段时间的“调整”。

例如,我们必须调整我们的环境,以更好地应对磁盘调节。当在我们自己的数据中心中运行时,我们可以尽可能快地运行备份。我们没有在共享环境中或从共享存储设备运行,所以随着时间的推移,我们配置了我们的备份以使用所有可用的磁盘IO。但是当在GCP中运行时,我们会自动启动一个备份,这会导致虚拟机被IO限制并导致上游应用程序运行缓慢。我们通过在备份中使用不运行全速率来解决这个问题。

我们还必须调整我们的环境,以更好地应对实时迁移 。这是一个伟大的Google后端功能,可让他们在主机之间迁移VM,以便进行维护,并且在大多数情况下,客户不可见。我们发现UserStore和一些较早的,更活跃的Evernote分片在进行实时迁移时并不高兴,因为它们是高IO机器。即使是非常小的,停电或掉电窗口是足以造成中断 。我们已经调整MySQL和我们的应用程序,以更好地管理这一点。它尚不完美,但这是我们将继续完善与Google的帮助和帮助。

客户沟通是关键

我们需要一个更好的方式来发送即将到来的维护通信给我们的客户。我们不想让数百万用户使用他们可能不会阅读的电子邮件。因此,我们决定使用我们的Evernote社区论坛和社交媒体向我们的用户传达即将到来的维护时段。虽然这个工作很好,我们确实有一小部分用户不知道维护和受到影响。这是一个我们想要关闭的差距,我们正在考虑应用内传播,以便在未来更好地实现这一点。

总结

虽然不是意想不到的,我们一直在追踪与在新环境中运行相关的各种小错误和打嗝。虽然有些人比其他人更痛苦,但我们最终能够通过调整现有配置或与Google密切合作,缩小我们平台上的差距来解决这些问题。

所以现在怎么办?

现在,我们已经完成了迁移,我们可以展望未来。我们迁移到云的原因之一是继续消除我们的工程过程中的摩擦,并让工程师访问更广泛的工具集来构建。我们知道,随着时间的推移,我们将迁移远离基于虚拟机的构建块,并拥抱容器和无服务器技术在驱动器中专注于重要的。

我们已经开始看到好处

Evernote的Hack Week

每年,Evernote都会运行一次Hack Week(通常在1月份)。这是公司中每个人都有机会将他们的努力集中在他们认为重要的事情上,但可能不是我们正式的产品积压。看到当创造力被释放时,人们可以创造什么,团队和想法被允许以最小的监督自我形成,这多么惊人啊。

过去,运营团队花了很多时间支持该活动,构建服务器等。今年是非常不同:我们只是给了工程师访问到一个单独的GCP项目,并任由他们自己鼓捣。他们能够轻松地提供服务,以支持他们的项目,为操作人员提供最少的增加的工作量,他们只是被隔离完成迁移项目。

微服务平台

去年,我们还决定从现有的单片式应用程序架构转向基于微服务的架构。采用的容器是实现这一目标的很自然的一步。我们做了一些广泛的研究,最终登陆Kubernetes作为我们的编排框架。运行生产级Kubernetes集群可能是一项艰巨的任务,但幸运的是,Google容器引擎 (GKE)简化了整个过程。

我们目前有一个GKE集群在生产项目中运行,支持我们的下一代搜索基础设施。注意关于该特定主题的此博客的另一个后续帖子。

无服务器的未来

尽管我们当前的架构有所改进,但是仍然有与操作系统实例(1000+)相关的大量开销,它的唯一目的是运行Evernote代码。在理想情况下,我们只需部署代码,Google将负责大规模运行该代码。

在这种情况下,我们将只使用计算能力来处理来自用户的请求,而不再使用。虽然在这一领域还有很长的路要走,但我们正在调查Google云端功能作为我们的核心构成块。这将允许我们专注于编写代码/功能来解决用户问题。

最后但并非最不重要:你如何命名的云项目呢?

每一个伟大的项目都需要一个名称。经过多番考虑,我们得到了双方都满意的结果。我们的软件工程团队提出了“Cloudy Meatballs”的名字,来自Judi Barrett的“Cloudy with a Chance of Meatballs”一书。我们的运营团队喜欢的代号为“Bob Ross”,他是著名的艺术家和电视人物,会画出绚丽的云彩!打破僵局的唯一方法是,有公司投票决定。“Bob Ross”赢了,我们赢了了一个开门红。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注