← 返回 Tony's Lab
Distributed Systems × Collaboration

CRDTConflict-free Replicated Data Type

多台设备同时改同一份数据,不用锁、不用服务器当裁判、断网也能改, 同步之后大家看到的内容还自动合成一个——不丢字、不冲突、不打架。 这不是魔法,是一类数学性质很讲究的数据结构。来,三分钟看懂它。

发布于 2026-09-22 · 读了约 12 分钟
设备 A 设备 C 一致状态 不管来的顺序
三股流从不同起点出发 → 汇聚成一个确定的状态
01

三个人同时改一份文档

先不看答案,看问题。假设你和两个同事在没有网络的飞机上,各自用笔记本编辑同一份文档的同一个副本。 落地后一同步,会发生什么?如果你用过传统的同步方式,下面三种死法至少中过一种:

01

锁:先到先得,其他人干等

文件被一个人锁住,别人只能看不能改。协调成本高,离线时更是不可能——断网了谁来发锁?

02

后写覆盖:谁最后保存,谁说了算

你写了一小时,同事先一步保存,你再保存——他的改动无声无息没了。数据丢了,还没人告诉你。

03

服务器仲裁:离线就瘫痪

所有改动都得先经过中央服务器排定顺序,它挂了你停摆,网差了你卡住,它记错排序大家一起错。

人真正想要的是第四种:谁也不用等、改动谁都别丢、断网照样写——合起来的时候自动拼成一份。 CRDT 就是为这个目标发明的。

02

CRDT 的魔法:三条性质

CRDT 全称 Conflict-free Replicated Data Type(无冲突复制数据类型)。 名字很唬人,本质是:一种把"合并"这个动作设计得极其乖巧的数据结构。 每一台设备都保存一份完整副本,各自独立地改;副本之间随时交换更新;交换完,自动合并。

它凭什么能自动合并?靠合并运算 ⊔(读作 merge)满足的三条数学性质:

交换律A ⊔ B = B ⊔ A

合并顺序无所谓。你先把 A 的更新发给我、还是先把 B 的发给我,最终结果一样。网络乱序、迟到、重排都不怕。

结合律(A ⊔ B) ⊔ C = A ⊔ (B ⊔ C)

先合并哪几份也无所谓。三台、五台还是二十台设备,怎么分组同步,结果都一致。拓扑结构随便你摆。

幂等A ⊔ A = A

同一份更新重复发几次都不影响结果。网络重发、断线重连、客户端重试,全都安全——不需要去重逻辑。

三条性质合起来推出一个很强的结论:最终状态只取决于"你收到了哪些更新"这个集合,与收到它们的顺序、次数、路径完全无关。 只要各端最终持有相同的更新集合,状态就一定相同。这就是强最终一致性(Strong Eventual Consistency)。 数学上,这叫一个 join-semilattice(并半格)——是不是听着就稳。

实现它的两件必备零件

顺带一提:删除也是门学问 在 CRDT 里,"删除"通常不是真把数据抹掉,而是留一个墓碑(tombstone)——一个"这条已被删"的标记。因为别的设备可能还引用着它,真删了会"复活"。代价是文档体积只增不减,所以工程上要靠快照 + 垃圾回收来压缩。
03

亲手试:乱序合并

说一百遍不如点三下。下面这句话,三台设备各自离线改了一处。 随便你按什么顺序把它们的改动并进来——注意最终那一句。

同步引擎 · 实况演示
已合并 0 / 3 处改动

这就是 CRDT 的核心观感:设备 B 和 C 明明改的是同一个位置(都在 "hello" 后面加字), 搁传统方式这叫"冲突",得人来裁决;搁 CRDT,系统按唯一 ID 排出确定的先后(B 的在前、C 的在后), 两句话都留着,谁的字都没丢。顺序你随意,结果就一个。

诚实声明:这是教学简化模型 真实文本 CRDT(如 RGA、YATA)用更精细的字符树、锚点与删除规则来保证同样性质, 内部还要处理墓碑与压缩。这里只保留了它的灵魂——合并结果只依赖操作集合,不依赖操作顺序。
04

五种常见 CRDT

CRDT 不是单一的算法,是一大家子。按"数据长什么样"分,常用的有这几类:

