首页 › programming › 各改各的终归一致:CRDT合并内核拆解

各改各的终归一致:CRDT合并内核拆解

导语:为什么你和同事同时改一份文档,最后不会"打架"?

设想一个再普通不过的场景:你正和三位同事协作编辑同一份设计稿,你拖动一个图层的颜色,A同事同时在移动它的位置,B同事此刻还断着网——他在高铁上继续修改。几分钟后B重新联网,所有人的改动竟然奇迹般地全部保留、互不覆盖,文档最终归于同一个确定的状态。

这件事之所以"奇迹",是因为在分布式系统里,"多个副本各自修改、互不通信,最后还能合而为一"听起来就像在违反物理定律。让它成立的,是一类被称为 CRDT(Conflict-free Replicated Data Type,无冲突复制数据类型) 的数据结构。而如果要挑一个把这件事讲到骨头里的视频,非 Martin Kleppmann 的演讲《CRDTs: The Hard Parts》莫属——Kleppmann 是被程序员奉为"分布式圣经"的《Designing Data-Intensive Applications》(DDIA)的作者。

这篇解读要解决的问题是:CRDT 究竟靠什么数学魔法让"各改各的"也能收敛?为什么 Kleppmann 说 CRDT"很容易实现得很烂"?以及——它和你日常用的 Figma、Google Docs、以及 5G 核心网里的分布式数据,到底有什么关系。

视频背景卡片

项目 内容
视频标题 CRDTs: The Hard Parts
讲者 Martin Kleppmann(剑桥大学副教授,《DDIA》作者)
频道 Martin Kleppmann — Talks(Hydra 分布式计算大会 2020)
时长 约 52 分钟
影响力 分布式系统社区长期被反复引用的标杆级长青演讲,数十万次播放
核心主题 CRDT 表面原理易懂,但"水面之下"藏着大量实现难题

核心内容拆解

要点一:CRDT 是为"乐观复制"而生的自动调停者

视频开篇就给 CRDT 划定了精确的活动范围。要理解 CRDT,得先理解分布式系统处理"同一份数据被多处同时修改"的两种截然相反的哲学——这一点在权威站点 crdt.tech 上讲得最清楚。

第一种叫强一致复制(Strongly consistent replication):副本之间必须先"协商"达成共识,才能确认一次修改。这种方式能保证最强的正确性(可串行化事务、线性一致性),但代价沉重——必须等待协调完成,性能被拖累;更致命的是,CAP 定理告诉我们:当某个副本因为网络分区或离线而与系统失联时,它根本无法进行任何数据修改。

第二种叫乐观复制(Optimistic replication):用户可以在任意副本上独立修改数据,哪怕这个副本处于离线、断网状态。这条路把性能和可用性拉满,但必然产生冲突(conflict)——多个人同时改了同一处,等副本重新通信时,冲突必须被解决。

CRDT 正是为乐观复制系统服务的,它专门负责"自动解决冲突"。无论各副本做了什么修改,数据都能被合并成一个一致的状态,而且这个合并由 CRDT 自动完成,不需要任何特殊的冲突解决代码,也不需要人工干预。

(延伸分析)这条定义里最值钱的一个词是"自动"。传统分布式数据库让你写一大堆 if-else 去判断冲突怎么办;CRDT 把冲突解决策略"焊死"进数据结构本身。你只管改,合并不需要你操心。这是 CRDT 最反直觉也最优雅的地方——它用数学的确定性,换掉了工程的不确定性。

要点二:CRDT 的三道数学护身符——交换、结合、幂等

Kleppmann 反复强调:CRDT 能保证 最终一致性(eventual consistency)——只要不再有新更新,所有副本最终都会看到同一个结果。但"最终"凭什么成立?答案藏在三条看似枯燥的代数性质里,这是整个 CRDT 大厦的地基(延伸分析):

  1. 交换律(Commutative):合并的顺序不影响结果。先合并 A 再合并 B,等于先合并 B 再合并 A。
  2. 结合律(Associative):合并的分组不影响结果。(A⊕B)⊕C = A⊕(B⊕C)。
  3. 幂等性(Idempotent):把同一个更新合并两次,等于合并一次。A⊕A = A。

只要合并算子 ⊕ 同时满足这三条,就构成一个半格(semilattice / join-semilattice)。在半格里,无论消息以什么顺序到达、到达几次、丢没丢重不重传,所有副本的状态都只会沿着格"单调上升",最终必然收敛到同一个"上确界"(least upper bound)。

这就是为什么 CRDT 不需要协调、不怕乱序、不怕重复——因为它从一开始就不在乎顺序。

