1.3. 最终一致性

在上一个文档中 为什么是CouchDB? ,我们看到CouchDB的灵活性允许我们随着应用程序的增长和变化而发展我们的数据。在本主题中,我们将探讨如何使用CouchDB的“粒度”来提高应用程序的简单性,并帮助我们自然地构建可伸缩的分布式系统。

1.3.1. 处理谷物

A 分布式系统 是一个在广域网络上稳定运行的系统。网络计算的一个特点是网络链接可能会消失,而管理这种类型的网络分段有很多策略。CouchDB与其他方法的不同之处在于接受最终一致性,而不是将绝对一致性放在原始可用性之前,比如 RDBMSPaxos . 这些系统的共同点是,当许多人同时访问数据时,它们的行为会有所不同。当涉及到 一致性可利用性分区 他们优先考虑宽容。

设计分布式系统是很棘手的。随着时间的推移,你将面临的许多警告和“陷阱”并不是立竿见影的。我们没有所有的解决方案,而且CouchDB并不是万能的,但是当您使用CouchDB的grain而不是反对它时,阻力最小的方法会引导您获得自然可伸缩的应用程序。

当然,构建分布式系统只是一个开始。一个只有一半时间可用的数据库的网站几乎一文不值。不幸的是,传统的关系数据库一致性方法使得应用程序程序员很容易依赖全局状态、全局时钟和其他高可用性,甚至没有意识到他们在这样做。在研究CouchDB如何促进可伸缩性之前,我们先来看看分布式系统所面临的约束。在我们了解了当应用程序的各个部分不能依赖于彼此保持联系时出现的问题之后,我们将看到CouchDB为围绕高可用性对应用程序建模提供了一种直观而有用的方法。

1.3.2. 帽定理

CAP定理描述了在网络上分布应用程序逻辑的几种不同策略。CouchDB的解决方案使用复制来跨参与节点传播应用程序更改。这是一种与一致性算法和关系数据库根本不同的方法,它们在一致性、可用性和分区容忍度的不同交叉点上运行。

CAP定理,如 图1。帽定理 ,确定了三个不同的关注点:

  • 一致性 :所有数据库客户端都看到相同的数据,即使是并发更新。
  • 可利用性 :所有数据库客户端都可以访问某些版本的数据。
  • 分割公差 :可以在多个服务器上拆分数据库。

选两个。

The CAP theorem

图1。帽定理

当系统变得足够大,以至于单个数据库节点无法处理其上的负载时,一个合理的解决方案是添加更多的服务器。当我们添加节点时,我们必须开始考虑如何在它们之间划分数据。我们是否有几个数据库共享完全相同的数据?我们是否将不同的数据集放在不同的数据库服务器上?我们是否只允许某些数据库服务器写入数据而让其他服务器处理读取?

不管我们采用哪种方法,我们都会遇到一个问题,那就是保持所有这些数据库服务器同步。如果您向一个节点写入一些信息,您将如何确保对另一个数据库服务器的读请求能够反映这些最新信息?这些事件可能相隔几毫秒。即使有一个适度的数据库服务器集合,这个问题也可能变得极其复杂。

当所有客户机都能看到数据库的一致视图非常重要时,一个节点的用户必须等待其他节点达成一致,然后才能读写数据库。在这个例子中,我们看到可用性在一致性之后。然而,在某些情况下,可用性胜过了一致性:

系统中的每个节点都应该能够完全基于本地状态做出决策。如果你需要在高负载下做一些失败的事情,并且你需要达成一致,你就失败了。如果您关心可伸缩性,任何强迫您运行协议的算法最终都将成为您的瓶颈。把这当作是理所当然的。

—Werner Vogels,亚马逊首席技术官兼副总裁

如果可用性是优先考虑的,我们可以让客户机将数据写入数据库的一个节点,而不必等待其他节点达成一致。如果数据库知道如何协调节点之间的这些操作,我们就可以实现某种“最终一致性”,以换取高可用性。对于许多应用程序来说,这是一个令人惊讶的适用性权衡。

