Google文档如何处理编辑冲突?

时间:2015-06-27 19:25:00

标签: algorithm google-docs editing operational-transform collaborative-editing

我一直在编写自己的Javascript编辑器,功能类似于Google Docs(允许多人同时处理它)。有一点我不明白:

让我们假设您已经将用户A和用户B直接相互连接,网络延迟为10毫秒。我假设编辑器使用差异系统(据我理解文档所做),其中编辑表示为"插入'文本'在索引3,"并且差异被加上时间戳并被迫按时间顺序应用于所有客户。

让我们从包含文字的文件开始:" xyz123"

用户A类型" abc"在文档的开头,在时间戳001ms,而用户B键入"你好"介于" xyz"和" 123"在时间戳005ms。

两个用户都希望结果是:" abcxyzhello123,"但是,考虑到网络延迟:

  • 用户B将收到用户A编辑的"插入' abc'在索引0"在时间011ms。为了保持按时间顺序排列,用户B将撤消用户B在索引3处的插入,插入用户A" abc"在索引0处,然后在索引3处重新插入用户B的插入,该索引现在位于" abc"和" xyz,"从而给予" abchelloxyz123"
  • 用户A将收到用户B对"插入'你好'的编辑。在索引3"在时间015ms。它会认识到用户B的插入是在用户A之后完成的,只需插入"你好"在索引3(现在介于" abc"和" xyz")之间,给出" abchelloxyz123"

当然," abchello xyz123"与" abc xyz 你好 123"

不一样

除了字面上为每个角色分配自己的唯一ID之外,我无法想象Google将如何有效地解决这个问题。

我想到的一些可能性:

  • 跟踪插入点而不是使用差异发送索引会起作用,但如果用户B在编辑前1ms移动了插入点,则会出现完全相同的问题。
  • 您可以让用户B使用他的差异发送一些信息,例如"插入' xyz'"这样用户A就可以智能地识别出这种情况已经发生,但是如果用户A插入文本" xyz会怎么样?"
  • 用户B可以识别出这种情况发生了(当它收到用户A的差异并发现它发生冲突时),然后发出一个差异撤消用户B的编辑和一个新的差异插入用户B"你好" " abc"。长度指数更右。这个问题是(1)用户A会看到"跳跃"在文本和(2)如果用户A继续编辑,那么用户B将不得不不断修复其差异 - 甚至是"修复者"差异将关闭,需要修复,指数增加复杂性。
  • 用户B可以发送一个属性,即它收到的最后一个时间戳差异是-005ms或者其他东西,然后A可以识别出B不知道它的变化(因为A'差异是在001ms)然后解决冲突。问题是(1)所有用户的时间戳都会稍微偏离,因为大多数计算机时钟都不准确到ms和(2)如果第三个用户C与用户A有25ms的滞后但是2ms用户B延迟,用户C在" x"之间添加一些文字。和" y"在-003ms,然后用户B将使用用户C的编辑作为参考点,但是用户A不知道用户C的编辑(以及用户B的参考点)直到22ms。我相信如果您使用通用服务器为所有编辑加时间戳,这可以解决,但这似乎相当复杂。
  • 您可以为每个角色提供一个唯一的ID,然后使用这些ID而不是索引,但这似乎有点过分......

我通过http://www.waveprotocol.org/whitepapers/operational-transform阅读,但很想听到解决此问题的所有方法。

1 个答案:

答案 0 :(得分:25)

实现并发更改副本有不同的可能性,具体取决于场景的拓扑结构和不同的权衡。

使用中央服务器

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

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

另一种方法称为操作(al)转换,类似于传统版本控制系统中的rebase。它不需要中央服务器,但是如果你有超过2个参与者,那么让它变得更容易(参见OT FAQ)。要点是您在一次编辑中转换更改,以便编辑假定已经发生了另一次编辑的更改。例如。 A会根据结果insert(3, hello)对其编辑insert(0, abc)转换B的修改insert(6, hello)

变基和OT之间的区别在于,如果您在不同的顺序中应用编辑,则变基不能保证一致性(例如,如果B反向重新编译A的编辑,则可能导致文档状态不同)。另一方面,OT的承诺是允许任何顺序,如果你做了正确的转换。

没有中央服务器

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

最后,有一组称为CRDT,WOOT,treedoc和logoot的算法,它们试图通过特殊设计的数据类型来解决问题,这些数据类型允许操作通勤,因此它们的应用顺序无关紧要(这类似于你对每个角色的ID的想法)。这里的权衡是内存消耗和运营构建的开销。