【技术本质篇】深度解析 OT (操作转换) 算法:如何优雅地解决多人编辑冲突?
在 Web Excel 这种高频交互的应用中,用户感受到的“实时”其实是一种技术构建出来的“幻觉”。 由于网络延迟的物理存在,用户 A 的修改传到服务器需要时间,服务器再广播给用户 B 也需要时间。在这个“时间差”里,用户 B 可能也进行了一次修改。如果系统只是简单地按照“谁最后到达服务器谁就生效”的规则(Last Write Wins),那么先到达的数据就会被覆盖,用户的工作成果会无故丢失。 更糟糕的是,如果两人操作的是位置相关的逻辑(例如:Alice 在第 5 行插入数据,而 Bob 删除了第 2 行),如果不进行逻辑转换,Alice 的数据最终可能会出现在错误的位置。 OT 算法的核心使命,就是让每个客户端在接收到远程指令时,能够根据自己当前的上下文环境,对该指令进行“二次加工(转换)”,从而达成全局一致。 在 SpreadJS 的协同世界里,所有的变更都被抽象为 Op(操作)。Op 是对文档状态单次修改的严谨描述。 当你在 SpreadJS 单元格输入“Hello”时,客户端会产生一个简单的 Op: 当这个 Op 到达协同服务器后, 通过 为了通俗易懂地解释 OT,我们参考 SpreadJS 产品文档中的经典案例: 初始状态: 共享文档中只有三个字符 结果: A 看到的是 OT 的精髓在于 最终: 全局一致,冲突消失。 在 SpreadJS 协同插件中,开发者无需手动编写复杂的转换逻辑, 通过 codeTypeScript 其中的 开发者只需要在客户端和服务端通过 对于 SpreadJS 的专用表格协同, 有些简单的同步方案采用的是“全量结果同步”,即每次修改都把整个单元格甚至整个表格发给后端。这在企业场景下是不可接受的: SpreadJS 采用 OT 算法,实现了真正的“意图同步”。 它传输的是轻量级的指令流,这使得它在处理超大型文档和高并发协作时,依然能保持极低的延迟和极高的准确度。 对于金融、财务、生产制造等企业级应用来说,数据的准确性高于一切。SpreadJS 协同插件通过底层的 OT 算法不仅仅是解决冲突,更是对用户劳动成果的尊重。 它确保了每一处修改都能被正确地理解、转换并应用,让多人在线协作从“敢看不敢改”变成了真正的“放心协作”。 在了解了冲突解决的底层逻辑后,你可能会好奇:在视觉上,我们如何知道其他用户正在做什么?如何避免两个人在完全不知道对方存在的情况下修改同一个格子? 下一篇文章,我们将聊聊协同中的“上帝视角”——Presence 插件与实时状态共享。敬请期待。 技术要点回顾:一、 实时协作的挑战:幻觉与真相
二、 解剖操作意图:什么是 Op (Operation)?
1.客户端 Op:纯粹的操作描述
// 示例:在文档位置 1 插入文本 'hello'
sharedDoc.submitOp({ pos: 1, text: 'hello' })2.服务端 Op:带有元数据的“身份牌”
js-collaboration-ot 模块会为其添加元数据,使其具备可追溯性和唯一性:src 和 seq 的组合,服务器可以检测网络不稳定导致的重复提交;通过 v 版本号,服务器可以判断该操作是否已经过时,是否需要进行转换。三、 OT 算法的实战:从“xyz”到“xaybz”的冲突之旅
"xyz"。 并发场景:"a",期望结果:"xayz"。"b",期望结果:"xybz"。场景 1:如果没有 OT(导致数据不一致)
"xayz",并广播给 B。"b")。服务器在 "xayz" 的位置 2 插入 "b",结果变为 "xabyz"。"b")传给 A 时,A 当前的文档是 "xayz"。如果在位置 2 插入 "b",A 的屏幕上会显示 "xabyz"。"xybz"。如果在位置 1 插入 "a",B 的结果变成了 "xaybz"。"xabyz",B 看到的是 "xaybz"。两边数据不一致,系统崩溃。场景 2:有了 OT 的优雅转换
transform 函数。当 B 的操作到达 A 时,A 并不直接执行它,而是先问一下系统:“在我已经执行了 A 操作的前提下,B 操作应该怎么变?”"a",那么原来在位置 2 的操作意图,现在应该“自动往后挪一位”,变成在位置 3 插入。"b"),得到 "xaybz"。"xaybz"。四、 SpreadJS 协同框架中的 OT 实现
js-collaboration-ot 已经封装好了核心底座。1.OT 类型的定义
OT_Type 接口,系统定义了如何创建快照、如何转换操作以及如何应用操作。export interface OT_Type<S = unknown, T = unknown> {
uri: string; // 类型唯一标识
transform(op1: T, op2: T, side: 'left' | 'right'): T; // 核心转换函数
apply(snapshot: S, op: T): S; // 应用操作
}side 参数('left' 或 'right')非常巧妙,它用于处理“平局”情况:如果两个用户同时在同一个位置插入字符,系统通过 side 约定谁的字符排在前面,确保无论在哪个客户端,转换结果都绝对统一。2.注册与绑定
TypesManager.register(type) 注册相同的 OT 类型,SpreadJS 就会自动接管后续的冲突处理。spread-sheets-collaboration-addon 已经内置了符合 Excel 逻辑的复杂 OT 类型。它不仅能处理简单的字符串,还能处理单元格属性、行列增减、公式重新计算等复杂的表格逻辑。五、 意图同步 vs 结果同步:为什么 OT 更适合企业级 Excel?
六、 总结:严谨,是协同的第一要素
js-collaboration-ot 模块,将深奥的操作转换理论转化为开箱即用的能力。扩展链接