<!-- title: 离线编辑为何永不冲突：CRDT解码 -->
<!-- domain: programming -->
<!-- video: https://www.youtube.com/watch?v=EL-VoBcUIJk -->

# 离线编辑为何永不冲突：CRDT 如何驯服并发

## 导语：一个看似不可能的承诺

想象一个场景：你和同事各自打开同一份设计稿、同一篇文档、同一块白板，然后**同时**在上面写写画画。更要命的是，你们俩的手机都断了网——一个在飞机上，一个在地铁里。几个小时后重新连上网，两份被各自改得面目全非的副本，竟然能自动合并成一份，而且**一字不差、一条不丢、从不出错**。

这听起来像魔法，但它正是你每天都在用的 Google Docs、Figma、Notion、飞书背后的核心技术难题。视频《How Collaborative Text Editors Don't Break》（PawelCodeStuff 频道）用一整期节目，把支撑这套魔法的底层数据结构——**CRDT（Conflict-Free Replicated Data Type，无冲突复制数据类型）**——拆得明明白白。

为什么这个视频值得每个开发者认真看一遍？因为它回答了一个困扰分布式系统三十年的根本问题：**当多个副本各自演进、消息乱序到达、甚至永远无法对齐时间，我们凭什么还能保证最终一致？** 这篇解读会带你从"为什么会冲突"出发，一路走到 CRDT 的数学内核（半格、交换律、幂等）、两大实现流派、最难啃的文本序列问题、工业级落地（Yjs/Automerge/Zed/Figma），以及它身上那些被刻意回避的代价。

## 视频背景卡片

| 项目 | 内容 |
|------|------|
| 视频标题 | How Collaborative Text Editors Don't Break |
| 频道 | PawelCodeStuff（@pawel_code_stuff） |
| 视频链接 | https://www.youtube.com/watch?v=EL-VoBcUIJk |
| 内容形式 | 深度技术讲解（长视频拆解） |
| 核心主题 | CRDT 如何让多人协同文本编辑在断网、乱序、并发下仍最终收敛、永不冲突 |
| 适合人群 | 前端/全栈工程师、分布式系统学习者、做协同功能的产品技术决策者 |
| 风格定位 | 代码级讲解 + 概念可视化，属于 CRDT 解读类在开发者社区广泛传播的代表 |

## 核心内容拆解

### 一、问题起点：并发编辑为何天然就会"打架"

视频的开场其实是在制造焦虑感，但说得极准：**只要有"共享可变状态"，就必然有"冲突"**。

回到最朴素的情形。一份文档存在服务器上，A 和 B 同时拉取到本地。A 把第 3 行改成 "Hello"，B 把第 3 行改成 "World"。两人各自把改动推回去——服务器该信谁的？

最朴素的答案是 **Last-Write-Wins（最后写胜出）**：看时间戳，谁晚谁赢。但时间戳在分布式系统里是个谎言：两台机器的时钟根本不同步（NTP 误差可达几十毫秒，更别说时钟漂移和闰秒），而且即便时间戳一致，"晚"也不代表"对"。于是 B 的 "World" 覆盖了 A 的 "Hello"，A 的输入悄无声息地蒸发了——用户毫无感知，数据已经丢了。

另一个朴素答案是**加锁**：谁先抢到编辑权，谁独占。但这直接摧毁了协同体验的根基——"两个人能同时在一个文档上工作"。Figma 早期就坚决反对加锁，因为设计的灵魂就在于实时看到对方的光标在动。

视频反复强调的一个点（这也是它最想破除的直觉）：**冲突不是 bug，冲突是并发的必然副产品**。只要允许多个副本独立写、再异步同步，就一定会有"两个不同操作作用在同一份状态上"的时刻。与其试图消灭它，不如设计一种**结构，让冲突在数学上根本不可能产生坏结果**——这就是 CRDT 的全部出发点。

> 延伸分析：这个起点其实和 CAP 定理、PACELC 是一脉相承的。当网络可能分区（P）时，你要么牺牲可用性（C 一致性强但锁死），要么牺牲强一致性（A 高可用但允许临时分歧）。CRDT 选的是后者——**拥抱弱一致，但承诺"最终一致（eventual consistency）"**，而且把"最终一致"从一个模糊的工程口号，升级成了一条可证明的数学定理。

### 二、旧答案 OT：为什么 Google Docs 那套不够用

