Evernote 从自有数据中心到 Google 云的迁移 - 第 2 篇

阅读数:580 2017 年 4 月 4 日

话题:Google语言 & 开发架构

前文请见《Evernote 从自有数据中心到 Google 云平台的迁移》第 1 篇

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

我们的系统架构

下一个重大决定是如何在即将实施的系统架构方案上达成一致。下面是一些重要的考虑点。

  • Evernote 服务可以运行在多个数据中心上(不包括中国地区的服务)。
  • 将用户数据的两个副本保存在不同的地理区域。
  • 最小化加州北部数据中心和 GCP 之间的网络延迟,为迁移工作带来更多的灵活性。而且如果有可能,在短期内还能支持多站点运行。

基于这些考虑,我们达成了以下方案。

  • 主要位置(处理生产流量):US-West1
  • 次要位置(灾备站点):US-Central1

后续我们还会把 US-West1 的服务拆分成两个区域,进一步提升故障防护能力。

正如Google 所说的,“Google 把区域设计成相互独立的:每个区域有自己的供电系统、冷却系统、网络和控制面板,大部分的故障只会影响到单个区域”。

基于上述的架构,我们可以处理 US-West1 之外的流量,并在 US-Central1 保存用户数据的第二个副本。如果 US-West1 发生故障,我们可以使用 US-Central1 的数据进行恢复。

我们以后还要想办法对我们的应用进行重构,提升它们的弹性。还要考虑如何同时处理多个区域的流量,以便进一步减少从故障中恢复的时间。我们也会考虑如何更好地利用 GCP 的全球基础设施来改善用户的延迟体验。

到现在为止,我们定义了清晰的需求,并做出了很多决策。接下来开始进入具体的实现阶段。

技术转型

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

我们在一开始就意识到,从数据中心到 GCP 的网络连接是决定这次迁移成功与否的关键因素,同时也是主要的约束所在。为了能够灵活地对数据和服务进行迁移,网络互连计划需要实现如下几个目标。

  1. 对从数据中心流向 CGP 的流量进行加密。
  2. 将两个区域中的任意一个作为用户流量的“前门”,如果有必要,可以基于这两个“前门”来分割流量。
  3. 对每个区域的服务进行分割,让它们部分运行在物理数据中心里,部分运行在 GCP 上。
  4. 最大化站点间的带宽,以支持大块数据的拷贝。

在 GCP 上运行 Evernote 后端服务,并向 CGP 拷贝 3PB 的数据。在这种情况下,为了让 100% 的“前门”流量流经数据中心和物理负载均衡器系统,我们需要进一步提升灵活性。

我们已有的外部网络连接可以处理峰值负载,而且留有余量。不过,它的容量仍然无法满足及时清退数据的要求。

另外,我们的内部网络结构并不支持将如此大规模的请求导出到外部设备(比如 Google 云存储)上。基于目前的情况,要将所有的数据上传到云端,可能需要超过一年的时间,而且会影响用户体验。所以我们还有很多工作要做。

首先,我们需要建立私有网络互连(PNI),或者直接在 Evernote 的网络和 GCP 之间建立连接。这样可以将可用带宽增加一倍,而且这些连接独立于用户流量。这就在 Evernote 和 GCP 之间建立起了一个快速的私有通道。

其次,我们需要确定数据中心的哪些地方需要输出数据。我们的数据有多个副本,所以需要确定该使用哪个副本。我们需要开辟出一条网路,在不影响系统正常运行的情况下,将成千上百台服务器的数据通过私有通道移动到 GCP。我们需要小心地协调数据拷贝作业,让这些请求正确地流经一系列负载均衡器和代理服务器。

在项目启动后的头一个月,我们的网络工程团队争取在数据拷贝启动之前完成准备工作。因为如果他们的工作无法按时交付,整个迁移工程都会受到影响。

我们可以多站点运行吗?

到目前为止,我们的应用只在单个数据中心里运行。在单个数据中心里,节点间的延迟通常是亚毫秒级的。如果我们能够成功地在数据中心和 GCP 上运行应用,我们需要知道该如何将节点间的延迟保持在 20 毫秒到 50 毫秒之间。延迟的增加有两方面的原因,一方面是受光缆的速度限制,另一方面是受数据中心和 GCP 之间距离的影响。

