随着为分布式世界重建遗留 RDBMS (ACID 事务、关系、约束、规范化建模)的原语,数据库世界正在经历复兴。
这些新的系统从技术上来说是 CP 而不是 AP,但是有很高的容错性。
这些系统大多数依赖于 Google 的 Spanner 或 Percolator 协议,使用两阶段锁,需要物理的同步时钟来保证一致性。
一些系统采用基于 Calvin 协议的预提交方法,比如 FaunaDB,不需要特别的硬件就能提供更强的保障。
在这两种情况下,系统开发人员不再需要放弃事务来获得现实世界中的可伸缩性和可用性。
数据库复兴运动起源于 NoSQL,大约是因为 2008 年新的一大波互联网公司(Twitter、Facebook、Google、Amazon)的技术需求远超传统的 RDBMS 能力。另外,像 Oracle 这样的 RDBMS 供应商的价格体系不能够支持这些新兴公司的较小单位经济效益,尤其是广告支持的公司。在这种情况下要怎么做呢?
一开始,公司会使用遗留数据库作为存储引擎的自定义、分布式数据分片服务,尤其是 MySQL 和 InnoDB。像 Memcache 和之后的 Redis 这样的分布式缓存无处不在。然而,这些系统不是自运维的,它们仍然需要 DBA 的干预才能从失败中恢复。它们不是自动分片的,也不是自修复的。
当时,我在 Twitter 管理软件基础设施团队,对我们而言,完全有可能构建一个可以透明地大规模和灵活交付的系统,因此,连同业内的其他人,我们开始将其作为开源项目着手来做,一开始主要是寄希望于 Apache Cassandra、Hadoop 和其他存储平台。当你的业务规模变大的时候,任何会阻碍它前进的东西都需要放弃。
这些数据库系统的商业供应商号称他们的系统不能提供某些 RDBMS 传统功能,因为没人需要这些功能,但这显然是错误的。现在每个企业都需要扩大规模,然而我们为了使用 NoSQL 放弃了什么呢?我们能把放弃的东西拿回来吗?
最终一致性的实践效果
CAP 理论可以有效地概括为:当网络分片时会发生什么?系统会保证一致性(正确性)而暂时停一下,还是为了保证可用性,而尽最大努力保证它的运行?其实在实践中,一致性和可用性的选择是模棱两可的,真实世界的情况更加微妙。
第一代 NoSQL 系统是最终一致的,在分区事件发生之后,它们会在可变但有限的一段时间之内协调冲突,对数据进行概率投票。在现实世界中,很少有这么复杂的最终一致性系统,相反,它们使用基于本地系统时间的简单“最后一次写入获胜”机制。即使它再智能,对搭建应用程序也没有实质上的帮助。它需要系统设计人员将整个主机的冲突解决功能引入到应用程序层。
举个例子
明显的交易失败模式如下所示:
Bob 在 ATM 存了 200 美元。
数据库分片接受 Bob 的“最终一致”存款。他的账户余额 200 美金更新需要排队等待。
Bob 又通过移动应用程序存了 100 美元。这次交易更新使用了另一个分片,并复制给其他的集群,但没有到原来的分片。
网络恢复,复制发生。
由于第二个交易发生的时间比较晚,所以 Bob 的余额根据第 3 步的分片被更新为 100 美元。
Bob 现在损失了 200 美元现金。最终一致性能保证可用性。在这种情况下,我们存钱的数目多少并不能保证正确。
CRDT 和区块链
为了解决这个问题,人们想出了各种方案,特别是 CRDT(conflict-free replicated data types),它能帮助有效地关闭所有的中间状态,保证最终值正确重建。在这种情况下,Bob 的交易会被记录为借方和贷方,而不只是最终余额。最终余额的获得可以在数据库或应用程序层中完成。一种例子是区块链,它会记录集群上所有节点每时每刻的交易。
然而,在实际操作中,这不能解决所有的问题,因为数据库不是封闭的系统。任何数据读写的重点在于协调外部影响。如果 Bob 从 ATM 取出现金,即使之后 CRDT 显示当时他的余额不足,但是钱也被拿走了。
这并不是一个完全的理论问题。恶意使用 ATM 的漏洞是一种欺诈行为。因此,数据库需要“外部一致性”,就是俗称的 ACID 属性。
遗留解决方案
遗留数据库(通常是集中的 RDBMS)通过非分布式来解决这个问题。其实说到底,就是一个有单个磁盘的单机,并且出于实际目的,该磁盘基于一个互斥锁进行更新,事实就是这么回事。对状态的更新是序列化的,这代表它们是根据单一、确定的顺序发生的,并且是外部一致的,对应着实际发生顺序。任何规模化都是出于单机的垂直扩展。
该模型很方便实现,但不能满足很多关键需求,特别是和可用性相关的问题。
因此我们迎来了主从复制系统,主节点可以被 DBA 手动替换为从节点,该从节点异步地复制了主节点的状态。这虽然不是很好,但是不像最终一致性分布式系统,不一致的范围是已知的:恰恰是最后一次事务复制到从节点,而主节点发生了外部可见的故障,这之间就出现了不一致情况。
这是互联网分片系统真实运行的情况:
它们可以在分片(单机)中执行交易。
它们可以手动或半手动地将故障备份到备份分片上。
它们不能同时在多分片进行事务处理。
对于 Twitter 和 Facebook 来说这勉强可行,但并不可取。比如说,如果在手机上得到的消息通知在网站上不能读,这是很奇怪的,但这并不是灾难性的。需要事务处理的产品特性(例如,用户名分配)在遗留的 RDBMS 中没有那么大的伸缩性。
但是随着产品变得越来越复杂,缺乏外部一致性的劣势也变得更加严重。
Google Spanner
Spanner是 Google 对于这两个问题的解决方法。最初它只在 Google 内部的基础设施上使用,现在作为托管产品在 Google Cloud 上可以获得。
它完成了两件事:
多分片事务通过两阶段准备/提交算法实现,基本上类似于 20 世纪 80 年代的事务监控协议 Tuxedo。
不再依赖 HA 或是主机级别的硬件来保证可用性,通过 Paxos 自动实现商品硬件的分片故障转移。
这个方法在某种程度上很有效果。它保证了可串行化,能根据实时顺序对每个单独的分片进行更新。但它不能保证外部一致性,也不能保证分片之间的实时协调。为了解决这个问题,Spanner 需要做这件事:
物理原子钟硬件在很小的误差范围之内同步所有分片上的系统时间。
现在事务协调器就能放心地说:“分片们,我在 T 时间做了一个事务,如果你没有看到在我们共享时间误差范围内的其他任何更新,那就是不冲突的”。这引发了每次最终一致读写间的小延迟,因为每个分片都需要等待时钟模糊窗口。
除去延迟的影响,这对 Google 来说很可行。它们有资源来构建并管理自定义的原子时钟硬件和有限延迟的网络。然而,有许多新的数据库系统在没有原子钟的情况下实现类似的协议,但总是要付出代价的。
基于 NTP 的 Spanner
基于 NTP 时钟同步的数据库有更长的模糊窗口,通常需要几百毫秒。实践中,这些系统完全放弃等待,转而保证没有外部一致性的单记录可串行化。这可能会导致类似的交叉线模列和双借方效果。
它们还不提供快速地外部可串行化读取,通常从最后一个已知的 Paxos leader 这里读取。这可能违背了可串行化,因为 leader 可能不知道它已经被废弃,于是在选举窗口(通常是几秒钟)愉快地提供了已经废弃的值。要预防这个窗口的发生需要暂停。
最后,时钟是否会碰巧不同步,这也是 Google 花费心思需要预防的,因为在云中任何与时钟无关的事件,比如 VM 停顿,都会造成这个情况的发生,甚至写端的可串行化保证都会丢失。
Google Percolator
另一类数据库查询单个物理时钟(Google Percolator 的论文中称其为”clock oracle”)拥有类似共享时钟的往返互联网延迟的模糊窗口,更糟糕的是,还会遇到明显的单点故障。
这个模型类似于多处理器 RDBMS,它也用了单个物理时钟,因为它是单机,但是系统总线被网络所取代。在实践中,这些系统放弃了多区域扩展,受限于单个数据中心。
很明显没有物理时钟同步,分布式外部一致性是不可能的,还是有其他方法?
使用 Calvin 规避物理时钟
如果你可以创造给所有的事务服务的逻辑 clock oracle,不依赖于任何单个物理机器,还可以广泛分布,那怎么样?这就是Calvin完成的工作。Calvin 是耶鲁大学Daniel Abadi实验室开发的。
John Calvin 是法国新教改革者,他相信每个人都有最终的宿命,无论是上天堂还是下地狱,都是上帝在创造世界之前决定的。
这也是 Calvin 的机制。事务的顺序是预处理决定的,节点的子集作为分区的 Oracle,给一系列流水线批处理中传入的事务分配外部一致的顺序。之后,批处理用 10 毫秒的时间提交到分布式提前写日志,可以用 Paxos 或 Raft 实现。
这种预处理有很多好处:
它可以在单个网络往返中完成提交到分布式日志,不需要副本上的两阶段锁。
它的操作拓扑和副本拓扑不同,减少了长尾延迟。
可串行化读不需要协调,也不需要等待模糊窗口。相反,它们像最终一致性系统那样在全局内扩展。
然而,它肯定会做出一些让步:
由于事务是分批提交的,写延迟不能低于等待下次提交的时间,平均约为 5 毫秒,最多可到 10 毫秒。
FaunaDB进行了一系列性能改进,实现了这个模型,是个关系型 NoSQL 系统,而不是 NewSQL 系统,但是没有什么特别阻碍 FaunaDB 最终实现 SQL 接口的东西。
CAP
尽管没有 CP 系统可以在完全任意分区事件中保持活动性,但是在实践中,我们能发现 Spanner 和 FaunaDB 云系统和 AP 系统的可用性类似。超过 99.9%的可用性故障可能都来自于实现的问题,也可能来自于硬件和网络的故障,CP 系统的一致性模型可以让它们比 AP 系统更容易地在故障下验证。
比如说,五个数据中心的 FaunaDB 集群可以承受丢失两个完整的数据中心,而不影响活动性。同时,类似 FaunaDB 的 CP 系统通常可以在较低的一致性级别下维持读的可用性(比如快照隔离的情况),甚至在分区事件的时候也能实现,这和 AP 系统一直提供的一致性级别相类似,甚至更好。
理论上来说,将可用性从 99.999%提升到 99.9999%是不值得的(一年几分钟的差别),特别是在暂停期间接受写的代价是永久的数据不一致。
结论
分布式事务是计算机科学领域最难解决的问题之一。我们很幸运地生活在这些系统现成的世界里。任何的有界一致性都比最终一致性要好,甚至抛开其他遗留 NoSQL 的问题,比如持久性,以及遗留的 RDBMS 问题,比如运维开销等等。
然而,除了 Calvin 之外,其他产品并不能实现没有物理时钟的分布式外部一致性。FaunaDB 是唯一实现了类似 Calvin 事务协议的产品。我推荐你注意你的数据库系统所提供的一致性保障,并尝试一下使用 FaunaDB。
本文作者
Evan Weaver 是 Twitter 的前任基础设施总监,他的团队负责扩展 Twitter 的后端。他拥有计算机科学硕士学位,之前在 CNET 和 SAP 工作过。他是 FaunaDB 的联合创始人。
查看英文原文:Back to the Future with Relational NoSQL
评论 1 条评论