与传统的关系数据库不同,在传统的关系数据库中,执行的每个操作都必须进行数据库范围的一致性检查,CouchDB使构建应用程序变得非常简单,这些应用程序牺牲了即时一致性,以换取简单分发带来的巨大性能改进。

1.3.3. 局部一致性

在我们试图理解CouchDB如何在集群中运行之前,了解单个CouchDB节点的内部工作原理是很重要的。couchdbapi旨在为数据库核心提供一个方便但很薄的包装器。通过仔细观察数据库核心的结构,我们将更好地理解围绕它的API。

1.3.3.1. 数据的关键

CouchDB的核心是一个强大的 B-tree 存储引擎。B树是一种排序的数据结构,允许在对数时间内进行搜索、插入和删除。作为 图2。视图请求的剖析 说明了CouchDB对所有内部数据、文档和视图使用这个B-tree存储引擎。如果我们理解一个,我们就会理解所有的。

Anatomy of a view request

图2。视图请求的剖析

CouchDB使用MapReduce来计算视图的结果。MapReduce使用两个函数“map”和“reduce”,这两个函数单独应用于每个文档。能够隔离这些操作意味着视图计算有助于并行和增量计算。更重要的是,由于这些函数生成键/值对,CouchDB能够将它们插入到B树存储引擎中,按键排序。按键查找或按键范围查找是使用B树进行的非常高效的操作,如中所述 big O 符号为 O(log N)O(log N + K) ,分别。

在CouchDB中,我们访问文档并按键或键范围查看结果。这是对CouchDB的B-tree存储引擎执行的底层操作的直接映射。除了文档插入和更新之外,这种直接映射也是我们将CouchDB的API描述为围绕数据库核心的一个精简包装器的原因。

能够单独通过键访问结果是一个非常重要的限制,因为它允许我们获得巨大的性能增益。除了速度的巨大提高外,我们还可以将数据划分到多个节点上,而不会影响我们孤立地查询每个节点的能力。 BigTableHadoopSimpleDBmemcached 正是由于这些原因,按键限制对象查找。

1.3.3.2. 禁止上锁

关系数据库中的表是一个单一的数据结构。如果您想要修改一个表,比如更新一行,数据库系统必须确保没有其他人试图更新该行,并且在该行被更新时没有人可以从该行读取数据。通常的处理方法是使用锁。如果多个客户机想要访问一个表,第一个客户机将获得锁,让其他所有客户机等待。当第一个客户机的请求被处理时,下一个客户机将被授予访问权限,而其他所有客户机都在等待,依此类推。这种请求的串行执行,即使它们是并行到达的,也会浪费大量服务器的处理能力。在高负载下,关系数据库可以花费更多的时间来确定谁可以做什么,以及按照什么顺序来做,而不是做任何实际的工作。

注解

现代关系数据库通过在后台实现MVCC来避免锁,但对最终用户隐藏它,要求它们协调单行或字段的并发更改。

CouchDB使用 Multi-Version Concurrency Control (MVCC)管理对数据库的并发访问。 图3。MVCC表示没有锁定 说明了MVCC与传统锁定机制之间的区别。MVCC意味着CouchDB可以全速运行,任何时候,甚至在高负载下。请求是并行运行的,充分利用了服务器提供的每一点处理能力。

MVCC means no locking

图3。MVCC表示没有锁定

CouchDB中的文档是经过版本控制的,就像在常规版本控制系统中一样,比如 Subversion . 如果要更改文档中的值,请创建该文档的整个新版本并将其保存在旧版本上。完成此操作后,您将得到同一文档的两个版本,一个旧版本和一个新版本。

