一句话定义:操作变换(Operational Transformation,OT)是一类协同编辑算法——当多个人同时改同一份文档时,它把每个人的编辑操作按照别人已经做过的操作"顺移"到正确位置,让所有人的屏幕最终收敛到同一份内容。
位置为什么需要"顺移"
OT 把编辑拆成一个个原子操作,最常见的是两种:在某个位置插入字符 insert(pos, text),和从某个位置删除若干字符 delete(pos, len)。注意,pos 是一个数字,而数字只有在文档内容不变的前提下才有意义。
设想文档是 abc。A 在位置 1 插入 X,得到 aXbc;几乎同时,B 在位置 3 插入 Y,他心里的意思是"在末尾加个 Y"。
服务器先处理了 A。这时轮到 B 的操作 insert(3, "Y"),如果原样执行,Y 会插在 c 前面,变成 aXbYc——B 明明想加在最后,结果跑到了中间。所以必须把 B 的位置从 3 调整成 4,得到 aXbcY。这个"按别人的操作调整自己操作参数"的动作,就是操作变换:T(opB, opA) = opB'。
打个生活化的比方:食堂排队。你手上拿的是"第 5 位"这张号牌,前面有人走掉,你的号要往前挪;有人插到你前面,号要往后挪。号牌本身不重要,重要的是根据"别人动没动、动在哪儿"重新算。
两个人容易,三个人以上就难了
只处理两个并发操作,规则很简单:别人的插入位置 ≤ 我的位置,我的位置 +1;别人的删除发生在我之前,我的位置 -1。
真正的困难在于:
- 人一多,顺序就多。三个操作可能有多种到达顺序,变换后的结果必须完全一致,否则不同客户端会永久分叉。这要求变换函数满足一组被称为 TP1、TP2 的性质,而 TP2 在看似正常的操作对上就可能不成立,需要用更复杂的控制方式绕开。
- 位置对不等于意思对。两人同时给同一段加粗、同时调整列表缩进,位置算对了,语义仍可能拧巴,这叫意图保持问题。
- 结构一复杂就爆炸。纯文本还只是线性位置;换成富文本、表格、树形大纲、白板图形,变换规则的数量和耦合程度都会急剧上升。
- 还要处理离线积压。断网期间攒下一串操作,重连后要和服务器上的历史合并,队列越长越容易出错。
和相邻概念的区别
| 方案 | 冲突怎么处理 | 优点 | 代价 |
|---|---|---|---|
| OT | 由中心节点把操作变换后再应用 | 元数据小、工程成熟 | 变换函数难写难证,通常依赖中心服务器 |
| CRDT(无冲突复制数据类型) | 数据结构本身可交换、可合并 | 去中心化、离线友好 | 元数据膨胀,回收复杂 |
| 加锁 / 串行编辑 | 同一时刻只允许一人写 | 实现简单、不会错 | 并发体验差 |
还要区分两个容易混淆的东西:版本向量只负责发现冲突,不负责解决;diff/patch 描述的是同一份内容的先后差异,不处理"同时发生"。
对实际工作意味着什么
对普通职场人:多人同时编辑一份文档而不互相覆盖,不是理所当然的,背后是这类算法在扛。偶尔遇到的"光标跳走""文字错位",往往就是变换或同步环节出了岔子。
对从业者:选型时先问三个问题——需不需要离线、是不是富文本、能不能接受中心服务器。纯文本协同的 OT 方案相对成熟;富文本和图形场景要谨慎评估,CRDT 是常见替代路线。具体产品用哪种、限制如何,以官方文档为准。
