文档协同设计

文档协同技术笔记

概述

什么是文档协同?

文档协同,是指 多个用户不同的设备同一份文档 进行实时编辑协作。

需要解决的问题

  • 防止冲突:多用户同时编辑时避免数据覆盖
  • 数据一致性:确保所有用户看到相同的文档状态
  • 实时同步:将修改即时同步给所有协作者

常见解决方案

方案 实时性 并发编辑 实现复杂度 人工干预
读写锁 ❌ 不支持 ⭐ 简单
diff-patch ⚠️ 支持但需处理冲突 ⭐⭐ 中等 ⚠️ 冲突时需要
OT ✅ 支持 ⭐⭐⭐ 复杂
CRDT ✅ 支持 ⭐⭐⭐ 复杂

方案一:读写锁

核心概念

读写锁通过 写锁(Write Lock)读锁(Read Lock) 管理文档访问权限:

  • 写锁:获取写锁的用户独占编辑权,其他用户无法修改
  • 读锁:文档被编辑时,其他用户仍可只读访问

与操作系统读写锁的区别

类型 写锁时 读锁时
操作系统 禁止读和写 允许并发读,禁止写
文档协同 允许只读 允许并发读,允许写锁排队

优缺点

优点

  • 实现简单
  • 完全避免冲突
  • 读取具有并发性

缺点

  • 写锁期间其他用户只能等待,体验差
  • 可能造成饥饿现象(长时间占锁)

使用场景

  • 对实时性要求不高
  • 内容稳定、变动频率低(90%查看,10%编辑)
  • 强调强一致性(如审批流程文档)

方案二:diff-patch 合并

核心概念

通过计算文档差异来合并不同用户的修改,类似 Git 的合并流程:

  1. diff:比较多个用户的编辑内容与原始文档的差异
  2. patch:生成格式化更新补丁
  3. 冲突检测:无法自动合并时提示用户手动解决

优缺点

优点

  • 实现相对简单
  • 支持并发编辑
  • 版本可追溯
  • 带宽占用小

缺点

  • 冲突需要人工处理
  • 合并精度有限
  • 冲突区域难以管理

使用场景

  • 小范围协作编辑
  • 文档结构稳定
  • 异步协作与离线编辑
  • 注重版本可追溯性

小结

diff-patch 是一种轻量级、实现成本低的文档协同策略,适用于修改频率适中、协同规模有限的 非实时协作场景。虽然高频编辑时人工干预成本较高,但作为"Git 风格"协作的核心组件,仍被广泛应用于代码编辑器、CMS 等工具中。


方案三:OT 算法(Operational Transformation)

发展史

年份 里程碑
1989 OT 算法正式提出,标志协同编辑技术进步
2006 Google 首次将 OT 应用于商业产品 Google Docs
2011 微软在 Office 365 中基于 OT 实现协同编辑
2012 Quill 编辑器开源,其数据模型 Delta 基于 OT 设计
2013 ShareDB 开源,基于 OT 的流行解决方案

核心思想

OT = 操作(Operation)+ 转换(Transformation)

每个用户对文档的操作被记录并传播给其他用户,通过转换操作保证最终文档一致性。

1. 操作

文档的每次修改都是原子化操作,以 Quill 的 Delta 模型为例:

json 复制代码
{
  "ops": [
    { "insert": "Gandalf", "attributes": { "bold": true } },
    { "insert": " the " },
    { "insert": "Grey", "attributes": { "color": "#cccccc" } }
  ]
}
操作类型
操作 说明 示例
insert 插入文本 { "insert": "Hello" }
retain 保留(跳过)字符 { "retain": 7, "attributes": { "bold": null } }
delete 删除字符 { "delete": 4 }

attributes

  • null 值表示移除格式
  • 对象表示应用格式

2. 转换

转换发生在 服务端,对多个客户端的操作进行变换和冲突修正。

具体示例

初始文档:|a|b|c|(索引 0,1,2)

复制代码
用户A:在位置 1 插入 X
用户B:在位置 0 插入 Y

并发场景

复制代码
              初始状态 abc
                  ↓
        ┌──────────────┐
        │     abc      │
        └──────────────┘
             /      \
            /        \
A: Insert(1,'X')  B: Insert(0,'Y')
        ↓              ↓
     aXbc            Yabc
        ↓              ↓
   OT 变换          OT 变换
        ↓              ↓
应用 B'→Insert(0,'Y')  应用 A'→Insert(2,'X')
        ↓              ↓
   ┌──────────┐    ┌──────────┐
   │  YaXbc   │    │  YaXbc   │
   └──────────┘    └──────────┘
      ✅ 一致         ✅ 一致
关键点
  • 强一致性:OT 保证最终结果一致
  • 依赖服务端:所有操作需在服务器转换
  • 需要中央协调:不适合纯分布式系统

OT 的局限性

OT 是 强一致性设计,所有操作需在服务端转换,因此:

  • 对网络要求高
  • 不太适合分布式系统
  • 网络异常可能导致转换失败

方案四:CRDT 算法(Conflict-free Replicated Data Type)

为什么需要 CRDT?

CRDT 是为了解决 OT 的局限性

  • OT 需要中央服务器协调,不适合分布式
  • 网络异常时服务端转换会出问题

CAP 理论

CAP 理论由 Eric Brewer 提出:

  • C(Consistency):一致性,所有节点访问数据一致
  • A(Availability):可用性,系统始终响应请求
  • P(Partition tolerance):分区容忍性,容忍网络故障

在分布式系统中,P 是必须的,只能在 C 和 A 中选择其一。