很显然,我们不希望在迁移过程中发生此类问题。为了避免给用户带来延迟,我们需要先进行自测。在项目计划期间,我们决定使用一种服务端工具(tc)引入人为的网络延迟,并模拟出因地域和光缆速度限制所带来的延迟。我们逐渐将 NoteStore 的延迟增加到 50 毫秒,并保持 4 天不变。在此期间,我们对应用的 KPI 进行监控,并将它们与我们的基准数据进行比较。我们发现,大部分 API 调用有轻微的减慢,不过都在可接受的范围之内,而且没有对用户体验造成影响。

这是一个里程碑式的阶段性胜利:我们确信我们的应用可以跨站点运行,也就是说,我们的“增速阶段性切换”方案是可行的。

负载均衡(从物理均衡器到 HAProxy)

我们的数据中心运行着一个高可用的负载均衡器集群。在迁移到云端之后,就无法使用物理的负载均衡器,所以我们开始调研虚拟的负载均衡解决方案。

理想情况下,我们可以在 GCP 上部署单个负载均衡层。不过从现实来看,这个是不可行的。我们根据 cookie、消息头和 URL 将请求消息路由到特定的分片上,但我们无法在 GCP 负载均衡平台上做这些解析工作。最后,我们基于 Google 网络均衡器和一个基于 Linux 的 HAProxy 集群构建了新方案。

Google 网络均衡器将成为用户流量的入口点,并将流量均衡地分发到 HAProxy 集群上,然后 HAProxy 集群将流量路由到特定的分片上。

在通过实验测试之后,我们想使用真实的流量来对新方案进行测试。我们采取阶段性测试,在前一个测试通过之后才会增加更多的流量。到目前为止,后端的 Evernote 服务仍然在物理数据中心里运行,而跨站点的负载流量会通过私有 VPN 连接路由到那里。我们打算这样做:

  1. 将 Evernote 员工流量重定向到新的“前门”。我们更新了公司的 DNS,从而将 Evernote 员工流量引导到新的前门。
  2. 使用 Dyn Traffic Manager 逐步将用户流量导入到新前门。

我们通过上述的两个方法对我们的新负载均衡平台进行测试。与跨站点测试一样,我们庆幸我们能够单独完成组件的测试,并确信这个方案是可行的。

Reco 服务(从 UDP 到发布订阅)

当用户将附件或资源上传至 Evernote,有个后端服务会尝试提取图像或 PDF 里的文本信息,这个服务被称为 Reco(“recognition”的缩写)。

因为之前的架构存在种种限制,Reco 服务器只能通过轮询的方式拉取资源。可以想象得到,多台 Reco 服务器定期向 NoteStore 服务器发起轮询,会给 NoteStore 服务器和资源存储服务器带来很大的压力。随着用户附件的增加,需要添加更多的 Reco 服务器,从而让情况愈加恶化。

为了减少因 Reco 服务器的增加而带来的开销和延迟,我们对 Reco 服务器进行了重新设计,通过使用多路广播,当 NoteStore 增加新的资源时,Reco 服务器就会收到通知。

不过GCP 的网络并不支持多路广播,所以我们使用新的通信模型对应用进行了重新设计。

我们移除了轮询机制和多路广播机制,取而代之的是基于发布订阅模型的队列机制,这种机制不仅可靠而且可伸缩。NoteStore 向队列里发布任务,Reco 服务器订阅队列,并获取任务,在处理完任务后进行确认。我们创建了多个优先级队列,Reco 服务根据队列的优先级来处理任务。

我们使用基于云的队列机制极大简化了原先的架构,现在能够影响 Reco 服务的因素只有两个:队列里是否有需要处理的任务以及通知的速度。除此之外,我们正进一步改造 Reco 服务,让它支持自动伸缩,这样我们就可以把注意力放在如何管理和维护附件资源上。

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

我们需要将 120 亿个用户附件和元数据文件从原先的 WebDav 服务器拷贝到 Google 云存储上。