在 CRDT 之前，业界主流是 **OT（Operational Transformation，操作变换）**，1989 年由 Ellis 和 Gibbs 提出，后被 Google Docs、Google Wave 发扬光大。视频里把它作为"前辈"做了简短但精准的对比。

OT 的思路是：**不去设计特殊的数据结构，而是去"变换"操作本身**。当两个并发操作 $o_1$、$o_2$ 同时发生，定义一个变换函数 $T$，让 $o_1 \cdot T(o_1, o_2)$ 和 $o_2 \cdot T(o_2, o_1)$ 作用后的结果相同。简单说就是"我执行我的操作之前，先根据你刚做的操作把它修正一下"，从而让两个本来顺序相关的操作，变得和执行顺序无关。

听起来优雅，但 OT 有两个致命弱点，视频点到即止，我帮你展开：

**第一，变换函数 $T$ 极其容易写错。** 经典论文里，光是两个操作（插入/删除）之间的变换，就要满足所谓的 $TP_1$（转换性质 1）和 $TP_2$（转换性质 2）两条数学性质。学界花了十几年才搞清楚，业界流传的很多 OT 实现其实是不满足 $TP_2$ 的——也就是说，在特定的并发组合下，副本之间会**永久地分叉**，再也合不回去。这就是为什么 Google Docs 早期论文 Implant（Jupiter 协议）要那么小心。

**第二，OT 强依赖一个中心服务器来对所有操作排出一个全局总序。** 这意味着它**天生不适合 P2P、不适合长离线**。你飞机上离线编辑三小时，重连后要把这三小时的操作"重放"并对每个并发操作做变换——变换函数的累积误差很容易爆炸。

CRDT 的革命性正在于此：**它把"保证收敛"的责任，从脆弱的运行时变换算法，转移到了坚固的数据结构本身。** 你不需要算对每一步变换，只要数据结构满足几条数学性质，收敛就被自动保证。

### 三、CRDT 的数学内核：交换、结合、幂等——以及"半格"

这是整期视频、也是整个 CRDT 理论的灵魂。我用尽量朴素的话讲清楚。

CRDT 的承诺可以浓缩成一句话：**任意两个副本，无论经历过怎样乱七八糟的操作序列，只要它们收到过相同的操作集合，最终状态必然完全相同。**

要做到这点，操作（或状态的合并）必须同时满足三条代数性质：

1. **交换律（Commutative）**：$a \circ b = b \circ a$。先做 A 再做 B，和先做 B 再做 A，结果一样。消息乱序到达？无所谓。
2. **结合律（Associative）**：$(a \circ b) \circ c = a \circ (b \circ c)$。操作分批到达、分组不同？无所谓。
3. **幂等律（Idempotent）**：$a \circ a = a$。同一条消息由于网络重传到达两次？无所谓，做两遍等于做一遍。

这三条加在一起，意味着**消息可以任意乱序、任意重复、任意丢失后重传，合并结果都不变**。这对一个不可靠的异步网络来说，简直是量身定做。

视频里更妙的点（也是它"魔法感"的来源）是引入**单调性（monotonicity）**和**半格（semilattice）**的概念。把系统的每一个可能状态想象成空间里的一个点，合并操作就是"取两个点的最小上界（least upper bound，join）"。CRDT 保证：**状态只会沿着半格"向上"走，永远单调增长，永远不回退**。两个副本各自往上爬，它们爬过的点可能不同，但因为半格里任意两点都有唯一的最小上界，它们最终一定会爬到同一个点上——这就是"必然收敛"的数学保证。

> 延伸分析：这条单调性其实就是著名的 **CAP 里的 AP + Eventual Consistency** 的形式化。CRDT 用一个抽象代数结构（join-semilattice），把"最终一致"从一句工程师的祈祷，变成了一条可证明的定理。这是分布式系统里少见的"用数学把工程问题彻底解决"的范例。

### 四、两大流派：CvRDT（状态派）vs CmRDT（操作派）

视频在讲完数学后，转向工程实现，CRDT 分成两大流派：

**状态派 CvRDT（Convergent，收敛型）**：每个副本只管维护自己的完整状态，同步时直接把**整个状态**发给对方，对方用一个满足交换/结合/幂等的 `merge()` 函数合并。代表：G-Counter、G-Set。优点是**极其简单、极鲁棒**——连消息顺序和重复都不用管，反正每次都是 `merge(本地, 远端)`。缺点是**带宽开销大**，每次同步要传整份状态。