(延伸分析)注意这三条性质其实是在给分布式系统"卸担子":可靠 causal 广播、消息去重、全局排序这些昂贵的基础设施,对 state-based CRDT 都不是必需的。这正是 CRDT 与 Paxos/Raft 这类共识算法的本质区别——共识算法花大力气"统一口径",CRDT 干脆承认"口径永远不统一,但结果一定一样"。

要点三:两大实现流派——传状态 vs 传操作

CRDT 在工程上分裂成两个家族,Kleppmann 在视频中都有涉及。

State-based(基于状态,又叫 CvRDT / 收敛型):每个副本把自己的完整状态广播出去,接收方用一个合并函数把两边状态融合。合并函数必须是上面说的那个半格 join。优点是容错——丢包重传无所谓,幂等;缺点是状态越大、广播越胖(后来用"状态压缩/δ-CRDT"缓解)。

Operation-based(基于操作,又叫 CmRDT / 交换型):副本之间不传状态,只传操作(operation)。它要求底层提供一个可靠的因果广播(reliable causal broadcast)通道:消息不丢、不重复、且因果有序。在满足这个前提下,只要操作本身满足交换律,合并就能正确。

(延伸分析)两者的取舍很像通信工程里"传整个文件 vs 传增量 diff"。CvRDT 像"全量同步",简单粗暴费带宽;CmRDT 像"增量同步",省带宽但要求传输层更可靠。在 5G 核心网里,网元之间做配置/状态同步时也常面临同样的"全量 vs 增量"抉择,背后的权衡逻辑完全一致。

要点四:CRDT 的乐高积木——Counter、Register、Set

视频没有停留在抽象数学,而是落到几个最基础的"积木"数据类型。这些是构建复杂 CRDT 的原子,也是理解 CRDT 思路最快的方式:

Figma 的博客里讲得非常直白:它用 LWW-Register 来同步"对象属性"——两个客户端改同一个对象的不同属性不会冲突,改同一属性就让服务器定谁后到谁赢;它用类似 last-writer-wins set 的思路来管理"对象是否存在"。

(延伸分析)这套积木的思想极简:把"容易冲突的操作"翻译成"天然无冲突的数据表示"。计数难,就拆成每个副本独立计数再相加;删除难,就不真删、改记墓碑。这与通信里"用冗余换确定性"的工程哲学一脉相承。

要点五:真正的"硬骨头"——序列/列表 CRDT(文本协作编辑)

到这里都还是"入门甜点",视频标题里的 The Hard Parts(硬骨头)真正从"文本/列表编辑"开始。这也是 Kleppmann 全场最精彩的部分。

问题看似简单:两个人在协作文档里插入文字。A 在第 5 个位置插入了"Hello",B 同时在第 5 个位置插入了"World"。麻烦在于——"位置"是会漂移的。当 A 的插入还没到达 B 时,B 眼里的"第 5 位"和 A 眼里的"第 5 位"已经指代不同的物理位置了。如果天真地用"位置下标"来描述插入,合并后文字会错乱。

为此,研究者发明了一整套序列 CRDT 算法:WOOT、Treedoc、Logoot、LSEQ、RGA。它们的共同思路是——放弃用"脆弱的整数下标"定位,改用"唯一标识符"来定位每个字符,让插入位置不依赖当下文档状态。

但 Kleppmann 用自己团队的研究戳破了"乐观":他在 PaPoC 2019 发表的论文揭示了 交错异常(Interleaving Anomaly)——许多已发表的序列 CRDT 算法,在特定并发场景下,会把两个人分别打入的字符错误地交错穿插,产出谁都不想要的乱码(比如 "HeWlolorld")。这类算法"很容易实现得很烂",而且"许多已发表的算法都有异常,导致它们在某些情况下表现得很奇怪"。

(延伸分析)这段是整场演讲最该被记住的警示:CRDT 的"无冲突"只在数学层成立,具体算法在真实并发下的人类可读性是另一回事。这也是为什么 Kleppmann 的团队后来用 OpSets(操作集合)给 RGA 做了形式化证明,证明 RGA 确实不会交错——证明一个数据结构"正确",本身就是 PhD 级别的难活。

要点六:删除是最大的坑——墓碑与元数据爆炸

序列 CRDT 还埋着一个更深的雷:你根本不能真正删除任何东西。原因听起来反直觉——

假设 A 删掉了第 5 个字符,而 B 此时还不知道删除这件事,正基于"字符还在"的状态插入一个新字符。如果 A 真把那个字符从记录里抹掉,B 的插入就失去了参照物,合并必然出错。所以 CRDT 的删除,本质上是一种"软删除(tombstone,墓碑)"——被删的字符只是被标记为"看不见",但它的标识符、时间戳必须永久保留,好让后来的并发操作能正确定位。

