编者按:InfoQ 开设新栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注。本文节选自孙宇聪翻译的《 SRE Google 运维解密》中的第26 章“数据完整性:读写一致”的一节“Google SRE 保障数据完整性的手段”。目前本书已上市,可在各主流渠道购买。
关于译者:孙宇聪,曾担任Google Senior Site Reliblity Engineer 长达7 年时间, 参与Youtube 视频转码、存储、直播管理系统的设计维护,以及全球Youtube CDN 网络的研发管理工作,之后还曾就职于Google 内部云计算部门,从事开发维护全球百万台服务器生命周期管理系统及任务管理系统。
就像我们假设Google 的底层系统经常出问题那样,SRE 同样假设任何一个数据保护机 制都可能在最不适合的时间出现问题。在所依赖的软件系统不停改变的情况下保障大规 模数据的完整性,需要很多特定选择的、相互独立的手段来各自提供高度保障。
由于数据丢失类型很多(如上文所述),没有任何一种银弹可以同时保护所有事故类型, 我们需要分级进行。分级防护会引入多个层级,随着层级增加,所保护的数据丢失场景 也更为罕见。图 26-2 显示了某个对象从软删除到彻底摧毁的过程,以及对应的分级数据 恢复策略。
第一层是软删除(soft deletion)(或者是某些 API 提供的“懒删除”机制)。这种类型的 保护在实践中被证实是针对意外数据删除的有效手段。第二层,是备份和对应的恢复机 制。第三层,也就是最后一层,更多信息参见本章后面“第三层 :早期预警”小节。在 这三层中,如果同时还有复制机制,那么在某些场景下对数据恢复是很有用的(但是数 据恢复计划不应该依赖于复制机制)。
当更新速度很快,同时隐私很重要的时候,大规模数据丢失和损坏通常是由应用程序的 Bug 造成的。事实上,数据删除逻辑的 Bug 非常常见,以至于快速复原一定时间内的删 除操作是针对永久性数据丢失的第一道防线。
任何一个保障用户隐私性的产品都必须允许用户删除全部或者一部分数据。这种产品不 可避免地要处理误删除场景。允许用户恢复他们的数据(例如,使用回收站机制)可以 降低出现频率,但是不能彻底消除这种场景,尤其是当该服务允许第三方插件删除数据 的时候。
软删除机制可以大幅度减少支持人员的压力。软删除意味着被删除的数据立刻被标记为 已删除,这样除了管理后台代码之外其他代码不可见。管理后台这里可能包括了司法取 证场景、账号恢复、企业管理后台、用户支持,以及在线排错等。在用户清除自己的应 用中的垃圾箱时应该采用软删除,同时提供某种支持工具允许有授权的管理员恢复误删 除的文件。Google 在使用量最高的工具上都实现了这种机制,否则用户支持所带来的压 力是无法持续的。
另外一个常见的数据误删除场景是由于账号被劫持造成的。在这个场景中,劫持用户账 号的人常常会先将用户原始数据删除,再用这个账号来发送垃圾或者进行其他非法活动。 当我们将用户误删除和账号劫持两个场景结合起来,在应用程序层内,或者其下某层实 现软删除机制的需求就非常明显了。
软删除还意味着一旦数据被标记为已删除,在某段时间以后会真的被删除。这个时间的 长度,尤其是在有很多短期数据的情况下,取决于某个组织的政策和相关的法律条文、 可用的存储空间和存储成本,以及产品的价格和市场定位等。常见的软删除时间是 15、 30、45 或者 60 天。在Google 的经验中,大部分账号劫持和数据完整性问题都会在 60 天内汇报或者检测到。因此,为软删除保存超过 60 天的需求可能没有那么强烈。
Google 同时发现最严重的数据删除案例经常是由于某个不熟悉现有代码的开发者实现新 的删除逻辑时造成的。尤其是在开发某种离线批处理数据流水线的时候(例如,某个离 线的 MapReduce,或者 Hadoop 流水线)。于是,在设计接口的时候,最好能够使得不熟 悉现有代码的开发者无法(或者非常困难)绕过软删除逻辑。一个有效方法是让云计算 API 提供内置的软删除和恢复删除 API,同时确保启用这项功能。再好的盔甲也要穿上才能起作用。
面向直接用户的 Gmail 或者 Google Drive 的删除功能都采用了软删除策略,但是云计算 API 类型的服务怎么办呢?假设该项服务已经提供了可编程的软删除和还原机制,同时 有比较合理的默认值,剩下的误删除场景就是由内部开发者或者是外部开发者错误操作 造成的数据丢失了。
针对这种场景,有时候可能会再增加一层软删除机制,我们称之为“懒删除”机制。可 以将“懒删除”认为是某种幕后清理机制,由存储系统控制(与此相比,软删除是由程 序或者服务直接控制的)。在懒删除场景下,由某个云计算应用程序删除的数据马上对应 用程序不可用,但是由云计算服务提供商保留几周时间再彻底销毁。懒删除不一定在所 有的策略中都适用 :在一个短期数据很多的系统中保存长期懒删除数据可能成本会很高, 甚至在某些需要保障已删除数据的销毁时间的系统中不可行(例如某些隐私敏感的程序)。
历史记录功能属于哪种?有些产品允许将某个对象恢复到之前某个状态。当用户直接可 用这种功能时,这其实就是回收站机制的一个变种。当这种功能针对开发者可用时,取决于具体实现,可以用来替代软删除机制。
对 Google 来说,历史记录功能在恢复某些特定数据损坏场景下比较有用,但是在恢复 大部分数据丢失的场景中(包括误删除、人工以及程序化都算)都没有作用。因为在某 些历史记录功能的实现中,将删除作为一个特例处理,要求之前的历史状态也要被删除, 而不是仅仅修改历史记录中的一条记录。为了针对这些场景提供合理的防护,我们也需 要将懒删除与 / 或软删除机制应用于历史记录上。
- 使用哪种备份和还原方法。
- 通过全量或者增量备份建立恢复点(restore point)的频率。 y 备份的存储位置。
- 备份的保留时间。
另外一个关键问题是 :在一次数据恢复中能够损失多少最近数据。能够损失的数据越少, 增量备份策略就越重要。在 Google 的一个极端的例子中,旧版的 Gmail 采用了一个准 实时的流式备份系统。
就算不考虑成本问题,频繁地进行全量备份也是非常昂贵的。最主要的是,这会给线上 服务用户的数据存储造成很大的计算压力,以至于影响服务的扩展性和性能。为了缓解 这种压力,我们需要在非峰值时间段进行全量备份,同时在繁忙时间段进行增量备份。
在此之外,故障恢复的时间很重要。所需的故障恢复时间越短,备份存放的位置越需要 本地化。Google 通常只会将那些恢复很快,但是成本很高的“快照”存储很短的一段时 间(在同一个存储实例中)。注 8 同时,将其他长时间的备份存放在处于同一个(或者很近的) 数据中心的分布式存储上。这样一个策略本身并不能保护单个部署点的故障,所以这些 备份经常会在一段时间后被转移到其他离线存储上,从而给更新的备份腾出地方。
备份应该保存多久呢?备份策略随着保存时间的增长,成本会上升,但是同时可恢复的 时间也会提高(注意,这里也会受边际效应的影响)。
Google 的经验证实,应用程序 Bug 造成的低等数据损坏,或者应用程序内部的删除 Bug 需要的恢复时间最长,因为这些 Bug 可能在引入之后几个月才被发现。这样的场景意味 着我们需要能够恢复得越久越好。
但是,在高速更新的开发环境中,代码和数据格式的改变可能使得旧的备份很快就无法 使用了。更重要的是,试图恢复不同部分的数据到不同的时间点可能是不可能的,因为 这需要同时操作多个备份。但是,这恰恰是这种低等数据损坏和丢失场景中最需要的。
本章后面“第三层 :早期预警”一节中描述的策略都是为了加速检测应用程序 Bug 造成 的数据问题,以便于减少需要这种复杂恢复场景的需求。然而,我们在无法预知问题类 型之前,如何确定合理的备份保存周期呢? Google 将大部分服务的备份周期定于 30~90 天之间。每个服务必须通过针对数据丢失的容忍度设计和早期预警系统的研发投入来保 障落入这个区间。
将以上针对 24 种组合的预防建议总结得出 :以合理成本满足一系列广泛的数据恢复场 景都需要分级备份。第一级备份是那些备份频率很高,并且可以快速恢复的备份。这些 备份保存在与线上数据存储距离最近的地方,使用与数据存储相同或者是非常相似的存 储技术。这样可以为大部分的软件 Bug,以及开发者错误的场景提供保护。由于成本相 对较高,备份在这一层只会保留数小时,最多 10 天之内,恢复时间在几分钟内。
第二级备份的频率较低,只保留一位数或者两位数字的天数。第二级备份保存在当前部 署点的随机读写分布式文件系统上。这些备份可能需要数个小时来备份,是用来为服务 所使用的数据存储出现的相关问题提供额外保护的,但是要注意,这无法为用来保存备 份的技术栈自身的问题提供保护。这一级别的备份也可以用来针对那些检测时间太晚的 应用程序 Bug,以至于无法使用上一级别的备份恢复的问题。如果新版本的发布时间一 般是一周两次,那么保存一周到两周再删除这种备份可能是合理的。
接下来的级别会使用冷存储,包括固定的磁带库以及异地备份存储设施(不管是磁带, 还是磁盘)。这些级别的备份可以保护单个部署点级别的故障,例如数据中心电源故障, 或者是 Bug 造成的分布式文件系统的数据损坏。
在不同的层级中转移大量数据是很昂贵的,但是后续层级的数据存储容量并不会与生产 系统线上数据容量竞争。所以,后续层级的备份数据通常产生的频率很低,但是可以保 存的时间更长。
额外一层 :复制机制
在理想世界中,每个存储实例,包括那些保存了备份的存储系统都应该具有复制机制。 否则,就有可能在数据恢复过程中,才发现备份文件自身丢失了数据,或者发现保存备 份文件的那个数据中心正在进行维护。
随着数据量的增长,不是每个存储系统都可以进行复制。在这种情况下,将不同层级的 备份存放在不同部署点上是更合理的,这样每个部署点不会同时出现故障。同时,应该 将备份文件放置在某种冗余机制之上,例如 RAID、Reed-Solomon 纠错码,或者 GFS 类 型的复制机制之上。注 9
当选择冗余系统时,不要选择一种不常用的系统。因为这种系统的可靠性只能通过本身 就很罕见的数据恢复过程来测试。正确的做法是,选择一个非常常见,并且被许多用户 持续使用的系统。
1T vs. 1E :存储更多数据没那么简单
针对 T 级别(Terabytes)的流程和手段在 E 级别(Exabytes)并不能工作得很好。在几 个 GB 的结构化数据上进行校验、复制和进行端到端测试可能有些挑战。但是只要我们 有足够的数据格式信息,了解数据的交易模式等,这个过程并不会有特别多的难点。一 般来说,我们只需要找到足够的机器资源,遍历全部数据,执行某种校验逻辑就可以了, 可能还需要有足够的存储空间放置数据的几个副本。
现在,我们用同样的策略来校验 700 Petabytes 的结构化数据。就算我们使用一个理想化 的 SATA 2.0 接口(300MB/s 的性能),仅仅是遍历一遍所有数据进行最基本的校验也将 需要 80 年。而进行几次全量复制,就算我们有足够的存储媒介也至少需要同样长的时 间。备份的恢复时间,再考虑到一些后续处理时间则会需要更长。这样看来,我们可能 会需要将近一个世纪的时间来恢复这些数据,别忘了这些数据在恢复的时候已经是至少 80 年以前的了。显而易见,这个策略需要重新设计。
处理海量数据,最重要也最有效的方式是给数据建立一个“可信点”—也就是一部分 数据校验过之后,由于时间等原因变成不可变数据了。一旦我们确信某个用户的资料或 者交易记录已经是不会再变的了,就可以校验并且进行合适的复制,以便未来恢复。接 下来,我们可以进行增量备份,其中仅仅包含自从上次备份后新增或者更改过的数据。 这种技术可以将备份时间缩减到与主要处理逻辑的吞吐量在一个数量级内。这样的话, 经常性的增量备份就可以替代需要 80 年的巨型校验和复制任务了。
然而,上文说过,我们最关注的是恢复,而不是备份。假设三年前进行了一次全量备份, 然后每天进行增量备份。完全恢复一份数据就需要顺序处理大概 1000 个相关性非常强 的备份。每一个相关性很强的备份都会增加故障发生的风险,这还没有考虑到后勤和计 算上的成本。
另外一个降低复制和校验任务的时间总量的方法是分布式计算。如果我们将数据合理分 片,可以将 N 个任务并行进行,每个任务可以负责复制和校验 1/ N 的数据。这样需要在 设计部署阶段预先进行一些考量,以便于 :
- 正确平衡数据分片。
- 保证每个分片之间的独立性。
- 避免相邻并行任务之间的资源抢占。
通过水平分割负载,同时按时间维度进一步限制垂直数据量,我们可以将 80 年的处理 时间降低几个数量级,这样恢复策略才能有效工作。
第三层 :早期预警 “脏”数据并不会一直静止不动,它们会在整个系统中传播。针对不存在的或者是已经 损坏的数据的引用可能会被复制好几份。随着每次更新,数据存储中数据的整体质量会 持续下降。关联性的交易和潜在的数据格式变化使得从某个指定备份中还原数据的能力 随着时间大幅下降。越早检测到数据丢失,数据的恢复就越容易,也越完整。
云计算开发者面临的挑战 在高创新速度的环境中,云计算应用程序和基础设施服务面临着很多数据完整性的挑战, 例如 :
- 不同的数据存储之间的引用完整性。
- 数据结构的改变。
- 旧代码。
- 无停机时间的数据迁移。
- 与其他服务接口的不断变化。
如果不主动花费一定的精力在数据关系的管理上,任何一个不断增长的服务的数据质量 肯定是会不断下降的。
一般来说,初级的云计算平台开发者会选择使用一个分布式一致性的存储 API(例如 Megastore),也就是将应用程序数据一致性的问题交给该 API 使用的分布式共识性算法 来保障(例如,Paxos,见第 23 章)。开发者认为选择合适的 API 就足以保障应用的数据 完整性了。于是,开发者通常将所有应用数据合并到单个确保分布式一致性的存储方案中, 从而避免造成引用一致性的问题,哪怕这样会造成性能和扩展性的问题。
虽然分布式共识性算法理论上无懈可击,但是具体的实现经常充满了 Hack、优化、Bug 和一些猜测成分。例如 :理论上来说,Pa x o s 会忽略掉故障的计算节点,只要有满足法 定仲裁数量的节点还存在就能够继续进行工作。但是,在实际实现中,具体的忽略逻辑 要应对对应的超时、重试和其他灾难处理方法,这些都埋藏在具体的 Pa x o s 实现中(参 见文献 [Cha07])。Paxos 应该等待多长时间再忽略一个失去响应的节点?当某个具体的 物理机器出现某个特定故障时(可能是暂时的),符合某种特定的时间,以及出现在某 个具体的数据中心时,可能会有未定义的行为发生。应用程序的部署范围越大,那么受 这种无法预知的不一致性的影响就越严重。如果这个逻辑对 Paxos 实现适用(Google 的 经验证明的确如此),那么对采用最终一致性的 Bigtable 等实现来说就更适用了(Google 的经验再次证明)。不检查受影响的应用程序的话,没有任何办法可以 100% 确定数据是 正确的 :虽然我们相信存储系统的实现,但是还是要校验!
使这个问题变得更复杂的是,为了从低级数据损坏或者删除场景中恢复数据,我们必须 从不同恢复点的不同备份中恢复不同子集的数据。同时,我们还要考虑到在一个不断变 化的环境中,代码和数据结构的变化对老的备份数据的影响。
带外数据校验
为了避免用户可见的数据质量下降,以及在无法恢复之前检测到低级的数据损坏以及数 据丢失,我们需要一整套带外(out-of-band)检查和修复系统来处理数据存储内部和相 互之间的数据问题。
大部分情况下,数据校验流水线被实现成一系列 Map Reduce 任务或者是 Hadoop 任 务。更常见的是,这些任务通常是在一个服务成功之后后续添加的。有些时候,这些流 水线任务在服务达到某个扩展性节点的时候第一次被引入,随着架构的调整完全重写。 Google 针对上述每一种情况都构建了对应的校验器。
安排一些开发者来开发一套数据校验流水线可能会在短期内降低业务功能开发的速度。 然而,在数据校验方面投入的工程资源可以在更长时间内保障其他业务开发可以进行得 更快。因为工程师们可以放心数据损坏的 Bug 没那么容易流入生产环境。和在项目早期 引入单元测试效果类似,数据校验流水线可以在整个软件开发过程中起到加速作用。
举一个具体的例子:Gmail 拥有一系列数据校验器,每一个校验器都曾经在生产环境中 检测到真实的数据完整性问题。Gmail 开发者由于知道每个新引入的数据一致性问题都 可以在 24 小时之内被检测到而感到非常安心。这些数据校验器的存在,结合单元测试 及回归测试的文化使得 Gmail 开发者有足够的勇气每周多次修改 Gmail 生产存储系统的 实现。
带外数据校验比较难以正确实现。当校验规则太严格的时候,一个简单的合理的修改就 会触发校验逻辑而失败。这样一来,工程师就会抛弃数据校验逻辑。如果规则不够严格, 那么就可能漏过一些用户可见的数据问题。为了在两者之间取得恰当的平衡,我们应该 仅仅校验那些对用户来说具有毁灭性的数据问题。
例如,Google Drive 的周期性校验文件内容与 Drive 文件夹中的列表一致。如果不一致, 那么某些文件可能缺少数据—也就是毁灭性的问题。负责开发 Dr i v e 所用基础设施的 工程师非常注重数据一致性,他们甚至将数据校验器增强为可以自动修复这种不一致情 况。这个安全机制使得 2013 年发生的一次“天啊,所有文件都在消失”的意外情况变 成了“我们可以放心回家睡觉,周一再来修复根源问题”的情况。这样,数据校验器提 高了工程师的士气,避免了加班,也提升了功能上线的可预知性。
大规模部署带外检测器可能成本较高。Gmail 计算资源的很大一部分都被用来支持每日数据检测的运行。使这个问题变得更严重的是,校验器本身可能会造成软件服务器缓存 命中率的下降,这就会造成用户可见响应速度的下降。为了避免这种问题,Gmail 提供 了一系列针对校验器的限速机制,同时定期重构这些校验器,以降低磁盘压力。在某一 次重构工作中,我们降低了 60% 磁盘磁头的使用率,同时没有显著降低校验器覆盖的范 围。虽然大部分 Gmail 检测器每天运行一次,但是压力最大的校验器被分为 10~14 个分片, 每天只会运行一个分片。
Google 云存储(compute storage)是另外一个数据量显著影响数据校验的例子。当带外 校验器已经不能一天之内完成时,云存储工程师开发了一种更有效校验元数据的方法, 而不再使用暴力方法校验。就像在数据恢复机制中那样,带外数据校验也可以分级进行。 随着一个系统的规模不断扩展,每天能够运行的校验器越来越少。我们可以保证每天运 行的校验器持续寻找毁灭性的问题,而其他更严格的校验器可以以更低频率运行,以便 满足延迟和成本的要求。
分析校验器为何失败也需要很多精力。造成某项间断性不一致情况的原因可能在几分 钟、几小时或者几天之内消失。因此,快速定位校验日志中的问题是非常关键的。成熟 的 Go ogle 服务给 on - call 工程师提供了非常完备的文档和工具,用来定位问题。例如, Gmail 提供给 on-call 工程师的有 :
- 一系列 Playbook 文章讲述如何应对某种校验失败的报警。
- 类似 BigQuery 的查询工具。
- 数据校验监控台。
一个有效的带外数据校验系统需要下列元素 :
- 监控、报警和监控台页面。
- 限速功能。
- 调试排错工具。
- 生产应急应对手册。
- 校验器容易使用的数据校验 API。
大部分高速进行的小型开发团队都负担不起设计、构建和维护这样的系统所需的资源。 就算强迫他们进行,最后的结果也经常是不可靠、不全面的,以及浪费性、一次性的方案, 很快就会被抛弃。因此,最好的办法是单独组织一个中央基础设施组为多个产品和工作 组提供一套数据校验框架。该基础设施组负责维护带外数据校验框架,而产品开发组负 责维护对应的业务逻辑,以便和他们不断发展的产品保持一致。
确保数据恢复策略可以正常工作
一个电灯泡什么时候会坏掉?是在拨动开关而灯却不亮的那一瞬间坏的吗?当然不 是—电灯泡可能早已经坏了,拨动开关时只是注意到了这个已经损坏的情况。而这时, 你要面临一个漆黑的房间,可能还会扎到自己的脚趾(美国俚语,指自己给自己制造了 障碍)。
同样的,成功进行一次数据恢复所需要的资料(通常是你的备份数据)可能也处于一个 损坏的状态,在试图进行恢复之前我们并不知道。
如果能够在真正需要这些数据之前检测到数据损坏,我们就可以在灾难发生之前避免它们 :我们可以再进行一次备份,准备更多的资源,或者修改 SLO。那么,主动检测这些 问题需要以下步骤 :
- 持续针对数据恢复流程进行测试,把该测试作为正常运维操作的一部分。
- 当数据恢复流程无法完成时,自动发送警报。
数据恢复流程中任何一个环节都有可能出问题,只有建立一个完善的端到端测试才能让 人晚上放心睡觉。即使最近刚刚成功进行过一次数据恢复,下次执行时某些步骤仍然可 能出问题。如果读者从本章仅仅学到一个知识点,那就是一定要记住 :只有真正进行恢 复操作之后,才能确定到底能否恢复最新数据。
如果数据恢复测试是一个手工的、分阶段进行的操作,那么就不可避免地成为一个讨人 嫌的麻烦事。这样会导致测试过程要么不会被认真执行,要么执行得不够频繁以至于作 用有限。因此,在任何情况下,都应该追求完全自动化这些测试步骤,并且保证它们能 持续运行。
数据恢复计划中需要覆盖的点是很多的 :
- 备份数据是否是完整的、正确的—还是空的?
- 我们是否有足够的物理机资源来完整地完成整个恢复过程 :包括运行恢复任务本身,真正恢复数据,以及数据后处理任务?
- 整个数据恢复过程能否在合理的时间内完成?
- 是否在数据恢复过程中监控状态信息?
- 恢复过程是否依赖于某些无法控制的元素—例如某个不是 24×7 随时可以使用 的异地存储媒介?
Google 不光发现过上述列表中提到的每一个问题,还意外发现了很多其他没有提到的失 败场景。如果我们没有在日常测试中主动寻找这些问题,而是在每一次进行数据恢复的时候才意外发现,那么 Google 很多非常受欢迎的产品可能就不会存在了。
失败是不可避免的,不去主动寻找这些数据恢复失败的场景等于玩火自焚。只有通过进 行测试来主动寻找这些弱点,才能够提前修复,关键时刻才不至于追悔莫及!
书籍介绍
大型软件系统生命周期的绝大部分都处于“使用”阶段,而非“设计”或“实现”阶段。那么为什么我们却总是认为软件工程应该首要关注设计和实现呢?在《SRE:Google 运维解密》中,Google SRE 的关键成员解释了他们是如何对软件进行生命周期的整体性关注的,以及为什么这样做能够帮助 Google 成功地构建、部署、监控和运维世界上现存 zui 大的软件系统。通过阅读《SRE:Google 运维解密》,读者可以学习到 Google 工程师在提高系统部署规模、改进可靠性和资源利用效率方面的指导思想与具体实践——这些都是可以立即直接应用的宝贵经验。
评论