CRDT (conflict-free replicated data type) 无冲突复制数据类型,是一种可以在网络中的多台计算机上复制的数据结构,副本可以独立和并行地更新,不需要在副本之间进行协调,并保证不会有冲突发生。
概念
分布式计算系统:由多个计算机节点组成的系统,这些节点可以通过网络相互连接,协作完成任务。
最终一致性(EC,Eventual Consistency):冲突可能会发生,但节点会相互传达其更改以解决这些冲突,因此最终它们会就最终值达成一致。 即由三个属性构成:
- Eventual Delivery: 发布到一个正确的副本上的更新列表最终将被传达至所有正确的副本。
- 收敛 Convergence: 收到了一样的更新列表的正确副本们最终状态将一致。
- 终止 Termination: 所有执行的方法都会终止(即保证算法能在有限时间内完成计算,而不是要进行 2^128 次计算来遍历整个状态空间的这种算法)。
强一致性:这里要求在更新操作时所有节点都同意新值,然后才能将新值对客户端可见。这样,更新对所有客户端来说都是“同时”可见的,因此它们将始终读取相同的值。
CAP定理(布鲁尔定理),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
- 
一致性(Consistency):每个读请求都能返回最新的写入结果,以确保所有节点的数据一致。 
- 
可用性(Availability):每次请求都能获取到非错的响应,无论结果是否是最新数据。 
- 
分区容错性(Partition tolerance):系统能够继续运行,即使网络出现分区或节点间的通信中断。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。 
CRDT的作用
比如多用户同时对同一页面上的节点操作时,在这种情况下要求每个用户看到的内容都是一样的,即使在用户出现冲突编辑后(例如两个用户同时修改同一个节点,两个请求同时到达服务器)也不会产生两个版本(即一致性,CRDT满足的是最终一致性)。
CRDT 让用户即使离线也可使用,并在恢复网络后能继续和所有人同步至一致的状态。也可以和其他用户通过 P2P 的方式一起协同编辑(即分区容错性)。这让 CRDT 可以很好地支持去中心化的应用:即使没有中心化服务器各端之间也能完成同步。
CRDT是如何处理CAP定理
CRDT的提出是由于最终一致性的冲突解决设计很困难,而CRDT是一种简单的、理论证明的方式来达到最终一致性。
根据定理,分布式系统只能满足三项中的两项而不可能满足全部三项。理解CAP理论的最简单方式是想象两个节点分处分区两侧。允许至少一个节点更新状态会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。
CRDT被设计为一种特殊的数据类型,无需对CAP定理中的一致性和可用性之间进行严格取舍。通过以下的特性和设计,CRDT能够确保分区容错性的同时实现强一致性和最终一致性的折中(即最终一致性):
- 最终一致性
- CRDT 的设计目标是确保在没有进一步更新的情况下,所有副本最终都会收敛到同一个状态。通过复制和同步所有更新操作,使系统各节点上的数据最终一致。
- 无冲突合并
- 由于 CRDT 的特性,分布式节点可以独立地处理更新和合并数据,而不会产生冲突。CRDT 确保所有并发操作的合并是确定性的,即无论操作应用的顺序如何,最终状态都会一致。
- 高可用性
- 在网络分区期间,CRDT 允许节点继续处理读写请求,提高系统可用性。由于所有操作都是局部应用并最终传播到其他节点,即使在网络分区的存在下,系统仍然对外提供服务。
CRDT 满足 A + P + Eventual Consistency;是 CAP 下很好的权衡
常见的CRDT案例
通过案例来理解强最终一致性!!!
Grow-only Countter
 
Grow-only Counter是一个只增不减的计数器。每个节点只能递增自己的计数器(即不发生冲突),并且每个节点同时保存着其他节点的计数值,每次操作都会合并状态(各个计数器的最大值),在消息同步后便实现了一致性。
Grow-only Set
 
