CRDT:两个人同时改同一段文字,为什么不会互相覆盖

CRDT 允许多个副本先本地修改,再通过可合并的数据结构最终收敛。本文用计数器和协同编辑讲清 CvRDT、CmRDT、并发插入、删除标记、元数据成本与业务语义冲突。

CRDT:两个人同时改同一段文字,为什么不会互相覆盖

两个人同时编辑同一段文字时,最朴素的同步方式是“谁最后提交,谁覆盖谁”。网络一抖、有人离线,冲突就会变成一场人工抢救。CRDT 的思路更像是:允许每个副本先本地修改,网络恢复后再合并,并且让合并规则保证所有副本最终收敛到同一个状态。

分布式网络中的节点与连接

CRDT 是 Conflict-free Replicated Data Type 的缩写,中文通常译为“无冲突复制数据类型”。它不是某一个数据库,也不是一条网络协议,而是一类带有数学性质的数据结构。经典研究把它分成两条路线:基于状态合并的 CvRDT,以及基于操作传播的 CmRDT。CRDT 研究论文

先看一个不会让人头疼的例子:计数器

假设 Alice 和 Bob 都看到计数器是 10。Alice 离线加 1,得到 11;Bob 同时加 1,也得到 11。若直接传“新值是 11”,服务器无法知道两次加法都发生过,最后可能只剩 11。

CRDT 计数器不把更新表达成“把值改成 11”,而是表达成“我的副本增加了 1”。每个副本维护自己的增量,合并时把各副本的增量相加。Alice 的增量和 Bob 的增量都被保留下来,最终结果是 12。

这个例子体现了一个重要原则:同步的不是容易覆盖的最终快照,而是能安全合并的事实。

CRDT 的“无冲突”到底是什么意思

它并不是说任何业务冲突都能神奇消失,而是说:在数据结构规定的操作范围内,只要不同副本最终收到同一批更新,它们会按照确定的规则得到同一个状态。

对于状态型 CRDT,合并函数通常需要满足:

  • 交换律:先合并 Alice 再合并 Bob,与反过来相同。
  • 结合律:分组方式不影响最终结果。
  • 幂等性:同一个状态重复收到,结果不会越来越大。

这让网络层可以重试、乱序甚至重复发送更新,而不会因为传输细节把副本推向不同结果。操作型 CRDT 则把重点放在操作可交换,以及传输层满足必要的投递和因果关系条件。

文本编辑为什么比计数器难得多

计数器的“加 1”没有位置问题,文本却有。Alice 和 Bob 都在字符串 ABCB 前面插入字符,如果更新只记录“在位置 1 插入”,网络乱序时两个操作就没有稳定身份。

文本 CRDT 通常会给每个字符或元素分配唯一标识,并记录它与其他元素的关系。插入操作不再是“插到第几个格子”,而是“插到某个已知元素附近,并带有唯一 ID”。当两个元素竞争同一个位置时,系统使用确定的排序规则,让所有副本看到同样的顺序。

删除也不是简单地把字符从数组中抹掉。某个副本可能还没收到插入操作,另一个副本已经删除它;为了让迟到的更新仍然能被识别,很多实现会保留删除标记或墓碑。这样做换来了正确合并,却带来元数据膨胀、压缩和垃圾回收问题。

因此,“CRDT 不需要冲突解决”这句话不够准确。它把一部分冲突处理从人工步骤提前搬到了数据结构设计中。设计者仍然要回答:并发插入如何排序?删除和编辑同时发生怎么办?撤销操作如何定义?格式化和权限是否也要合并?

CvRDT 与 CmRDT:两种合并风格

基于状态的 CvRDT

每个副本可以直接发送自己的状态,接收方通过合并函数计算新状态。优点是实现直观、重复发送通常安全;缺点是状态可能很大,频繁传输浪费带宽。

基于操作的 CmRDT

副本发送“发生了什么操作”,接收方重放这些操作。优点是更新小,适合增量同步;缺点是操作需要唯一 ID、去重机制和一定的因果处理,不能把任意乱序消息直接当作安全输入。

现实项目常常把两者结合:首次加入房间时发送压缩后的状态,之后发送操作更新;离线时间较长时,再通过状态向量或快照补齐缺口。

CRDT 不会替你解决的三类问题

第一,语义冲突仍然存在。两个人同时把商品库存从 1 改成 0 和 10,数学上可以收敛,但业务上哪个结果合法,需要库存规则决定。

第二,元数据有成本。为了处理并发、因果和删除,CRDT 往往比最终文本多保存不少信息。移动端、长文档和大量历史记录尤其需要压缩策略。

第三,权限不能靠“最终一致”解决。一个没有权限的客户端如果可以产生任意更新,CRDT 只会忠实地把恶意操作合并到所有副本。权限校验、签名、撤销和服务端策略仍然要单独设计。

什么时候值得选 CRDT

CRDT 特别适合离线优先、多人实时协作、节点经常断线、希望本地操作立即响应的场景,例如共享笔记、白板、表单草稿和部分游戏状态。如果业务必须每一步都经过中心服务器批准,或者数据量极小、单主写入已经足够,CRDT 的复杂度可能不值得。

落地时可以按这个顺序思考:先确定需要合并的抽象数据类型,再定义并发操作的语义,然后验证交换、结合和幂等性质,最后才选择具体库。不要因为“协同编辑”四个字就直接把一个文本 CRDT 塞进所有数据模型。

一句话带走

CRDT 的魔法不是让冲突不存在,而是把数据表示成“可以安全合并的事实”。它让每个副本先行动、网络随后同步成为可能,但代价是更多元数据、更复杂的撤销与权限设计,以及对业务语义的更严格建模。

延伸阅读

KEEP READING