**操作派 CmRDT（Commutative，交换型）**：不传状态，只传**操作（op）**本身。`op1: 在位置 3 插入 'x'`、`op2: counter++`。优点是**带宽极省**，缺点是要求底层消息层提供**因果序广播（causal broadcast）**——也就是保证每个副本看到操作的顺序满足因果关系，且操作要满足所谓的"前条件（precondition）"。一旦消息层不可靠，操作派很容易出错。

视频给出的实用判断（也是工业界共识）：**简单的计数器、集合用状态派；高吞吐、对带宽敏感的用操作派；文本编辑这种大体量场景，通常用操作派或二者混合**。

### 五、经典积木：从计数器到集合

理解 CRDT，必须动手过一遍它的"标准零件"。视频和配套代码里反复出现这几块：

- **G-Counter（只增计数器）**：每个节点 $i$ 维护自己的一格 $c_i$，只能加自己的；合并时取逐元素 `max`。全局值 = 所有格之和。满足三条性质，完美收敛。
- **PN-Counter（可增可减计数器）**：用**两个** G-Counter，一个记加法 $P$，一个记减法 $N$，值 = $\sum P - \sum N$。于是减法也变成了"只增"——把减法状态也单调增长，绕开了"单调性不能下降"的限制。这个"把负变成另一个正"的技巧，是 CRDT 设计的典型心法。
- **G-Set（只增集合）**：只能 add，不能 remove。
- **2P-Set（两阶段集合）**：一个 add-set 加一个 remove-set（叫 tombstone 墓碑集），元素在 remove-set 里就不能再出现。又是用"记录删除"绕开"不能真删"。
- **OR-Set（Observed-Remove Set，观察删除集合）**：每次 add 都打一个唯一标签 tag，remove 时只删"已经看到过的那些 tag"。于是**并发的一 add 一 remove 不会冲突**——add 带新 tag，remove 删不到它，元素最终存在。这是最巧妙的集合 CRDT。
- **LWW-Register（Last-Write-Wins 寄存器）**：每个值带时间戳，合并取时间戳大的。看似就是朴素的 LWW，但配上单调的"时间戳只增"约束，它就成了合法 CRDT。

这几块积木的共性是：**它们都通过"把信息只增地累加"来回避删除和顺序问题**。这是 CRDT 设计哲学的核心——**绝不删除信息，只追加信息**。

### 六、最难啃的骨头：文本序列 CRDT（视频的主战场）

前面都是热身，视频真正的标题"How Collaborative Text Editors Don't Break"指向的，是 CRDT 里**最难、也最有商业价值**的一类：**序列/文本 CRDT**。

为什么文本特别难？因为文本编辑的核心操作是"在中间插入"和"在中间删除"。经典做法用**位置下标**（在第 5 个字符后插入），但位置是会漂移的——A 在位置 5 插入一个字符的同时，B 也在位置 5 插入，合并后谁排前？而且只要前面有人插入，后面所有人的"位置 5"就全都错了。

视频的核心解法（也是 Yjs/Automerge/RGA 一脉的思路）：**彻底放弃位置下标，给每个字符分配一个不可变的、全局唯一的"身份标识（identity）"，并用它的邻居关系来表达顺序**。

具体来说，每个字符是一个节点，记录三件事：`{id, value, leftOrigin}`——它的唯一 id、它的字符内容、以及它"插在哪个字符的右边"。这样插入就变成"挂到某个已知节点之后"，而不是"放到某个位置号"。两个并发插入同一个 `leftOrigin` 之后？用 id 的大小作为稳定的决胜规则（tie-breaker），于是无论消息怎么乱序，所有副本排出来的字符序列都完全一致。

这里还有两个视频强调、但很多人会忽略的坑：

**第一，删除不是真删，是"墓碑化（tombstone）"。** 因为并发，你删除一个字符的瞬间，可能别人正往它后面插字符。如果你真把节点删了，那个并发插入就"挂到了虚空"，再也合不回来。所以 CRDT 文本里，删除只是把节点标记为"已删除"（tombstone），它仍然留在结构里维持拓扑。这直接导致了**元数据膨胀**——一份 100KB 的文档，CRDT 内部可能存了好几 MB 的墓碑和 id。

**第二，垃圾回收（GC）几乎是无解的。** 你什么时候能安全地清理一个墓碑？只有当你**确信所有副本都已经看到了这次删除**时。但在一个开放系统里（节点随时加入离开、P2P 对等），你**永远无法知道"所有副本"是谁**。这就是为什么 Yjs、Automerge 的 GC 都很保守，长期运行下内存和存储压力是个真实的工程难题。