这与锁相比有何改进?考虑一组要访问文档的请求。第一个请求读取文档。在处理此过程中,第二个请求会更改文档。由于第二个请求包含文档的全新版本,CouchDB可以简单地将其附加到数据库中,而不必等待读取请求完成。

当第三个请求想要读取同一个文档时,CouchDB将把它指向刚刚编写的新版本。在整个过程中,第一个请求可能仍然是读取原始版本。

读取请求将始终在请求开始时看到数据库的最新快照。

1.3.4. 验证

作为应用程序开发人员,我们必须考虑我们应该接受什么样的输入,应该拒绝什么样的输入。在传统关系数据库中对复杂数据执行这种类型的验证的表达能力还有很多需要改进的地方。幸运的是,CouchDB提供了一种从数据库中执行每个文档验证的强大方法。

CouchDB可以使用类似于MapReduce的JavaScript函数来验证文档。每次尝试修改文档时,CouchDB都会向validation函数传递现有文档的副本、新文档的副本和附加信息的集合,例如用户身份验证详细信息。验证功能现在有机会批准或拒绝更新。

通过使用grain并让CouchDB为我们做这件事,我们节省了大量的CPU周期,这些CPU周期本来是用来从SQL序列化对象图、将它们转换为域对象并使用这些对象进行应用程序级验证的。

1.3.5. 分布式一致性

对于大多数数据库来说,在单个数据库节点内维护一致性相对容易。当您试图维护多个数据库服务器之间的一致性时,真正的问题就会浮出水面。如果客户端在服务器上执行写操作 A ,如何确保这与服务器一致 BCD ? 对于关系数据库来说,这是一个非常复杂的问题,整本书都致力于解决这个问题。您可以使用多主机、单主机、分区、分片、直写缓存和其他各种复杂的技术。

1.3.6. 增量复制

CouchDB的操作发生在单个文档的上下文中。由于CouchDB通过使用增量复制实现了多个数据库之间的最终一致性,您不再需要担心您的数据库服务器能够保持恒定的通信。增量复制是在服务器之间定期复制文档更改的过程。我们可以建立一个 什么也没分享 数据库集群,其中每个节点都是独立的和自给自足的,在整个系统中没有留下单一的争用点。

需要扩展CouchDB数据库集群吗?再加一台服务器就行了。

如中所示 图4。CouchDB节点之间的增量复制 ,通过CouchDB的增量复制,您可以在任意两个数据库之间同步您的数据,无论何时何地。复制之后,每个数据库都能够独立工作。

您可以使用此功能同步集群内或数据中心之间的数据库服务器,使用作业调度器(如cron),或者您可以使用它在旅行时将数据与笔记本电脑同步,以便脱机工作。每个数据库都可以按通常的方式使用,以后可以在两个方向同步数据库之间的更改。

Incremental replication between CouchDB nodes

图4。CouchDB节点之间的增量复制

当您更改两个不同数据库中的同一文档并希望彼此同步时会发生什么情况?CouchDB的复制系统具有自动冲突检测和解决功能。当CouchDB检测到一个文档在两个数据库中都被更改时,它将此文档标记为冲突,就像它们在常规版本控制系统中一样。

这并不像一开始听起来那么麻烦。当复制期间文档的两个版本发生冲突时,获胜的版本将保存为文档历史记录中的最新版本。CouchDB没有像您预期的那样丢弃丢失的版本,而是将其保存为文档历史记录中的以前版本,这样您可以在需要时访问它。这是自动且一致的,因此两个数据库将做出完全相同的选择。

如何以对应用程序有意义的方式处理冲突取决于您自己。您可以保留所选文档版本,恢复为旧版本,或尝试合并两个版本并保存结果。

1.3.7. 案例研究

GregBorenstein是他的朋友和同事,他构建了一个小库,用于将Songbird播放列表转换为JSON对象,并决定将其作为备份应用程序的一部分存储在CouchDB中。完成的软件使用CouchDB的MVCC和文档修订来确保Songbird播放列表在节点之间得到可靠的备份。

