把时间线拨到 2021 年 11 月 18 日星期四,Dropbox 服务一切如常。用户没有感觉到任何异样,就如同无数个岁月静好的日子。但真是这样吗?当然不是,那天下午五点,一群 Dropbox 员工在 Zoom 频道里吵作一团,因为大家突然接到命令,要求把圣何塞数据中心跟 Dropbox 网络直接断开。
这可是件大事,毕竟灾难恢复(DR)团队为此准备了一年有余,而影响到的可是 Dropbox 六年多积累下的工作结晶。
但面对自然灾害愈发普遍的世界,我们必须考虑数据中心受到此类影响的可能性。就是说我们不仅要在数据中心选址方面精心规划,同时也得建立起有助于降低风险的应对策略。
自 2015 年从 AWS 迁出之后,Dropbox 的基础设施就大量集中在圣何塞地区。虽然用户的元数据多年来一直在跨区域复制,但必须承认的是,我们的大多数服务都诞生在圣何塞、成熟在圣何塞。这是块宝地,但也靠近圣安地列斯断层,所以我们得保证突如其来的地震不会让 Dropbox 意外离线。
怎么向客户证明我们已经为灾害做好了准备?答案就是恢复时间目标(RTO,Recovery Time Objective),这项指标用于衡量我们要花多长时间才能从灾难性事件中恢复正常。多年以来,我们一直在通过工作流程缩短预期 RTO,希望能更从容地应对包括地震在内的可能灾难。
也正是依靠 2020 年和 2021 年的跨职能协作,灾难准备团队最终下决心把圣何塞数据中心完全断开,看看 Dropbox 是不是真的能够把 RTO 降低到目前的水平。下面,咱们就来回顾这段惊心动魄的故事。
为了更好地理解如何缩短 RTO 周期,我们首先得明白 Dropbox 是怎么进行架构设计的。
Dropbox 拥有两大核心服务栈:其一用于块(文件)数据,其二用于元数据。不少关注 Dropbox 技术动向的朋友可能知道,我们的块存储解决方案名为 Magic Pocket,其设计就是通过多宿主主机提高可靠性。所谓多宿主服务,就是说这项服务在设计上能够依托于多个数据中心保持运行。
Magic Pocket 还是一套所谓双活系统。就是说除了多宿主设计之外,它还能同时且独立地为来自多处数据中心的块数据提供服务。Magic Pocket 设计包含内置复制与冗余机制,能够保证把区块故障给业务造成的影响控制在最低。这套架构还具备灾难弹性,所以用户能够明确看到哪处数据中心出了问题、但服务体验并不太受到影响。在部署了 Magic Pocket 之后,Dropbox 又启动了一项三段式计划,先是增强元数据堆栈弹性、最终在元数据层面同样建立双活架构。
具体来讲,计划的第一阶段就是实现主动-被动架构。也就是说,在完成必要的变更之后,我们就能把元数据从当前主动城域——即圣何塞数据中心(SJC)——转移至另一被动城域。这个过程就是所谓故障转移。
我们的首次故障转移已经在 2015 年成功完成,但这还只是达成最终目标的小小一步。之后,我们开始为元数据堆栈构建主动-主动的双活架构,希望以独立方式为来自多处数据中心的用户元数据提供服务。到了这一步,麻烦开始出现。
我们的元数据堆栈建立在两个大型分片 MySQL 部署之上。其一用内部数据库 Edgestore 承载着通用元数据,其二则负责承载文件系统元数据。集群中的每个分片由六台物理设备组成:两个核心区域,各自对应一台主机 primary 和两台副本 replica。
而 MySQL 层中的两大权衡性设计,再加上 Edgestore 中的数据建模复杂性,迫使我们不得不重新考量整个灾难准备计划。
MySQL 中的第一个权衡就是如何处理复制操作。我们之前使用半同步复制以求取数据完整性和写入延迟之间的平衡性。但正是由于这一设计,导致各区域间的复制只能异步完成——意味着远程 replica 始终落后于 primary 主机。这种复制层面的滞后,导致我们很难处理主区域中的突发性故障。有鉴于此,我们对可能发生的故障做出预判,通过设计确保主区域在事件之下仍能保持一段时间的正常运行。冗余电源和网络系统都已部署到位,我们对实际效果也算是比较满意。
MySQ 中的第二个权衡则是一致性级别。我们的 MySQL 采用的是读取提交隔离模式,由此实现的强一致性使得开发人员能够轻松处理数据,但同时也限制了数据库的扩展能力。目前比较常见的扩展方式就是引入缓存以降低整体一致性,但同时增加读取吞吐量。在我们这套系统中,虽然 Dropbox 已经建立起缓存层,但它在设计上仍然与数据库保持强一致性。这个决定使得设计方案相当复杂,同时也限制了所能容忍的数据库缓存内容滞后度。
最后,因为 Edgestore 是一套面向多种用途的大型多租户图数据库,所以往往很难搞清其中的数据所有权。这种复杂的所有权模型,导致我们几乎没法简单将用户数据中的特定子集转移到其他区域。
这些权衡设计的存在,直接决定了我们后续构建双活系统的基本思路。开发者已经适应了前一种权衡所带来的高写入性能,也适应了后一种权衡实现的强一致性。总而言之,这些选择严重限制了我们在设计双活系统时的架构选项,也导致最终系统变得愈发复杂。到 2017 年,灾难准备工作已经停滞不前,但开发强大故障应对方案的压力却丝毫未减。为了保障能在灾难发生时获得良好的业务连续性,我们决定改变方向,朝着主动-被动故障模型迈出探索的脚步。
在决定转向主动-被动方案后,我们开始为更频繁的故障转移设计必要工具。2019 年,我们完成了第一次正式的故障转移,之后每个季度都会再次尝试转移、并借此机会改进整个流程。2020 年是个重要的转折点——除了新冠疫情的爆发,我们 Dropbox 的灾难准备水平也自此真正上了一个新台阶。
2020 年 5 月,我们的故障转移工具发生严重故障并引发宕机,业务瘫痪达 47 分钟。负责驱动故障转移的脚本在执行当中出错,致使我们身陷半中断状态。这次失利也暴露出我们灾难准备策略中的几个重大问题:
对于第二和第三个问题,我们组建了专门的故障转移团队,也就是前文一直提到的灾难准备(DR)团队。有了这样一支专业队伍,我们就能把每季度一次的故障转移频度提升为每月一次。更频繁的故障转移不仅能帮助我们积累经验、提振信心,同时也让我们以前所未有的速度实现了灾难响应与灾难恢复。
明确的使命与新组建的七人小队,让我们有了设定更高目标的底气。到 2021 年底,Dropbox 必须把 RTO 控制在更短水平。
2020 年 5 月那次宕机事故,凸显出了我们的一大重要问题——总是想用单一 Go 二进制文件完成城域之间的故障转移。虽然这种方法最初效果不错,但随着我们对于故障转移不断提出更高要求,整个思路也变得愈发难以为继。
因此,我们决定从头开始重写这款工具,提升它的模块化与可配置水平。我们从 Facebook 的 Maelstrom 论文中获得了灵感,其中详细介绍了一种巧妙的外向流量导引思路,足以承载起庞大的数据中心灾难恢复需求。虽然有了参照对象,但我们还是从最小可行产品做起,希望整套方案更适合 Dropbox 自己的系统。
我们借用了 Maelstrom 中的 Runbook 概念。Runbook 中包含一个或多个任务,每个任务负责执行特定操作。这些任务共同形成了一个有向无环图,使我们不仅能够描述故障转移演习中的每一个必要步骤,同时也能概括一切通用性的灾难恢复场景。以此为基础,我们可以使用易于解析和编辑的配置语言,整理出一份描述各项故障转移条件的专用 Runbook,这样后续的故障转移调整将会像编辑配置文件一样简单快捷。
与直接编辑 Go 二进制文件相比,新方法不仅更加轻巧、同时也提高了 Runbook 的重用能力,帮助灾难准备团队轻松完成一次又一次定期测试。下图所示,为 Runbook 流程和其中的任务。
Runbook 状态机。一个 Runbook 由多个任务组成。
任务状态机。任务负责执行特定操作,例如对数据库集群执行故障转移、更改流量权重或者发送 Slack 消息。
我们还编写了一个内部调度程序,用于接收 Runbook 定义并向各工作进程发出所需执行的任务。在最小可行产品当中,调度程序和工作程序位于同一进程当中,并通过 Go 通道进行通信。这样灵活的架构让我们能在后续使用量增加时,快速把各任务拆分成单独的服务。
更新后的故障转移工具,由调度程序 goroutine 和多个工作程序 goroutine 组成,经多个通道保持通信,保证以正确的顺序指派并执行 Runbook 中的任务。
在这种新架构的支持下,我们能够轻松观察故障转移 Runbook 的执行状态,清晰判断哪些任务失败、哪些成功完成。显式图结构还让我们在发生故障时优先执行任务,同时保证某些重要操作在前序操作失败时暂停执行。此外,运维人员的操作灵活性也有所提升,例如可以轻松重新运行 Runbook、跳过已完成或者无需执行的任务等。随着 Runbook 复杂度的不断提升,这种简单性与可靠性将帮助我们始终保持程序具有良好的可管理性。
除了对工具做出的深层改进之外,我们还配合以下变更以降低风险、进一步提升客户体验:
回顾 2020 年 5 月以来的各项改进,我们一步步朝着理想中的故障转移目标迈进。在运营层面,我们已经把故障转移的执行与持续改进变成企业文化的一部分。故障转移服务也逐步自动化,灾难准备团队的预先准备需求大大削减、手动工作量远低于以往。此外,工具的改进也让我们的月均停机时间从 2021 年初的每次故障转移 8 到 9 分钟,缩减至下半年的 4 到 5 分钟。
不断打破新纪录:Dropbox 故障转移演习中的停机时间越来越长。
到这里,我们认为准备工作已然就绪,接下来就是真正的难关——彻底断开圣何塞数据中心。
从 2020 年到 2021 年,我们的故障转移能力不断提升,灾难准备团队则着手推进第二个关键里程碑:转向真正的主动-被动架构。
虽然故障转移演习已经证明我们有能力把元数据服务栈迁移到被动城域,但其他几项关键服务仍然需要从主动城域——也就是我们的圣何塞数据中心——提供服务。到这里,我们才意识到自己要想测试主动城域的真实弹性,最好的办法就是来一波灾难恢复测试。在测试中,我们得把圣何塞数据中心直接从 Dropbox 运营网络中断开。如果事实证明整个数据中心的断开都不会对运营造成太大影响,至少保证 Dropbox 还能正常运行几个小时,那就算大功告成。于是计划被提上日程,并定名为“黑洞项目”。
尽管用于支持实时用户流量的元数据和块堆栈不会受到黑洞项目的影响,但我们知道如果内部服务降级或者无法正常运行,那之前的努力仍然算不上真正的成功。更可怕的是,这么绝的测试设计有可能带来我们难以弥补的生产问题。所以,我们至少得保证圣何塞数据中心内运行的一切关键服务都拥有多宿主性质,或者至少可以暂时依靠圣何塞之外的城域实现单宿主运行。
之前的技术投资降低了多宿主改造的实现门槛,也让我们能够轻松完成单宿主服务检测。利用 Traffic 团队和 Envoy 流量负载均衡器,我们得以控制从 POP 流往数据中心的一切关键网络路由流量。Courier 迁移使我们能够构建起常见的故障转移 RPC 客户端,借此将客户端发出的服务请求重新定向至另一城域中的相应部署。此外,Courier 标准遥测功能还能衡量服务间的流量数据,帮助我们准确识别出单宿主服务。在网络层,我们利用带有自定义标记的 Kentik 网络流量数据来验证 Courier 服务的依赖性情况,同时及时捕捉持续性的非 Courier 流量。最后,几乎所有服务都由 Bazel 负责配置,因此我们可以建立并推广多城域部署实践,再借助另外的数据源验证服务城域的适应性。在确定了存在风险的服务后,灾难准备团队将提供建议和资源,确保服务所有者能够对服务架构做出修改、降低对圣何塞数据中心的依赖。
有时候,我们还会与各团队直接合作,将他们的服务集成到我们的月度故障转移当中。通过减少圣何塞数据中心内单宿主服务的数量并将其引入定期测试,我们愈发有信心让这些服务能够在另一城域内继续正常运行。在此阶段,故障转移清单中的重点关注服务主要是 CAPE 和 ATF 两套异步任务执行框架。对于某些团队,我们会以空降的方式直接协助他们把以往只能靠圣何塞数据中心运行的组件转化为多宿主形式。最后,我们抢在黑洞项目实施前完成了圣何塞数据中心内全部主要服务的多宿主改造,最大限度降低了设施断开可能引发的影响。
在确认圣何塞数据中心内各关键服务调整完成之后,我们开始为黑洞项目做最后的准备。
在实施日期的约两个月前,我们与网络工程团队合作,决定采取渐进式的方法进行测试筹备。在合作中,我们定下了三个主要目标:
最初,我们打算清空城域内的网络路由器,借此把圣何塞数据中心跟网络隔离开来。虽然这样也行,但我们最终还是决定采取纯物理方法,这样能够更好地模拟真实的灾难场景:拔掉网络光纤!在决定采取这种方法后,我们开始归纳具体的规程操作(MOP),也就是在“黑洞”降临那天到底该怎么分步进行。总体来看,我们的 MOP 基本就是下面几步:
在确定了整个计划之后,我们开始在达拉斯沃思(DFW)城域进行两轮试运行。之所以选择这个城域,是因为它更符合低风险要求:几乎不承载任何关键服务,所有服务均采用多宿主设计,而且设施弹性极佳。
DFW 城域共包含两处数据中心设施,DFW4 和 DFW5。我们决定先从单一数据中心开始进行首轮测试,之后再测试双设施齐断的场景。
为了做好准备,网络和数据中心团队拍摄了照片,确保网络光纤状态正常。他们还订购了备用硬件,防止意外故障令设施无法及时恢复。另外,灾难准备团队还定义了中止标准,并与各团队协调以开展流量导引、警报/自动修复禁用等事宜。
DFW 首轮测试的日子终于到了。我们 20 多人齐聚 Zoom 会议室,盯着屏幕上的 MOP 计划。大家都清楚自己的角色,万事齐备。按照计划,我们果断拔掉了 DFW4 的光纤网线。
在验证过程中,我们很快发现外部可用性出现了下降——这一点大家可没料到。等了约四分钟后,我们打通了测试中止电话,重新接上了网络光纤。到这里,首轮测试可以说是失败了,因为我们根本坚持不到 30 分钟的网络离线目标。
失败的核心原因,就是离开网络的 DFW4 数据中心是我们 S3 代理的所在设施。所以运行在 DFW5 中的服务会继续尝试跟本地 S3 代理通信、但却不断失败,这就导致服务受到影响、最终拉低了全局可用性。
在测试之前,我们误以为 DFW4 和 DFW5 应该没什么区别,所以断开一处应该不会影响另一处。但测试结果证明,不同设施间总会存在一些难以想象到的依赖性,所以不能粗暴将目标设施理解成完全独立的故障点。所以跟直接断开整个城域设施相比,单处设施的断开反而会引发更大的影响。
另外需要注意的是,灾难恢复测试的意义就在于帮助我们吸取教训。在首轮测试中,灾难准备团队和其他各部门都学到了宝贵的经验。具体包括:
于是乎,我们在接下来的测试 MOP 中引入了两个新的步骤:
利用前面积累到的知识,我们在几周后又重新试了一次。这回,我们决定把整个 DFW 设施直接断开。现场拍照完成、备用硬件到位,我们紧张地等待着第一次真正意义上的全城域测试。
我们首先清空了本地关键服务,之后按上面的流程执行剩余步骤。两位 Dropbox 人员已经前往设施现场,并根据命令迅速拔掉了网络……这一次,我们没有观察到任何明显的可用性影响,而且整个黑洞测试成功持续了 30 分钟。进步很大,结果喜人,我们觉得同样的套路放在圣何塞那边也应该能行。
DFW 测试给我们上的重要一课,就是务必要让非关键服务的所有者(包括部署系统、提交系统和内部安全工具等)都开始以批判性的角度思考 SJC 黑洞测试会带来怎样的影响。我们创建了一份影响文档,希望能以共识性的方式理解 SJC 黑洞测试期间、有哪些服务可能无法正常运行。
而在考虑后续测试的具体推进时,我们又从中发现了另一个重大利好:这些测试帮助我们训练了关键服务团队及待命运维人员,他们也更了解我们在用怎样的方式开展黑洞测试。有了这样的积累,我们已经拥有充分的信心、认定 SJC 这票大动作也一定能取得成功。
2021 年 11 月 18 日星期四,SJC 的网线已经在瑟瑟发抖。我们在 SJC 三处数据中心设施内各安排了一名 Dropbox 员工。还是一样,他们拍好照片、准备了备用硬件,防止在拔出或重新接入光纤网线时不慎造成端口损坏。大约 30 个人聚集在 Zoom 会议室里,更多同事则加入了 Slack 频道,公司里弥漫着一种登月火箭发射前的紧张感。
最终,太平洋时间下午五点,三处设施同时断开了网络连接。跟第二轮 DFW 测试时一样,我们还是没看到全局可用性出现太大的波动——SJC 黑洞测试的 30 分钟目标同样顺利达成!
呃,好吧,我知道这听起来好像缺了点戏剧冲突。但这样才对吧,我们为测试做了那么多准备,结果就应该这样顺顺利利、无声无息。
虽然我们关注的一些内部服务还是受到了一些意外影响,但总体来讲测试还是取得了巨大成功。事实证明,即使面对整个城域彻底断开这种发生几率极低的事件,我们的故障转移堆积仍然能凭借适当的人员和流程配置显著缩短 RTO,而且 Dropbox 的业务能在另一区域中继续保持顺畅运行。更重要的是,我们的黑洞演习也证明即使没有 SJC,Dropbox 业务仍然屹立不倒!
从左至右,Eddie、Victor 和 Jimmy 三位同事在 SJC 数据中心内同时拔下网络光纤。
长达 30 分钟的 JSC 城域离线,代表着 Dropbox 在灾难准备方面迈出的重要一步。我们证明,Dropbox 已经拥有了必要的工具、知识和经验,能够在灾难严重到整个城域完全断开时继续保持业务运行。这些改进,也让我们得以在服务的可靠性与弹性方面继续笑傲整个业界。
这是一项耗时多年的努力,离不开 Dropbox 各个团队之间的认真规划与协同配合——考虑到 Dropbox 服务及依赖性的复杂体系,这样的故障转移肯定还是存在风险。但我们凭借着尽职调查、频繁测试与规程改进,成功将这些风险降至最低。
更重要的是,黑洞测试的经验也帮助我们强化了灾难准备工作的核心原则:如同肌肉一样,灾难准备的能力也需要不断训练和演习。随着黑洞测试频率的提升,我们的灾难准备能力也会持续提高。只要准备工作到位,用户永远感受不到任何异常状况。Q 弹可靠的 Dropbox,才是好的、值得信赖的 Dropbox。
最后,我们要感谢每一位为黑洞测试付出努力的 Dropbox 同仁,感谢大家为这一全新里程碑所贡献的力量。如果没有几十个团队中每位成员共同打赢的几百场小仗,我们就无法达到这样的里程碑。
原文链接:
https://dropbox.tech/infrastructure/disaster-readiness-test-failover-blackhole-sjc
领取专属 10元无门槛券
私享最新 技术干货