浅谈在线文档中的协同编辑
这是我在面试一个后端 node.js 工程师的时候遇到的一个场景题目(公司是做 AI 知识库的,可能遇到这些问题)。
我当时回答了如下内容:
- 每一篇文档有 id, 作者,内容,版本号, 状态(编辑中,已发布)
- 用户在进入页面的时候,会从 DB 中拉取最新的版本,编辑的时候会向服务器发送一个请求,这个时候服务器将状态改变为编辑中,同时将版本号加一,通知其他所有客户端。将这个改动记录到日志中,便于回档。
- 其他用户收到消息的时候重新渲染页面。
这个结果的正确性应该是可以保证的,可以通过乐观锁的机制来保证旧版本不会覆盖新版本数据。问题是每次更新都是全量的,多客户端修改频繁,网络 IO, 频繁的 DB 修改都是问题。
抛开性能问题不谈?上面的方案有个核心问题:其他用户怎么看到当前用户的鼠标位置?
优化的一个方案就是只做增量修改,这方面可以联想到 rsync 的增量同步, git 中的 diff 算法。
下面搜索答案,其实业界有现成的算法
OT (Operational Transformation)
广泛应用在 Goole Docs 等早期在线文档工具中。
核心思想:
- 用户的每次操作(例如插入、删除)都会被转化为一个操作 (operation)。
- 操作在传输到服务器和其他客户端的时候,会根据其他用户的操作进行转换(transformation),确保最终状态一致。
优点:
- 保证最终一致性(所有用户看到的内容相同)
- 支持高并发编辑
缺点:
- 实现复杂,尤其是处理冲突时需要复杂的转换逻辑
- 对服务器依赖非常大
CRDTs (Conflict-free Replicated Data Types)
CRDT 是一种分布式数据结构,近年来被广泛应用于实时协同编辑。
核心思想:
- 每个用户的操作是独立的,并且可以通过数学规则合并,无需转换
- 所有操作都是可交换、可结合和幂等的,最终状态会自动收敛
优点:
- 去中心化,适合分布式系统
- 无需复杂的冲突解决逻辑
缺点:
- 内存占用高(需要存储所有操作的历史)
- 实现复杂度高
版本控制和冲突解决
- 版本号:每个操作附带一个版本号,用于确定操作的顺序
- 冲突检测:多个用户同时编辑同一部分内容的时候,系统会检测冲突并解决(如通过 OT 转换或者 CRDT 合并)
- 用户光标同步:显示其他用户的光标位置和编辑状态,避免冲突
前端优化
- 增量更新:只更新文档中发生变化的部分,而不是重新渲染整个文档。
- 协同光标:实时显示其他用户的光标和选择范围。
- 离线支持:允许用户在离线时继续编辑,并在重新连接时同步更改。
典型工作流程
- 用户 A 在文档中插入一段文字,操作被发送到服务器。
- 服务器将操作广播给其他在线用户(B、C)。
- 用户 B 和 C 的客户端根据 OT 或 CRDT 规则将操作应用到本地文档。
- 如果用户 B 和 C 同时编辑同一部分内容,系统会检测冲突并解决。
- 所有用户的文档状态最终保持一致。
多人实时在线文档的核心算法包括 Operational Transformation (OT) 和 Conflict-free Replicated Data Types (CRDTs),结合实时通信技术、版本控制和分布式架构,确保高效、一致和安全的协同编辑体验。
参考
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 雨碎江南!