视频用代码一步步实现了一个最小可用的文本 CRDT，这种"从零搓一个"的讲解方式，让那些抽象的 id、leftOrigin、tombstone 变得肉眼可见——这是它比纯概念视频高明的地方。

### 七、工业级落地：谁在用，怎么用的

理论再美，落不了地也是空中楼阁。CRDT 在 2015 年后真正爆发，靠的是几个关键工程库：

- **Yjs**：Kevin Jahns 开发，目前生态最成熟的文本/富文本 CRDT 库。Notion、Tiptap、无数协同编辑器底层都在用，性能优化做到极致（二进制编码、压缩、增量同步）。
- **Automerge**：Martin Kleppmann（就是写《Designing Data-Intensive Applications》的那位）主推，强调"**local-first**（本地优先）"理念——数据首先存在用户设备上，服务器是可选的。这正是 CRDT 在哲学上最激进的应用：**让服务器从"必需的中心"退化为"可选的同步中介"**。
- **Zed 编辑器**：高性能 Rust 代码编辑器，把一套自研 CRDT 直接焊进了编辑器的 DNA，让多人实时协作成为编辑器的原生能力而非外挂。其官方博客《How CRDTs make multiplayer text editing part of Zed's DNA》是讲工业级 CRDT 取舍的标杆文章。
- **Redis CRDT（Active-Active）**、**Riak**：在数据库层提供 CRDT 数据类型，做多地域最终一致。
- **Figma**：值得一提的是，Figma 的多人协作**并非纯 CRDT，也不是 OT**，而是一套自研方案——实时光标和频繁属性更新走低延迟的"按属性 last-write-wins"，复杂的结构变更走服务器仲裁。Figma 的工程博客明确指出，对设计工具而言，纯 CRDT 的开销和语义（比如两个并发重命名会变成两个图层）未必符合用户预期。**这是一个重要的提醒：CRDT 不是唯一答案，它是一组权衡**。

### 八、代价：CRDT 不是银弹

视频相对克制，没有大谈 CRDT 的缺点。我帮你补上这至关重要的一半：

1. **元数据开销巨大**：墓碑、唯一 id、版本向量（version vector），让 CRDT 状态往往膨胀数倍到数十倍。
2. **垃圾回收是结构性难题**：如前述，开放系统里几乎无法安全清理墓碑。
3. **不是所有冲突都有"正确答案"**：两个用户把同一个文件改名成不同名字，CRDT 会诚实地给你**两个文件**——数学上收敛了，语义上却可能是 bug。语义冲突（semantic conflict）CRDT 救不了，得靠上层业务逻辑。
4. **CPU/内存成本**：大文档的文本 CRDT 合并在客户端可能造成卡顿，需要精心的性能工程（这正是 Yjs、Zed 大量投入的地方）。
5. **心智复杂度**：调试一个并发合并不一致的 CRDT bug，比调试普通 bug 难一个量级。

## 知识结构图

```mermaid
graph TD
    A[并发编辑的根本困境<br/>多副本独立写 + 异步同步 = 必然冲突] --> B{如何保证最终一致?}

    B -->|旧答案: 变换操作| C[OT 操作变换<br/>Google Docs / Wave]
    C --> C1[弱点1: 变换函数T易写错<br/>不满足TP2则永久分叉]
    C --> C2[弱点2: 依赖中心服务器排总序<br/>不适合P2P/长离线]

    B -->|新答案: 设计结构| D[CRDT 无冲突复制数据类型<br/>把收敛责任从算法移到数据结构]

    D --> E[数学内核: 三条代数性质]
    E --> E1[交换律 Commutative]
    E --> E2[结合律 Associative]
    E --> E3[幂等律 Idempotent]
    E --> F[半格 Join-Semilattice + 单调性<br/>状态只增不减 → 必然收敛]

    D --> G{两大实现流派}
    G -->|传整份状态| H[CvRDT 状态派<br/>merge函数取逐元素max<br/>简单鲁棒 但带宽大]
    G -->|只传操作| I[CmRDT 操作派<br/>需因果序广播<br/>省带宽 但更复杂]

    D --> J[经典积木]
    J --> J1[G-Counter / PN-Counter]
    J --> J2[G-Set / 2P-Set / OR-Set]
    J --> J3[LWW-Register]
    J --> K[最难: 文本序列CRDT<br/>放弃位置 用唯一id+leftOrigin<br/>删除=墓碑 元数据膨胀]

    K --> L[工业落地]
    L --> L1[Yjs 生态最成熟]
    L --> L2[Automerge local-first]
    L --> L3[Zed 自研焊进编辑器]
    L --> L4[Figma 自研混合方案<br/>CRDT并非唯一答案]

    style D fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
    style E fill:#fff3e0,stroke:#e65100
    style K fill:#fce4ec,stroke:#ad1457
```