OT 选择 C(强一致性)
CRDT 选择 A(可用性)→ 追求最终一致性

核心思想

CRDT 的设计哲学:

只要所有副本 最终 都收到相同的信息,不管谁先谁后,最终状态一定一样

数据结构设计

每个字符带有唯一 ID:

js 复制代码
{
  char: 'x',              // 字符内容
  id: [position_id, client_id]  // 唯一标识
}
具体示例

初始文档 abc,各字符 ID:

字符 ID 说明
a [1, A] 用户A写入
b [2, A] -
c [3, A] -

操作1:用户A在 a 后插入 X

生成新 ID [1.5, A]

js 复制代码
[ [1, A]: 'a', [1.5, A]: 'X', [2, A]: 'b', [3, A]: 'c' ]

操作2:用户B在开头插入 Y

生成新 ID [0.5, B]

js 复制代码
[ [0.5, B]: 'Y', [1, A]: 'a', [2, A]: 'b', [3, A]: 'c' ]

合并副本:按 ID 排序自动合并

js 复制代码
[0.5, B] → [1, A] → [1.5, A] → [2, A] → [3, A]
Y        → a       → X        → b       → c
// 结果:YaXbc

数据结构特点

特点 公式 作用
可交换性 merge(a,b) === merge(b,a) 无视消息到达顺序
可结合性 merge(merge(a,b),c) === merge(a,merge(b,c)) 不限制合并顺序
幂等性 merge(state, state) === state 支持消息重发

冲突处理

CRDT 的数据结构设计确保 所有操作天然不冲突

  • 并发插入:按 ID 排序
  • 并发删除:标记删除,最终状态不含该字符
  • 合并:只需按 ID 排序,无需判断"谁赢谁输"

CRDT 实现对比

算法 ID 结构 特点
RGA [前一字符ID, 副本ID] 链表式插入
Logoot [路径ID, 用户ID] ID 可排序且稀疏
Yjs 类似 Logoot + 优化 实际使用偏移机制

OT vs CRDT 对比

对比维度 OT CRDT
一致性 强一致性 最终一致性
架构 需要中央服务器 去中心化
网络要求
离线支持 ❌ 不支持 ✅ 支持
冲突处理 服务端转换 客户端自动合并
复杂度 服务端复杂 客户端数据结构复杂
代表实现 Google Docs, ShareDB Yjs, Automerge

选择建议

  • 需要强一致性、高实时性 → OT
  • 需要离线支持、去中心化 → CRDT

实践:Yjs 框架

Yjs 是一个基于 CRDT 的高性能协作编辑框架。

架构设计

复制代码
┌─────────────────────────────────────────────┐
│  顶层:编辑器适配层                          │
│  ProseMirror / Tiptap / Slate / Monaco      │
├─────────────────────────────────────────────┤
│  中间层:Yjs 核心                           │
│  CRDT 数据结构(Y.Text, Y.Map, Y.Array)   │
├─────────────────────────────────────────────┤
│  底层:通信与存储模块                        │
│  y-websocket / y-webrtc / y-indexeddb      │
└─────────────────────────────────────────────┘

编辑器绑定

编辑器 绑定模块
ProseMirror / Tiptap y-prosemirror
Quill y-quill
Monaco(代码编辑器) y-monaco
CodeMirror y-codemirror.next

核心优势

  1. 冲突处理稳健:基于 CRDT,无需中央协调
  2. 协作者状态同步:内置 Awareness 模块,同步光标和用户状态
  3. 离线编辑支持:天然支持,配合 y-indexeddb 实现本地持久化
  4. 版本快照:轻量级快照机制,支持版本回溯
  5. 高并发:实测支持数十到上百人实时编辑
  6. 插件生态丰富:多种通信和存储模块可选

Yjs vs Automerge

对比点 Automerge Yjs
性能 较慢(v2有改进) 非常快
内存使用 较大(保留完整历史) 较小(支持GC)
社区活跃度 中等
插件生态 较少 丰富
适用场景 通用 编辑器方向

官网资源

  • 官网https://yjs.dev/
  • 文档:权威 API 和使用方式
  • 通信模块:y-websocket、y-webrtc、y-indexeddb、y-redis

-EOF-

相关推荐
pengyi8710151 小时前
IP被封禁应急处理,动态IP池快速更换入门
大数据·网络·网络协议·tcp/ip·智能路由器
狂奔蜗牛飙车1 小时前
大数据赛项(中职组)-VMware+Ubuntu环境安装
大数据·vmware安装·大数据应用与服务·大数据入门指南·中职组大数据应用及服务赛项·ubuntu系统安装及基础配置·虚拟机创建及配置
cl131413141 小时前
烟气测量格恩朗流量计选型指南
大数据·网络·人工智能·产品运营
xixixi777771 小时前
国内首家“AI+量子”实体公司成立:量智开物发布“追风”“扁鹊”,开启下一代计算文明大门
大数据·网络·人工智能·安全·ai·科大讯飞·量子计算
BizViewStudio1 小时前
甄选2026:AI重构新媒体代运营行业的三大核心变革与落地路径
大数据·人工智能·新媒体运营·媒体
aXin_ya2 小时前
微服务 第五天 (elasticsearch基础)
elasticsearch·微服务·架构
weixin_307779132 小时前
SparkPySetup:基于Python的Windows 11 PySpark环境自动化搭建工具
大数据·开发语言·python·spark
XS0301062 小时前
Agent 记忆管理
大数据·人工智能·算法
lifewange2 小时前
Hadoop 全套常用 Shell 命令完整版
大数据·hadoop·npm