Grow-only Set当中的元素是只能增加不能减少的(即不发生冲突),只需要将每个节点的状态做并集,再将消息同步后便满足一致性。
根据上面两个案例发现CRDT的性质:
- 每个节点都可以独立并发的更新,不需要在节点间进行协调(加锁)。
- 更新的过程中不会发生冲突。
- 可以保证最终一致性。
两种CRDT类型
基于状态的CRDT(State-based CRDT,CvRDT)
通过状态传播和合并来实现一致性。节点定期将状态发送给其他节点,其他节点通过合并状态来更新本地副本。合并操作必须是幂等的、结合的。
具体实现
设计一个基于状态的CRDT中需要先定义一个State-based Object。当它满足一定性质时就能成为State-based CRDT。
那么State-based Object的定义包括:
- State节点的内部状态的类型。
- state_zero内部状态的初始值。
- ≤定义了 state 之间的顺序。
- update(s, u)定义 state 的更新方式。
- merge(s, s')函数可以用于合并两个状态得到新状态。
- 副本之间通过传递自己的 state,并进行 merge 操作来达到一致性。
接下来在假设有Eventual delivery和Termination的属性,State-based Object还需要满足强一致性,那么该对象应满足单调半格对象(Monotonic Semilattice Object)。
则需要满足以下性质:
- 该对象集合是一个以 ≤ 为顺序的半格。
- 合并「本地状态 s」和「远端状态 s’」的方式为计算二者的上确界(LUB),即 merge(s, s’) = s⊔s′。
- 在所有的更新中,s 都是单调递增的,即 s ≤ update(s, u)。
综上,设计State-based CRDT需要:
- 保证系统满足 Eventual delivery 和 Termination。
- state 在 ≤ 上满足偏序集的要求。
- 对任意的 s, u 满足 s ≤ update(s, u)。
- merge(s, s')得到的是两个状态的上确界。
tips:
- 上确界,是指在一个给定集合中的所有元素的上界中最小的那个元素,它是大于或等于该集合中所有元素的最小值,即所有可能上界中最小的那个。上确界确保集合在某个方向上的范围限制,同时自身仍在这个限制范围内。
- 偏序集是一个集合,该集合中的元素间具备一个部分顺序关系,这个关系满足自反性、反对称性和传递性。
- 自反性:∀a∈S,有a≤a;
- 反对称性:∀a,b∈S,a≤b且b≤a,则a=b;
- 传递性:∀a,b,c∈S,a≤b且b≤c,则a≤c;
 
- 半格,简单来说就是一个偏序集,每个集合都有上确界或者下确界。
基于操作的 CRDT(Op-based CRDT, CmRDT)
通过传播操作来实现一致性。每个操作必须是可交换的,通过将操作广播到所有节点,所有节点独立地应用这些操作,确保最终状态一致。
具体实现
同样的设计一个基于操作的 CRDT 中需要先定义一个 Op-based Object。当它满足一定的性质时就能成为 Op-based CRDT。Op-based Object 的定义包括:
- state是每个副本的内部状态。
- state_zero内部状态的初始值。
- op是每个原子操作的类型,副本直接通过传递 op 来达到同步。
- apply_op(state, op)是在一个 state 上应用 op 的函数,返回新的状态;op 的交换律指的就是- apply_op(apply_op(state, opA), opB) == apply_op(apply_op(state, opB), opA)。
- check_state(state, op)确认一个状态是否满足应用 op 的前置条件。- Op-based CRDT 的每一个操作 op 都有对应的前置状态检查函数 check_state,在应用 op 之前需要检查 check_state 函数是否满足,如果不满足就将阻塞延迟。
- check_state函数的目的是为了保证 op 所依赖的因果顺序成立,例如删除 x 节点的操作依赖于 x 节点被创建的操作,否则就无法应用该操作。这也意味着 Op-based CRDT 的使用者有责任证明每个操作的前置状态都是能够得到满足的。
 
- 副本之间通过传递彼此缺失的 op,并进行 apply_op 来达到最终一致性。
接下来,构建一个Op-based CRDT 的充分条件为:
假设所有操作都按照因果顺序 deliver,且所有更新函数都会终止(满足 Termination)。那么所有满足以下条件的 op-based object 就具有强最终一致性(SEC)。
- 所有的并行的操作都满足交换律。
- 每个操作的前置条件都能通过按因果顺序应用得到满足。
假设所有操作都满足 Eventual Deliver (按照随机顺序 deliver),且所有更新函数都会终止(满足 Termination那么所有满足以下条件的 op-based object 就具有强最终一致性(SEC)
- 所有的操作都满足交换律。
直观的理解该定理: 如果有可能冲突的 Op 都是可以交换的,那么它们在一个副本上的应用顺序就对 Object 的最终状态没有影响,那么任意两个应用了同样的 op 集合的 Op-based CRDT 就都具有一致的状态,从而满足 SEC。
综上,设计Op-based CRDT需要:
- 保证系统满足 Eventual delivery 和 Termination。
- 保证可能出现并行的 Operation 都满足交换律(不管先应用哪个 Op,最终状态都一致)。
- 在应用 Op 时需要保证该 Op 所依赖的前置状态得到满足。
CmRDT 和 CvRDT的关系
在形式上,Op-based CRDT 和 State-based 是可以互相转换的。思路为:
- 
通过 Op-based CRDT 构建 State-based CRDT 的方式为: - 将新的 State-based Object 的 state 定义为一个二元组(s, M),s 和 Op-based CRDT 的内部状态一致,M 是 Op-based CRDT 的内部 Op 的集合。
- 将新的 State-based Object 的 merge 操作定义为 merge((s, M), (s', M')) = apply_ops(s, M' - M)。
 
- 
通过 State-based CRDT 构建 Op-based CRDT 的方式为: - 将新的 Op-based object 的 Op 定义为 State-based CRDT 的 State。
- 将 apply_op的操作定义为apply_op(state, op) = merge(state, op),而 merge 是服从对称性的操作,从而我们能够满足 SEC 得到一个 Op-based CRDT。