## 实战启示

**如果你是产品/应用开发者，要做协同功能：** 第一反应不应该是"自己撸一套同步逻辑"，而是直接评估 **Yjs**（前端富文本/画布协同事实标准）或 **Automerge**（强调本地优先、JSON 状态）。自己实现 OT 几乎必然踩 $TP_2$ 的坑，自己实现 CRDT 文本更是地狱难度。站在巨人的肩膀上，把精力放在业务语义而不是底层收敛证明上。

**如果你在做离线优先（offline-first）应用：** CRDT 是目前唯一在数学上能保证"断网随便改、重连自动合"的技术路径。Martin Kleppmann 的 local-first 宣言值得作为你的产品哲学——**数据主权还给用户，服务器降级为可选项**。笔记类、清单类、设计类工具尤其契合。

**如果你在做分布式系统/数据库：** CRDT 的心智模型（单调半格、幂等合并）远超"协同编辑"这一应用。多地域最终一致、边缘计算、P2P 网络、去中心化存储，凡涉及"弱一致但求最终收敛"的场景，CRDT 都是首选武器。Redis Active-Active、Riak、Cassandra 的计数器都是这套思想的体现。

**认知升级——最重要的那一点：** CRDT 教会我们的，不只是"如何做协同编辑"，而是一种**重新分配责任**的工程哲学：**当一个运行时问题总是出错，别去修运行时，去改数据结构，让错误在结构上变得不可能发生。** 这和"用类型系统消灭空指针""用不可变数据消灭竞态"是同一种高级智慧——**把正确性从"靠人小心"升级为"靠结构保证"**。这种思维一旦内化，你看任何分布式系统的设计都会通透很多。

**当然，也要清醒：** 评估 CRDT 时务必先问三个问题——状态膨胀能否接受？语义冲突（如重命名）上层能否处理？目标场景是否真的需要最终一致而非强一致？不是所有协同都需要 CRDT，简单场景上一个乐观锁 + 中心服务器可能又快又省。

## 延伸学习路径

1. **奠基论文**：Marc Shapiro 等人 2011 年的 *A Comprehensive Study of Convergent and Commutative Replicated Data Types*（INRIA RR-7687）——CRDT 的"圣经"，把状态派/操作派、所有积木、收敛证明一次讲透。
2. **理念宣言**：Kleppmann 等人的 *Local-First Software: You Own Your Data*（local-first.web）——理解 CRDT 为何重要的最佳背景读物，理想与现实的平衡讲得极好。
3. **知识总站**：crdt.tech ——CRDT 类型一览与库的导航，概念速查利器。
4. **工程标杆博客**：Zed 官方博客 *How CRDTs make multiplayer text editing part of Zed's DNA*——工业级自研 CRDT 的取舍与性能优化实战。
5. **动手实践**：Yjs 官方教程与 Demos、Automerge 的 quickstart——跑通一个最简单的多人协同 demo，胜过读十篇概念文章。

## 一句话总结

CRDT 用一个单调半格的代数结构，把"多副本最终一致"从一句工程祈祷变成了一条可证明的定理——它让离线、并发、乱序的协同编辑从"会崩"变成"不可能崩"，代价是你必须接受"信息只增不删"的膨胀与克制。

---

## 参考来源

1. [How Collaborative Text Editors Don't Break — PawelCodeStuff](https://www.youtube.com/watch?v=EL-VoBcUIJk) — 本文解读的核心视频
2. [How CRDTs make multiplayer text editing part of Zed's DNA — Zed Blog](https://zed.dev/blog/crdts) — 工业级自研文本 CRDT 的取舍与实现
4. [About CRDTs — crdt.tech](https://crdt.tech/) — CRDT 类型一览与生态库导航
5. [How Figma's multiplayer technology works — Figma Blog](https://www.figma.com/blog/how-figmas-multiplayer-technology-works/) — 自研非纯 CRDT 协同方案的真实工程权衡

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

> [!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学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