因为数据量巨大,拷贝文件是整个迁移项目的关键一环。在进行拷贝的过程中,我们的服务仍然会对 WebDav 服务器读写数据。

我们碰到的第一个障碍是我们的网络,我们无法每天从几千个节点拷贝几百 TB 的数据。所以,我们不得不花一些时间建立了多条到 GCP 的网络通道。在进行数据拷贝的同时,我们要确保不会对自己造成 DDoS 攻击,还要保护好用户服务。

资源迁移器

我们开发了一个 Java 应用程序,它可以直接运行在 WebDav 服务器上。WebDav 服务器根据它们的物理 RAID 情况被拆分成目录树。资源迁移器遍历目录树,并将每个资源文件上传到 Google 云存储(GCS)。

为了确保文件能够上传成功,我们在本地为每个文件生成了一个散列值,这个散列值连同文件一起发送给 GCS。GCS 有独立计算散列值的功能,它将自己计算的散列值与收到的散列值进行比较。如果散列值不匹配,GCS 会返回一个 HTTP 400 BAD REQUEST 错误码,资源迁移器会进行重试。如果连续几次重试失败,错误会被记录到日志里,用于后续的修复之用,而资源迁移器会继续上传其他文件。

通过性能测试,我们发现拷贝过程主要受 RAID 阵列的 IOPS(每秒输入输出操作)和 WebDav 服务器 CPU 的影响。为了避免对用户体验造成影响,我们使用了两个并行的资源迁移器实例(每个 RAID 阵列使用一个迁移器),每个实例使用 40 个并行线程。这样我们就可以同时上传 80 个资源文件而不会对用户造成负面影响。

有了资源迁移器,接下来需要创建一个控制层来管理这些迁移器。

迁移编排器

资源迁移器是一个小型的应用,WebDav 集群上的每个目录树都需要一个迁移器实例。因为有几百个目录树需要迁移,所以需要一个控制层来管理这些迁移器。

我们在 shell 脚本里集成了现有的目录管理工具,实现了对资源迁移器实例的启动、停止和追踪。

因为受每个 WebDav 服务器最多只能运行两个实例和每个物理服务器机柜最多只能运行 20 个实例(受网络限制)的限制,迁移编排器必须能够智能地管理好资源迁移器实例,尽量减少人工的干预。

从高层面看,迁移编排器需要满足如下要求。

  • 提供一个集中式的控制台用于管理所有的资源迁移器
  • 维护任务清单,并识别出可迁移的任务(正在写入的目录是不能进行迁移的)
  • 了解数据中心和主机的情况,避免出现资源过载或影响到生产流量
  • 提供稳定的 24/7 吞吐和并发任务

如果速度全开,我们可以同时运行 100 到 120 个迁移器实例,这些实例完全由迁移编排器来控制。

让应用与 GCS 发生交互

接下来,我们要考虑如何更新我们的应用程序,以便从 GCS 读写数据。我们决定加入一些开关,用于切换 GCS 的读写功能。有了这些开关,我们可以先在部分分片上打开这个功能,让新功能的发布更安全、更可控。

服务切换

将服务迁移到新的存储后端是一个敏感的操作。我们在开发环境和测试环境进行过大量的测试,在进入生产环境之前我们能做的也只有这些了。

为了做到干净安全的切换,并最小化对用户的影响,我们把迁移分成几个独立的步骤。为了方便理解,我们先来了解下最初的资源上传过程。

  1. 用户向 Evernote 服务发送保存资源的请求。
  2. 服务收到用户的资源,并启动两个进程:
    1. 确保资源的两个副本被保存到 WebDav 服务器上
    2. 将资源添加到异步任务队列
  3. 在完成了上述两个步骤之后,向用户返回保存成功的消息。
  4. 后台处理异步任务,资源的第三个副本被拷贝到远程的 WebDav 服务器上。

第一阶段

GCS 异步写入

第一步,我们往异步队列里写入需要上传到 GCS 的资源。这样做有如下几个好处。

  1. 新的资源将会被自动上传到 GCS,为我们节省了使用资源迁移器的时间。
  2. 另外,我们可以将生产环境的写入流量导向 GCS API。这对于测试我们的代码是否能够处理大量数据来说非常关键。我们也因此对一些故障场景和 GCS 的评估有了更深入的了解。
  3. 更重要的是,以上过程全部发生在用户的视野之外。这里所发生的故障对于用户来说都是 100% 透明的,所以我们可以自由地进行迭代,不断改进我们的代码,而不会对用户体验造成负面影响。