注解

Songbird 是一款基于Mozilla XULRunner平台的带有集成web浏览器的免费软件媒体播放器。Songbird可用于Microsoft Windows、Apple Mac OS X、Solaris和Linux。

让我们研究一下Songbird备份应用程序的工作流程,首先作为用户从一台计算机进行备份,然后使用Songbird在多台计算机之间同步播放列表。我们将看到文档修订如何将原本棘手的问题转变为 真管用 .

第一次使用这个备份应用程序时,我们将播放列表提供给应用程序并启动备份。每个播放列表都被转换成一个JSON对象并交给CouchDB数据库。如中所示 图5。备份到单个数据库 ,CouchDB会在保存到数据库时将每个播放列表的文档ID和修订版本发回。

Backing up to a single database

图5。备份到单个数据库

几天后,我们发现我们的播放列表已经更新,我们想备份我们的更改。在我们将播放列表提供给备份应用程序之后,它将从CouchDB获取最新版本以及相应的文档修订。当应用程序发回新的播放列表文档时,CouchDB要求请求中包含文档修订。

CouchDB然后确保请求中提交给它的文档修订与数据库中保存的当前修订相匹配。因为CouchDB每次修改都会更新修订,如果这两个不同步,则表明在我们从数据库请求文档到发送更新的时间之间,其他人已经对文档进行了更改。在其他人修改文档后,未事先检查这些更改而对其进行更改通常是一个坏主意。

强制客户机返回正确的文档修订版是CouchDB乐观并发的核心。

我们有一台笔记本电脑,我们想和我们的台式电脑保持同步。桌面上有了所有的播放列表,第一步就是“从备份中恢复”到我们的笔记本电脑上。这是我们第一次这样做,所以之后我们的笔记本电脑应该保存一个完全复制的桌面播放列表集合。

在我们的笔记本电脑上编辑了阿根廷探戈播放列表,添加了一些我们购买的新歌后,我们想保存我们的更改。备份应用程序将替换笔记本电脑CouchDB数据库中的播放列表文档,并生成新的文档修订版本。几天后,我们记住了我们的新歌,想把播放列表复制到我们的台式电脑上。如中所示 图6。在两个数据库之间同步 ,备份应用程序将新文档和新修订版复制到desktop CouchDB数据库。两个CouchDB数据库现在具有相同的文档修订版。

Synchronizing between two databases

图6。在两个数据库之间同步

因为CouchDB跟踪文档修订,所以它确保只有在基于当前信息的情况下,这样的更新才会起作用。如果我们不能顺利地修改播放列表的话,我们就不能顺利地进行备份了。

我们备份了笔记本电脑上的一些更改,却忘了同步。几天后,我们正在桌面电脑上编辑播放列表,做一个备份,并希望将其与我们的笔记本电脑同步。如中所示 图7。两个数据库之间的同步冲突 ,当我们的备份应用程序试图在两个数据库之间进行复制时,CouchDB发现从我们的桌面计算机发送的更改是对过期文档的修改,并有助于通知我们发生了冲突。

从应用程序的角度来看,从这个错误中恢复很容易。只需下载CouchDB版本的播放列表,并提供一个合并更改或将本地修改保存到新播放列表中的机会。

Synchronization conflicts between two databases

图7。两个数据库之间的同步冲突

1.3.8. 总结

CouchDB的设计大量借鉴了web架构和在该架构上部署大规模分布式系统的经验教训。通过理解这种体系结构的工作原理,并通过学习发现应用程序的哪些部分可以轻松分发,哪些部分不能,您将增强设计分布式和可伸缩应用程序的能力,无论是否使用CouchDB。

我们已经讨论了围绕CouchDB的一致性模型的主要问题,并暗示了在您工作时可以获得的一些好处 with 可以,不要反对。不过,理论足够了,让我们站起来跑起来看看这是怎么回事!