CRDT 与冲突解决

CRDT(Conflict-free Replicated Data Types)深入——G-Counter/LWW-Register/RGA 等数据类型、与 OT 的本质区别、Yjs/Automerge 实现原理、性能与内存权衡。

CRDT 与冲突解决

CRDT 最容易被讲成一句魔法口号

常见的说法是:

多个副本各改各的,最后自动一致。

这句话抓到了 CRDT 的表层魅力。

但如果只停在这里,基本等于没真正理解它。

因为工程上真正困难的问题是:

  • 为什么它能自动一致
  • 它为此牺牲了什么
  • 它适合哪些数据形态
  • 它什么时候不值得用

所以 CRDT 不是一个“分布式自动同步技巧”。

它是一类为并发复制场景专门设计的数据结构思路。

一个更准确的定义

更成熟的说法是:

CRDT 是一类通过数据结构设计本身保证副本在并发修改后仍能收敛的数据类型。

这个定义比“自动合并”更有用。

因为它点出了关键:

一致性保证不是靠事后人工决策。

而是写进了结构和合并规则里。

为什么前端工程师需要懂 CRDT

因为现代前端越来越多地会碰到:

  • 实时协作
  • 离线优先
  • 多端同步
  • 本地优先编辑器

这些场景一旦出现,前端就不再只是消费后端最终结果。

它开始直接参与:

  • 本地状态编辑
  • 远端状态合并
  • 冲突可视化
  • 同步时序处理

这时,CRDT 就不再是后端或分布式系统的“学术话题”。

它会直接进入产品实现边界。

CRDT 想解决的核心问题到底是什么

不是“如何同步”这么泛。

更具体地说,它在解决:

多个副本可以并发修改共享数据,而系统又不想依赖中心协调才能得出一致结果,这时如何设计一种结构,让合并仍然稳定可收敛。

这背后最重要的目标其实有两个:

  • 并发下不乱
  • 离线后能回来

为什么“自动收敛”不是免费的

这是理解 CRDT 的关键。

很多介绍会把 CRDT 写得像一种更高级的天然正确方案。

其实不是。

它的自动收敛能力,是通过额外结构成本换来的。

常见代价包括:

  • 元数据更多
  • 内存占用更高
  • 合并逻辑更复杂
  • 某些数据形态实现更难

也就是说,CRDT 很强。

但它不是“没有代价的一致性”。

CRDT 和 OT 为什么总被放在一起

因为它们都在处理并发编辑问题。

但两者的设计哲学明显不同。

OT

更偏向:

有操作进入,就根据其他并发操作去转换这个操作,再让系统保持一致。

重点在“转换操作”。

CRDT

更偏向:

让数据结构本身在合并时就具有收敛性。

重点在“结构天生可收敛”。

所以 CRDT 和 OT 的区别,不在于谁更新潮。

而在于:

一致性是通过哪里实现的。

为什么“是否需要中心服务器”不是唯一判断点

很多人会用一句话比较:

CRDT 不需要中心服务器,OT 需要。

这在很多简化讨论里有帮助。

但如果只记这一条,会误判很多工程问题。

因为真实系统里,哪怕用了 CRDT,你也仍然可能需要服务器来做:

  • 房间管理
  • 权限控制
  • 持久化
  • 广播
  • 历史回放

所以真正该理解的是:

CRDT 降低了“一致性必须依赖中心协调”的必要性。

它不等于整个系统就不需要中心服务。

常见 CRDT 类型为什么不能只背名字

Counter 类

像 G-Counter、PN-Counter 这类例子很适合用来理解:

不是所有共享数据都是文本。

很多业务状态本身就天然适合某些收敛规则。

Register 类

像 LWW-Register 很直观。

但它也很容易暴露一个问题:

“最后写入获胜”虽然简单,却不一定总符合业务语义。

这正说明 CRDT 不是只看能不能合并。

还要看合并结果是否符合产品语义。

Sequence 类

文本协作最常见的复杂度就出现在这里。

因为顺序结构远比简单计数和键值难。

这也是为什么文本 CRDT 一旦进入真实编辑器场景,复杂度会迅速上升。

Yjs 为什么值得前端工程师认真理解

Yjs 之所以重要,不只是因为它火。

而是因为它让很多前端团队第一次真正能把 CRDT 带进生产协作系统里。