GCS 伺机读取

到现在为止,我们对写入性能充满了信心,并对我们的代码和 GCS 有了正确的评估。下一步是如何让读操作的性能也能达到相同的等级。

我们伺机从 GCS 读取资源数据,如果有些资源还没有被迁移过来,或者出现了故障,我们的服务会立即转向主 WebDav 服务器读取数据。

因为下载资源处在用户的关键路径上,在读取资源时发生转向有可能会造成一些延迟。不过这些延迟一般在毫秒级别,用户感知不到。

初步结论

头两轮的测试让我们对新的存储后端和代码有了更多了解。我们发现了一些罕见的故障(1:1,000,000 的比率),而且发现 GCS Java SDK 内置的重试机制无法满足我们的要求。我们通过快速迭代改进了我们的代码,解决了这些故障,让服务更加健壮。

第二阶段

在经过几轮迭代之后,我们准备进行全面的提交。下一步要移除对 WebDav 服务器的依赖。不过,我们对资源的迁移流程还在进行中,所以伺机读取资源的机制还要保留,不过对于新的资源来说,就没必要再使用 WebDev 了。

关键路径上的 GCS 写入

首先,我们要改变资源上传流程。之前,资源会被上传到两个主 WebDav 服务器上,而现在它们直接被上传到 GCS,而且我们将 WebDav 服务器的位置数量减至一个。

  1. 如果 GCS 发生重大故障,所有的重试都会失败,用户最终会收到“上传失败”的错误信息(不过我们的客户端会再次重试,所以不用太担心)。这也就是我们能得到的最糟糕的结果。
  2. 虽然我们减少了 WebDav 的写入,但并不会降低数据的持久性。GCS 提供了更好的数据冗余,所以这是一个很大的进步!
  3. 减少 WebDav 数量有助于降低延迟(两次写入与三次写入的对比),同时,仍然保留单个主 WebDav 拷贝让我们感觉到很安全。

全面禁用 WebDav 写入

我们对系统的性能和健康情况进行了深切的监控,在运行了一段时间之后,我们准备全面停止向 WebDav 服务器写入数据。

  1. 禁用离岸灾备中心的异步备份作业(GCS 不仅为每个资源维护多个副本,还会将它们存储在不同的地理区域,为我们提供了很好的弹性,以便应对灾难性故障和自然灾害)。
  2. 禁用主 WebDav 写入。

现在的上传流程更简单了。

  1. 用户像 Evernote 服务发送保存资源的请求。
  2. 服务接收资源并将其写入 GCS。
  3. 服务向用户返回成功信息。

在后台,GCS 负责保存资源的多个副本,并保存在多个地理位置。

最后的验证

在撰写本文时,我们已经完成 99.2% 的资源迁移,这一过程是整个迁移项目非常重要的一部分。可以说,我们即将看到胜利的曙光!

GCS 伺机读取机制还会继续存在,直到我们进行一次最终的完整性检查,确保所有的资源都已成功迁移。

为了完成最终的完整性检查,我们遍历了资源数据库,然后向 GCS 查询这些资源是否存在,并再次验证每个文件的散列值。与资源迁移过程相比,完整性检查完成得会快很多。我们按周而不是按月来检查资源,确保 120 亿个资源都安全地进行了迁移。

在云端生产环境测试分片

进行云端分片测试也需要经过一些步骤。在迁移项目启动之初,我们就定义了一些迭代流程用于测试运行在 GCP 上的 Evernote 服务。在进行全面迁移之前,我们需要确保在用户负载全开的情况下,单个分片能够成功地运行在 GCP 上。我们要找出之前的测试没能覆盖到的边界情况。

在感恩节之前,我们成功地将两个用户分片迁移到 GCP 上,并让它们运行了几个小时,最后再回退到物理数据中心。我们完成了最后的预迁移检查,为后续的全面迁移亮起了绿灯。