CRDT 是一种无冲突复制数据类型,它支持强最终一致性(SEC, Strong Eventual Consistency)。这种数据类型常用于需要多用户协作编辑的文档,或者那些要求本地立即响应、可以放弃强一致性(线性一致)的场景,比如强调(AP, Availability and Partition Tolerance)的应用。
CRDT 适用于异步传播(gossip)场景。只要更新最终能投递到所有副本(网络分区最终恢复、没有永久丢消息),就能保证数据的最终一致性。它具有以下特点:
- merge 函数满足交换律,所以副本状态可以任意顺序合并
- merge 函数满足结合律,所以合并可以任意分组、分批进行
- merge 函数满足幂等性,所以同一份状态重复合并没有副作用
这三条(交换律、结合律、幂等性)缺一不可,而且主语是 state-based CvRDT 的 merge 函数,不是「请求/操作」本身。op-based CmRDT 的要求不一样:并发操作之间可交换,且投递层要保证因果序的可靠投递(每个操作恰好投递一次)——不能靠幂等来替代去重。
对 CvRDT 而言,请求延迟、乱序、重复都没关系。
其数据结构形成了一个并半格(也叫上半格,join semi-lattice),其偏序由 merge 诱导:a ≤ b 当且仅当 merge(a, b) = b。CRDT 通过维护这个偏序关系构成的有向图,在这张有向图中能保证最终所有的节点会收敛到同一个最小上界(least upper bound / join,记作 ⊔),也就是所有已知状态的 merge 结果。
收敛方向是往上界走,不是往「最小公共祖先」走——最小公共祖先对应的是下界(meet)方向,方向相反。而且只说 upper bound 不够,必须是 least upper bound:任意上界都行的话收敛结果就不唯一了。
CRDT 节点,需要维护:
- 本地对请求的排序
- merge 函数,可以将其他节点的请求合并到自己身上
- update 函数,可以更新自己的数据(结果是当前状态与更新的最小上界)
IPFS Cluster 所采用的 CRDT 是专为 Merkle DAG 设计的。被复制的状态是 pinset(go-ds-crdt 里的一个 CRDT map),Merkle DAG 只负责承载 delta 和因果序:可以将 Merkle DAG node 看做是 Merkle-Clock,作为一个分布式系统的 event,一旦创建便无法改变。各节点按 Merkle-Clock 给出的因果序合并 delta,最终对 pinset 收敛。