它在工程上的价值包括:

  • 生态成熟
  • 文本协作常见能力完善
  • 多编辑器适配
  • 社区案例多

也就是说,Yjs 帮团队把“CRDT 理论”往“可落地协作能力”推近了一大步。

Automerge 为什么也值得关注

Automerge 很有代表性,因为它让“local-first software”这条讨论变得更具体。

它的存在提醒前端工程师:

CRDT 的意义不只是文档协作。

它也和:

  • 本地优先
  • 离线体验
  • 多设备同步

这些更大范围的应用形态有关。

为什么文本协作是 CRDT 最容易被神化、也最容易翻车的地方

文本编辑是 CRDT 最著名的应用场景之一。

但也是最容易让人低估复杂度的地方。

原因在于:

  • 文本顺序很敏感
  • 光标和选区也要同步
  • 删除通常不是简单消失
  • 历史元数据会积累

所以“我们用 CRDT 做一个多人编辑器”这句话,听起来很自然。

真正实现时,难点远比一句话多得多。

墓碑、元数据和 GC 为什么是绕不过去的话题

很多介绍 CRDT 都停在收敛示意图。

但一进入长期运行文档,团队很快会碰到:

  • 元数据膨胀
  • 删除痕迹累积
  • 文档越来越重

这时你就会意识到:

CRDT 的工程难点不只在“对不对”。

也在“久了之后还能不能轻”。

所以垃圾回收、压缩、子文档拆分,都是很真实的工程问题。

Local-first 为什么会让 CRDT 的价值被重新放大

本地优先软件的核心愿景之一,是让应用:

  • 离线也能工作
  • 本地响应很快
  • 之后再与其他副本同步

这正好和 CRDT 的优势形成共振。

因为一旦你想让用户本地先改、之后再并发合并,数据结构收敛能力就会变得很有吸引力。

所以 CRDT 的价值并不只属于“多人文档协作”。

它也属于“本地优先应用”这条更大的产品路线。

中国互联网语境里为什么 CRDT 没那么常被单独提,但问题其实一直都在

中国互联网很多团队更常直接谈:

  • 实时协作
  • 多端同步
  • 在线编辑
  • 离线能力

而不一定直接讲 CRDT 这个词。

但只要产品进入这些能力区间,底层的一致性问题就一定会出现。

所以在中国语境里,CRDT 有时不是显性名词。

但它对应的工程挑战一直都很现实。

海外语境里为什么 CRDT 和 local-first 绑定得更紧

海外很多技术讨论会把 CRDT 放进:

  • local-first software
  • collaborative editing
  • distributed consistency

这类更系统的话语里。

这使得 CRDT 在海外语境中,不只是某个协作算法。

而是被看成一种更大软件范式的一部分。

容易被讲浅的误区

误区一:CRDT 就是自动解决冲突

太浅。

它解决的是一类收敛问题,但前提和代价都很多。

误区二:CRDT 比 OT 新,所以一定更好

不准确。

它们是不同的一致性路线。

误区三:用了 CRDT 就不需要服务端

不对。

很多系统仍然需要中心服务来处理连接、权限和持久化。

误区四:所有共享状态都值得做成 CRDT

也不对。

要看数据形态和业务语义是否真的适合。

面试或技术分享里怎么讲更成熟

一个更成熟的表达可以是:

CRDT 的核心价值不是“自动合并”这么简单,而是通过数据结构本身的设计,让副本在并发修改后仍然能够收敛。它特别适合协作和 local-first 场景,但这种收敛能力通常要用更多元数据、更高内存占用和更复杂的数据结构来交换。

所以真正的工程判断不是“CRDT 好不好”,而是这类数据和产品语义是否值得用结构性收敛来换取离线与并发自由度。

实践建议

实践一:自己实现一个最小 Counter 类 CRDT

不要一开始就做文本。

先用最小计数器感受:

  • 本地更新
  • 合并规则
  • 最终收敛

实践二:用 Yjs 做一个最小协作文本 Demo

观察:

  • 同步是怎么发生的
  • 远端修改怎么进入本地
  • 工程接入成本在哪里

实践三:写一页“为什么这个业务状态不适合用 CRDT”

这个练习很重要。

因为知道什么时候不用,才说明你理解得足够深。

延展阅读