谷歌文档如何处理编辑冲突



我一直在尝试编写自己的Javascript编辑器,其功能类似于谷歌文档(允许多人同时使用)。有一件事我不明白:

假设用户A和用户B直接连接,网络延迟为10ms。我假设编辑器使用diff系统(正如我所理解的Docs),其中的编辑表示为"在索引3处插入‘文本’",diff带有时间戳,并强制所有客户端按时间顺序应用。

让我们从一个包含文本的文档开始:"xyz123"

用户A在时间戳001ms的文档开头键入"abc",而用户B在时间戳005ms的"xyz"one_answers"123"之间键入"hello"。

然而,考虑到网络延迟,两位用户都预计结果为:"abcxyzhello123":

  • 用户B将在时间011ms接收用户A对"在索引0处插入‘abc’"的编辑。为了保持时间顺序,用户B将撤消用户B在索引3处的插入,在索引0处插入用户A的"abc",然后在索引3(现在位于"abc"one_answers"xyz"之间)处重新插入用户B的插入,从而给出"abchelloxyz123"
  • 用户A将在时间015ms接收用户B对"在索引3处插入‘你好’"的编辑。它会识别出用户B的插入是在用户A之后完成的,并简单地在索引3处插入"hello"(现在位于"abc"one_answers"xyz"之间),给出"abchelloxyz123"

当然,"abchelloxyz123"与"abcxyz你好123"不同

除了给每个角色分配自己唯一的ID之外,我无法想象谷歌会如何有效地解决这个问题。

我想到了一些可能性:

  • 跟踪插入点而不是用diff发送索引是可行的,但如果用户B在编辑前将其插入点移动了1ms,则会出现完全相同的问题
  • 你可以让用户B用他的diff发送一些信息,比如"在‘xyz’之后插入",这样用户A就可以智能地识别出发生了这种情况,但如果用户A插入文本"xyz"呢
  • 用户B可以识别出已经发生了这种情况(当它收到用户A的diff并发现这是一个冲突时),然后发送一个diff来撤销用户B的编辑,并发送一个新的diff来插入用户B的"hello"abc".length索引。这样做的问题是(1)用户A会看到文本中的"跳跃",(2)如果用户A继续编辑,则用户B将不得不不断修复其差异——甚至"修复程序"差异也会关闭并需要修复,从而成倍地增加复杂性
  • 用户B可以在发送diff的同时发送一个属性,即它收到的最后一个带时间戳的diff是-005ms或其他什么,然后a可以识别出B不知道它的更改(因为a的diff在001ms),然后进行冲突解决。问题是(1)考虑到大多数计算机时钟都不精确到毫秒,所有用户的时间戳都会稍微偏离;(2)如果第三个用户C与用户a的延迟为25ms,但与用户B的延迟为2ms,并且用户C在-003ms处添加了一些介于"x"one_answers"y"之间的文本,则用户B将使用用户C的编辑作为参考点,但是用户A直到22ms才知道用户C的编辑(以及用户B的参考点)。我相信,如果你使用一个通用的服务器来标记所有编辑,这是可以解决的,但这似乎相当复杂
  • 你可以给每个字符一个唯一的ID,然后使用这些ID而不是索引,但这似乎有些过头了

我正在通读http://www.waveprotocol.org/whitepapers/operational-transform,但我很乐意听到任何解决这个问题的方法。

根据场景的拓扑结构和不同的权衡,实现副本的并发更改有不同的可能性。

使用中央服务器

最常见的场景是所有客户端都必须与之通信的中央服务器。

服务器可以跟踪每个参与者的文档的外观。然后,A和B都向服务器发送一个diff及其更改。然后,服务器将这些更改应用于相应的跟踪文档。然后,它将执行三向合并,并将更改应用于主文档。然后,它将主文档和跟踪文档之间的差异发送给相应的客户端。这称为差分同步。

一种不同的方法被称为操作(al)转换,它类似于传统版本控制系统中的重基础。它不需要一个中央服务器,但如果您有两个以上的参与者,拥有一个会让事情变得更容易(请参阅OT常见问题解答)。要点是转换一次编辑中的更改,以便编辑假设另一次编辑的更改已经发生。例如,A将用结果insert(6, hello)将B的编辑insert(3, hello)变换为其编辑insert(0, abc)

重新定基和OT之间的区别在于,如果您以不同的顺序应用编辑,则重新定基并不能保证一致性(例如,如果B以另一种方式将A的编辑与他们的编辑重新定基,这可能会导致文档状态的差异)。另一方面,OT的承诺是,如果你做了正确的转换,就允许任何订单。

没有中央服务器

存在可以处理对等场景的OT算法(在控制层上增加实现复杂性和增加内存使用之间进行权衡)。版本向量可以用来跟踪编辑所基于的状态,而不是简单的时间戳。然后(取决于OT算法的能力,特别是转换属性2),传入的编辑可以转换为符合接收顺序,或者版本向量可以用于对编辑施加部分顺序——在这种情况下,需要通过撤消和转换编辑来"重写"历史,以便它们遵守版本向量施加的顺序。

最后,有一组基于CRDT的算法,称为WOOT、Treedoc或Logoot,它们试图通过专门设计的数据类型来解决问题,这些数据类型允许操作进行转换,因此它们的应用顺序无关紧要(这类似于每个字符的ID)。这里的权衡是操作构造中的内存消耗和开销。

相关内容

  • 没有找到相关文章

最新更新