后果就是:元数据(metadata)会无节制地膨胀。一份编辑了上万次的文档,墓碑会堆积如山,简单实现的 CRDT"性能极差",而让它性能好"非常具有挑战性"。

Kleppmann 给出的解法是他在 Automerge(最流行的 CRDT 库之一)上的研究:用列式编码(columnar data encoding)和二进制数据格式来压缩元数据开销。这正是他在参考文献里列出的 "Experiment: columnar data encoding for Automerge" 和 "Automerge binary data format"。

(延伸分析)这条对工程实践的冲击极大:选 CRDT 不是选了就"高可用",而是用无限的内存增长换取无需协调的合并。长生命周期、高频编辑的场景(比如一个用了三年的协作文档)必须配套墓碑压缩/GC 策略,否则内存迟早爆炸。Figma 的做法更务实——删除对象时,被删对象的属性不留在服务器,而是塞进执行删除那个客户端的 undo buffer,由它自己负责恢复,从而让"长寿文档不会因为编辑而持续膨胀"。这是中央服务器架构才能享受的"作弊"特权。

要点七:被忽视了十年的"移动操作"

Kleppmann 在视频中点出一个曾被整个学术圈长期忽视的空白:移动(Move)操作。

绝大多数早期 CRDT 研究都聚焦在"增、删",却很少严肃处理"把一个元素从 A 位置搬到 B 位置"这种最日常的操作。这在纯文本编辑里还能凑合(删了重加),但在树形结构(文件系统、设计图层级、思维导图)里,"移动一个节点"和"删除再新建"完全不是一回事——后者会丢失节点身份(identity),导致并发的其他修改全部失效。

为此 Kleppmann 团队发表了 "A highly-available move operation for replicated trees"(面向复制树的高可用移动操作),专门解决"移动 + 并发删除"交错时如何保持正确,这是分布式文件系统能用 CRDT 的前提。

(延伸分析)Figma 的工程笔记恰好印证了这个"移动有多难":它把"父子关系"表示成"子节点上存一个指向父节点的链接"(而不是父节点存子节点列表),从而在 reparenting(重新指定父节点)时保持对象身份不变。但这就引入了新麻烦——环(cycle):A 把 B 挪到自己下面、同时 B 把 A 挪到自己下面,就形成了环。Figma 的服务器会拒绝造成环的更新,客户端则在冲突时"暂时把这两个对象互相挂载并从树里摘出,直到服务器驳回"。Figma 还用 分数索引(fractional indexing)——用一个 0~1 之间的分数表示顺序,插入时取两邻居分数的平均值——来确定子节点排列。这套组合拳,就是 CRDT 思想在中心化产品里的"实用降维版"。

要点八:CRDT vs OT——为什么 Figma 和 Google Docs 走了不同的路

视频之外,最值得补的一笔是 OT(Operational Transformation,操作转换) 与 CRDT 的世纪对比,这也是 Figma 博客的核心叙事。

Google Docs 用的是 OT:所有操作流经一个中央服务器统一排序,服务器对并发操作做"转换",让它们能正确叠加。OT 的致命伤在于状态空间组合爆炸——用论文原话说,"即使只处理 insert 和 delete 两种基本操作,形式化证明也极其复杂、极易出错","导致难以预见的复杂状态组合"。

Figma 当年评估后选择了不走 OT、也不走"纯 CRDT",而是"受 CRDT 启发的自研系统"。理由很务实:Figma 是中心化的(有中央服务器当唯一裁判),可以名正言顺地砍掉 CRDT 为去中心化付出的性能/内存开销;而且 Figma 不是文本编辑器,不需要 OT 那套处理长文本的能力,"杀鸡焉用牛刀"。

Figma 的核心结论之一非常发人深省:"即使你不是在做去中心化系统,CRDT 文献同样大有可为。" CRDT 是一套被深入研究过的、坚实的地基,理解它能帮你构建出正确的系统,然后你可以"根据应用需要放松掉其中一些要求"。

(延伸分析)这场对比的真正启示不是"谁赢谁输",而是先问清楚你的系统假设是什么。需要去中心化/离线优先/P2P → CRDT;有强中心、追求极简 → 受 CRDT 启发但去繁就简;要强一致/可串行化事务 → 老老实实上共识算法。没有银弹,只有匹配场景的权衡。

知识结构图