G-Counter 只增计数器 · 总数 = Σ 各端分量 直播点赞 / PV 计数

每台设备各存各的计数,合并时逐台取最大值再求和。只增不减,天生不会冲突。

PN-Counter 增减计数器 · 两个 G-Counter 一正一负 库存余量 / 粉丝数

把"加"和"减"分别记在两个只增计数器里,合并后相减。加购 +1、取消 −1,各端随便点。

LWW-Register 最后写入赢 · 比较时间戳 昵称 / 头像 / 主题色

整块覆盖式的数据,谁的戳大听谁的。坑在于物理时钟会漂——设备时间不准就可能"改丢",所以成熟实现用逻辑时钟(Lamport 时钟)而不是墙上时间。

G-Set / OR-Set 集合 · OR-Set 可增可删 标签 / 购物车 / 收藏夹

G-Set 只能加不能删;OR-Set(observed-remove)用"加过的痕迹"来支持安全删除——你删掉的元素,别人再加能正确保留,不会幽灵复发。

文本 CRDT RGA / YATA / Loro 算法 · 字符树 + 墓碑 协同编辑器 / 代码 / 富文本

最有价值也最难的一类。每个字符带唯一 ID 和插入锚点,形成一棵可合并的树;删除留墓碑。你看到的"多人同时打字自动合并",背后就是它。

05

谁在真实世界里用它

JupyterLabYjs 从 3.1 版起,笔记本与文件的实时协作基于 Yjs 这一 CRDT 框架实现,多人同改一个 notebook 不会互相覆盖。
ExcalidrawYjs 这个手绘风白板的实时协作(含 P2P 模式)用 Yjs 做底层数据同步,多人同时画、元素不打架。
Google DocsOT 它走的其实是另一条路——OT(操作变换),一个需要服务器参与排定顺序的经典方案。见下一节对比。
Notion自研 页面模型与同步引擎是自研的,算法细节未公开——所以不能简单说它是或不是 CRDT。
Git非 CRDT Git 的合并冲突要人来解(merge conflict),不是自动的。但它的"协议定义交换、远端可以随时换、数据在你手里",正是 local-first 思想的启蒙老师。

而 CRDT 领域几个绕不开的开源库:

06

CRDT vs OT:两条路线

协同编辑的两大流派。简单说:OT 把聪明放在服务器,CRDT 把聪明放在数据结构。

维度OT(操作变换)CRDT
服务器角色核心:必须参与,实时做变换运算可选:可以只是个哑广播/存储
拓扑通常中心化任意,含 P2P、网状
离线编辑难,回连时要补算大量变换原生支持,本来就是为离线设计的
冲突处理服务器按顺序变换操作数据结构自动合并
代价算法实现坑极多,中心依赖强元数据/墓碑带来存储与计算开销
代表Google DocsJupyterLab、Excalidraw
07

它搞不定的事(以及"CRDT 还不够")

CRDT 很强,但边界很硬。想清楚它不做什么,比知道它做什么更重要。

一、强一致性场景:从原理上就无解

需要"任何时刻所有人看到同一个数"的场景——支付扣款、抢票、库存扣减、会议室抢占式预约—— CRDT 处理不了。它保证的是最终一致(可能晚几秒才一致),而这些场景要求强一致,必须有人裁决谁先谁后。 这类问题请交给数据库事务,别硬套 CRDT。

二、"一致"不等于"合理"

CRDT 保证所有端最终显示同一份内容,但不保证这份内容符合人的直觉。两个人在同一段里写了完全不同的句子, 系统可能把两段都留着拼接起来——技术上完美一致,读起来像车祸现场。所以产品上还需要冲突提示、历史回放这类 UI 来兜底。

三、CRDT 只是零件,不是整机

这正是那场分享「CRDTs Are Not Enough」要说的:CRDT 库(Loro、Yjs、Automerge…) 本质是比较纯的计算,它管"数据怎么合并",但不管网络、不碰磁盘、不懂权限、不做事务。 要做成产品级本地优先同步引擎,还缺一大堆:

所以完整的图景是:CRDT 负责"合并"这一半,同步引擎负责"搬运、存盘、授权、兜底"另一半。 两半合起来,才是本地优先软件真正想给用户的承诺——秒开、离线可用、多端同步、后端可换、数据归你。

08

想再往下挖