简介
这是一篇关于在分布式环境中维护数据一致性的复杂性的文章。它介绍了无冲突数据类型(CRDTs)作为解决并发数据变化的一种方式。在此过程中,我们回答了以下问题。
- 什么是强一致性?什么是最终一致性?
- 什么是复制冲突?
- Google Docs是如何解决冲突的?
- 什么是操作转换(OT)?
- 什么是无冲突复制数据类型(CRDTs)?
- 为什么要使用CRDTs?
- CRDTs能简化软件设计吗?
- CRDTs的缺点是什么?
- CRDTs的使用案例有哪些?
- 我在哪里可以找到更多关于CRDTs的信息?
常见的数据一致性挑战
考虑这样一种情况:有几个分布式实体,它们各自持有相同数据的副本。如果这些副本继续相互匹配,即使其中一个或多个副本被更新,也能保持数据一致性。
如果一个实体更新了它的数据副本,那么对于该数据的最新 "真实来源 "就没有争议,但每个分布式实体的状态可能存在时间上的差异。网络延迟会影响每个对等体接收更新的速度;网络或系统故障意味着一些对等体可能永远不会收到更新。
除了简单地导致一些更新未能到达一些对等体之外,这些故障还导致了数据一致性的挑战。例如,一些更新的分发可能会失败,但其他更新会成功;这导致一致性和排序问题。系统设计需要适应这种情况以确保一致性。
如果多个实体同时对数据进行独立的更新,还会有进一步的挑战。现在,任何数据不一定有一个单一的、规范的 "正确 "版本。一个对等体在一个状态下创建的更新可能会与其他对等体冲突,或者对其他更新的可见性没有意义。
什么是强一致性?什么是最终一致性?
强一致性是将任何更新传播到所有的数据副本。在最简单的情况下,对数据的一系列更新来自单一来源,所有其他持有数据副本的实体都保证以相同的顺序接收这些更新。
当数据的多个副本可以同时被更新时,需要有一种方法来商定数据的正确版本。有了强一致性,一旦一个实体做了任何更新,所有其他副本都会被锁定,直到它们被更新到相同的新版本。真相源以相同的顺序向所有实体推送更新,以保持它们的 "步调一致"。
在越来越多的用例中,强一致性所带来的设计限制是不可接受的。考虑一下实时协作(如在允许用户同时进行编辑的在线生产力工具中看到的)。每个实体都有可能进行和接收更新,所以没有办法确保每个副本都收到相同的更新序列。最终一致性是指数据的状态最终被调和的属性,不管到达每个副本的更新事件的顺序如何。
什么是复制冲突?
当不清楚如何调和变化以达到数据一致性时,就会发生复制冲突。例如,哪个更新是优先的?在如何解决冲突方面需要有一个共同的协议。
考虑一个简单的案例,Alice和Bob正在使用一个协作着色的应用程序。两人都选择了图像的同一区域来填充颜色:Alice选择填充黄色;Bob填充蓝色。这个区域的颜色应该是黄色还是蓝色(或者绿色:两种更新的组合)?
最常见的解决方案是有一个单一的仲裁策略,使用一套规则来解决这种冲突,比如 "最后写入获胜",它使用每个更新的时间戳,按照它们发生的顺序来应用变化。在上述情况下,如果Bob在Alice之后做了编辑,那么他的编辑就会压倒她的编辑,并且该部分的颜色对Bob来说保持为蓝色,对Alice来说则变为蓝色,反之亦然,如果Alice做了最后的编辑。
如果复制冲突不能被解决会怎样?
当时间戳相等而不能用来解决冲突时,解决策略可以是一个规则,如 "哪个账户的ID号最低?"或 "哪个用户名在字母表中排在第一位?",结果是一个更新优先。只要所有对等体的应用一致,该策略就可以有点随意性。
偶尔,需要一种强硬的方法,例如,它放弃了调和冲突的变化的努力,它既不结合更新,也不任意选择一个。在Bob和Alice的情况下,一个蛮力方法可能是覆盖这两个变化,丢弃它们,并将该部分恢复到原来的颜色,这使得用户体验很差,但能确保数据一致性。爱丽丝和鲍勃必须进一步更新,并且自己有效地调和冲突。
Google Docs是如何解决冲突的?
考虑一下这样的情况:Alice和Bob同时编辑同一个Google文档。爱丽丝键入了 "Hello World!"。然后她删除了 "世界"。鲍勃也输入了 "Hello World!"。
爱丽丝做了一个添加和一个删除。鲍勃也做了一个添加,和爱丽丝的添加是一样的。当我们考虑如何调和他们的变化时,有两种可能的解释。
首先,让我们把爱丽丝的加法,鲍勃的加法,然后爱丽丝的减法结合起来。
爱丽丝的增加+鲍勃的增加+爱丽丝的删除。
Hello World! + Hello World! - World.
理智的冲突解决检测到这两个加法是相同的。它抛弃了其中一个以避免重复。然后,"世界 "这个词被删除。
Hello World! + Hello World! - World = Hello!
但是如果你改变编辑的顺序呢?考虑爱丽丝的添加,爱丽丝的删除,然后最后是鲍勃的添加。
Hello World! - World + Hello World!
现在,冲突解决需要解决鲍勃的添加是否应该与爱丽丝的两个贡献合并。数据应该是 "Hello!"(放弃Bob的所有补充)还是 "Hello World!"(在爱丽丝的一对编辑之后,加入与数据不同的那部分鲍勃的文字)?
处理Alice和Bob的编辑的顺序将影响最终的文本。
Google Docs使用操作转换来解决这个数据一致性的挑战。基于客户-服务器模型,每个用户都是一个持有文档副本的客户,而Google Docs则作为一个中央服务器。客户端的更改会传播回服务器,而服务器会将其传递给其他客户端。
什么是操作转换(OT)?
当一个数据副本发生变化时,客户端将更新表示为一个或多个 "操作"(每个操作封装了所做的变化,而不是该变化的结果),并与中央服务器共享。多个客户端同时进行更改,因此它们将各自处于不同的状态。他们将他们的操作发送到服务器,服务器通过决定未完成的操作集的应用顺序来适应这些不同的起始状态(使用规则,如最后写入获胜)。然后,在将这些操作传播给每个客户以应用于他们的数据副本之前,它对这些操作进行转换。
什么是CRDTs?
无冲突复制数据类型(或CRDTs,有时也被称为 "收敛复制数据类型或换算复制数据类型")可以解决在分散环境中存在的副本上并发操作所产生的复制冲突。
通过CRDT,在数学上总是可以在没有冲突或中央仲裁者的情况下合并或解决并发的更新。一个关键的方法是把所有的编辑操作减少到只有换元操作,这样变化的顺序就不再重要。在实践中,这可以解决 "不按顺序 "到达的变化,因为对于发生在不同副本上的独立变化,要么没有固定的顺序,要么当有一个有效的顺序时,不能保证变化可以传播到每个接受者那里,以遵守它。
例如,如果数据类型是一个只能递增的计数器值,那么两个操作发生的顺序是不重要的。如果值从0 开始,一个副本(Alice)发送了12 的增量,而另一个副本(Bob)发送了6 的增量,那么无论应用的变化顺序如何,其他副本上的综合更新都相当于相同的值(18 )。尽管所有的副本可能暂时出现分歧,但数据最终会恢复到一致性(强最终一致性)。
在这个例子中,两个操作发生的顺序是不重要的。
在基于状态的CRDTs中,客户端并不传递它用于改变数据的操作。相反,它将数据的新状态(作为一个CRDT)发送给所有其他的客户端。客户端可以与他们自己的变化合并,因为CRDTs符合一个一致的政策来解决冲突并最终收敛。
在基于操作的CRDTs中,更新操作在本地传播和应用。有一些传递要求,以确保每个更新操作只被传送到副本一次,因为在CRDTs的一些表述中,操作并不总是等价的。一旦它们到达副本,就像基于状态的CRDTs,它们可以以任何顺序应用。
为什么使用CRDTs?
有目的的消除对中央协调者的需求,创造了一个分布式环境,允许大量用户之间的并发数据交换。在这种情况下,由于规模的原因,中央服务器可能是不现实的。
此外,缺乏对任何中央服务器的假设意味着CRDTs可以被使用,无论任何这样的服务器是否碰巧是拓扑结构的一部分。
CRDTs是否简化了软件设计?
CRDTs对操作的顺序有一个宽松的方法。从理论上讲,这降低了分发机制的设计复杂性,因为不需要严格的序列化协议。通过容忍失序的更新,分布机制必须满足更简单的完整性保证。然而,对于使用CRDT的应用程序来说,会产生一些复杂性,因为该数据结构可能没有应用程序所需的表达能力,我们将在下文中描述。
CRDT的缺点是什么?
为了做到不分先后,CRDTs被限制在对共享数据进行更简单、更少的允许操作,这意味着功能集和用户体验的一些妥协几乎是不可避免的。
Martin Kleppmann的一篇论文中的经典例子如下。爱丽丝和查理同时在同一个文档中打字。数据已经显示为 "你好!"。爱丽丝决定在文本中插入她的名字,以使数据显示为 "你好,爱丽丝!"。同时,查理也有同样的想法,他插入了自己的名字,所以文本读作 "你好,查理!"。
他们添加的每一个单独的字符都被视为一个单独的插入文件,他们只是被添加,在同一个插入点,按时间戳排序。
按插入顺序添加输入的字符意味着每个人都能看到相同的数据副本,但由此产生的杂乱无章的编辑(H e l l o A l C i h a r c l i e e !)并不是两个人的本意。
为了避免这种情况,以及其他类似的问题,当用户调用 "撤销 "或标记他们的文本时,它要求CRDT算法保留历史状态,以便在出现变化时进行比较。一个直观的冲突解决策略在技术上是复杂的,并且由于文档状态所需的存储量而引发了资源效率问题。
我在哪里可以找到更多关于CRDTs的信息?
Marc Shapiro, Nuno Preguiça, Carlos Baquero, and Marek Zawirski在2011年概述了CRDTs,他们的论文是理解这些概念的基础。
马丁-克莱普曼(Martin Kleppmann)继续推动着这一主题的发展,他的讲座和会谈对于休闲爱好者来说是可以理解的。
Alan Gibson在Github上维护着一个全面的资源列表,Nikita Voloboev也一样。如果你想迷失在基础理论中,《聚合和共轭复制数据类型的综合研究》应该在你的阅读清单上。对于一个普通人来说,这些关于CRDT概念的视频介绍在某种程度上更容易理解。
CRDTs的用例有哪些?
CRDT的用例包括:聊天应用;在线协作的生产力工具;存在竞争因素的工具,如票务系统、在线拍卖和其他实时股票市场系统;存在意识;全球数据中心。
越来越多的CRDT实施清单包括一系列企业级解决方案,如Redis Enterprise、Microsoft Azure CosmosDB、Concordant、Soundcloud、Figma、Facebook、PayPal、英雄联盟、Bet365、Akka、OrbitDB/IPFS、Apple Notes和Ably。
Ably和CRDTs
Ably是高性能基础设施,为全球数百万用户提供同步体验。我们已经使用CRDTs来简化传播信息的工程问题,并且我们也参与了用例研究。我们赞助CRDTs领域的工作(并对Yjs感到特别兴奋,它有一个强大的算法来支持文本编辑,以及Automerge)。
我们希望能够实现使用Ably的全部协作应用。我们正在积极研究如何利用CRDTs作为现实生活中的实时应用的构建模块之一。
在Ably,我们每天都在解决实时领域的艰巨工程问题,并为之感到高兴。想了解更多吗?请联系我们的技术专家。
鸣谢
我特别要感谢Paddy Byers和Martin Fietkiewicz在写这篇文章时提供的帮助。