graph TD
    A["CRDT 无冲突复制数据类型<br/>各副本独立修改,自动合并"] --> B["目标:乐观复制下自动解决冲突"]
    A --> C["数学地基:半格 join<br/>交换律 ∪ 结合律 ∪ 幂等性<br/>→ 最终一致性"]
    A --> D["两大实现流派"]
    D --> D1["State-based CvRDT<br/>广播完整状态<br/>容错好,状态胖"]
    D --> D2["Op-based CmRDT<br/>广播操作<br/>省带宽,需因果广播"]
    A --> E["基础积木"]
    E --> E1["G-Counter 只增计数"]
    E --> E2["PN-Counter 可减计数"]
    E --> E3["LWW-Register 后写覆盖"]
    E --> E4["OR-Set 加删集合+墓碑"]
    A --> F["The Hard Parts 硬骨头"]
    F --> F1["序列CRDT 文本协作<br/>WOOT/Treedoc/Logoot/LSEQ/RGA"]
    F --> F2["交错异常 Interleaving<br/>需形式化证明"]
    F --> F3["删除即墓碑<br/>元数据爆炸→列式压缩"]
    F --> F4["移动操作 Move<br/>CRDT树/分布式文件系统"]
    A --> G["工程对照"]
    G --> G1["OT 操作转换<br/>Google Docs 中心化"]
    G --> G2["受CRDT启发<br/>Figma 自研降维"]

实战启示:CRDT 对你到底意味着什么

如果你是应用开发者,最大的启示是——别从零造 CRDT,直接用成熟库。Kleppmann 全场演讲其实都在论证"CRDT 极易实现得很烂",连顶会论文都会出交错异常。生产环境请直接上 Automerge(Kleppmann 本人参与,主打 local-first)或 Yjs / Y-CRDT(Kevin Jahns 出品,性能极佳,被 Notion、Atlassian 等大量采用)。它们已经替你趟过了墓碑压缩、交错证明、移动操作这些深坑。

如果你在做协作/离线优先产品,CRDT 是当前唯一能在"断网也能改、联网必合并"这件事上给出数学保证的方案。判断要不要用的标准很简单:你的用户是否需要离线编辑后无缝合并?是 → CRDT;只是"实时多人在同一服务器上编辑" → OT 或更简单的方案也够。

如果你来自通信行业(这正是 51学通信 读者的主场),CRDT 的思想其实早已潜伏在你身边。5G 核心网里 UDR(统一数据仓库)的多副本同步、UDM/UDR 的地理冗余、网元之间状态数据的最终一致性,本质上都是"乐观复制 + 冲突调和"的问题域。当 NF(网络功能)跨数据中心、跨边缘部署时,强一致共识的开销会拖垮可用性,而 CRDT 式的"各副本独立演进、最终收敛"反而更贴合通信系统对高可用的执念。尤其在边缘计算(MEC)和本地优先趋势下,"断网也能写、联网自动合"正是 5G/6G 分布式数据架构的方向。理解 CRDT,等于掌握了评估这些分布式数据方案正确性的底层标尺。

一句话:CRDT 用"不在乎顺序"的数学,换来了"无需协调也能合一"的工程自由——它不是让你写更少的代码,而是让你少写那些永远写不对的冲突处理代码。

延伸学习路径

  1. crdt.tech(https://crdt.tech/)——CRDT 社区的官方门户,最权威的入门定义、算法清单和库索引,读完它就有了正确的术语地基。
  2. 《Designing Data-Intensive Applications》(Martin Kleppmann 著)——第 5 章"复制"系统讲解了乐观复制、最终一致性与冲突处理,是理解 CRDT 出现动机的最佳背景读物。
  3. Automerge / Yjs——两个最成熟的开源 CRDT 库。建议各跑一个 demo,亲手感受"断网编辑后合并"的丝滑,再回头看本视频会豁然开朗。
  4. "Local-first software" 论文(Kleppmann 等,Onward! 2019)——CRDT 的"信仰宣言",论述为何"数据该归用户所有、云只是同步工具",直接对应当下 AI 本地化、数据主权的趋势。
  5. Figma 博客《How Figma's multiplayer technology works》(Evan Wallace)——生产级中心化系统的"降维实战",把 CRDT 思想与 OT 对比、分数索引、树 reparenting 讲得通透,是本视频最好的工程注脚。

一句话总结

CRDT 用"交换律+结合律+幂等"这三道数学护身符,把分布式世界里最令人头秃的"冲突解决"焊进了数据结构本身——让多个副本即使各改各的、断网离线,也终将归于同一个确定的未来。

参考来源

  1. CRDTs: The Hard Parts — Martin Kleppmann(Hydra 2020 演讲) — 本次解读的核心视频
  2. About CRDTs · crdt.tech — CRDT 社区官方门户,权威定义与乐观复制/强一致对比
  3. How Figma's multiplayer technology works — Figma Blog — 生产级 CRDT 启发系统实战案例
  4. CRDTs: The Hard Parts — Martin Kleppmann's talks(演讲参考文献清单) — 演讲引用的全部学术论文(Logoot/LSEQ/RGA/移动操作等)

    本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

完成摘要(What was done)

下一步计划(What's next)

注意事项

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入