在过去的几年里,随着远程办公的迅速普及,在线文档已经成为了人们工作生活中不可或缺的一部分。在线文档允许用户在互联网上实时协作和共享文档,使得办公不再受地理限制,提高了工作效率。在这篇文章中,我们将探讨在线文档的一大关键技术协同算法,本文将从解决方法,优劣对比,重点算法原理等方面进行详细的讲解。
冲突处理解决方案
协同算法是在线文档中实现多人实时协作和共享的关键技术。为了提高工作效率和用户体验,在线文档产品需要采用一种高效的协同算法来解决冲突和处理不同用户之间的编辑冲突,目前我们所掌握的主流冲突解决算法如下所示。
编辑锁
这是一个比较粗暴的解决方案,当协作者1在编辑该段文字的时候,禁止协作者2编辑该文字,直到协作者2释放了编辑锁,实际上这是一种假协同,虽然对于整篇文档来说,它是协同的但是对于段落来说它又是不协同的。这样实现的协同,粒度较大,锁带来的用户体验是非常差的,初代的在线文档也是利用了这个技术实现了粒度比较大的协同。
diff-patch
这个就是基于git的版本管理思想,对内容进行差异对比,合并等操作。这个方案的缺点在于,diff算法的实现难度,以及diff的颗粒度无法保障,对于用户的学习成本很高,一般的用户很难掌握,会给用户带来不好的体验。
最终一致性实现
包括操作转换(OT)、无冲突可复制数据类型(CRDT,称为无冲突可复制数据类型)。OT算法是现有在线文档产品所采用的主流解决方案,当然随着yjs的出现,CRDT也慢慢进入大众视野。虽然OT算法实现上非常复杂,随着原子操作的增多,每次所需要实现的冲突处理将成倍增长,但是其稳定性和颗粒度相比较前面2种来说是优秀的,它能够覆盖大多数操作场景,同时不影响用户体验,是作为协调场景的不二选择。
OT与CRDT
使用场景 | 具体实现 | 不足 | |
---|---|---|---|
OT | 需要中央服务器调度,适用于在线文档 | 需要算法来进行操作转换 | 时间复杂度高 |
CRDT | 适合分布式系统 | 从数据结构层次确保数据一致性 | 空间复杂度高 |
CRDT算法
CRDT全称(Conflict-free Replicated Data Type)中文叫做:无冲突的复制数据类型,从定义上可以看出CRDT是一种数据类型,而非类似OT的算法,其核心思想不在于解决冲突,而是构造一种能够避免冲突的数据类型,可以进行直接合并从而得到文档的内容。
由于CRDT
设计上可以完成对于各个副本的合并与更新而不会产生冲突,那么经由CRDT
实现的算法就可以直接在客户端之间互相传递,相互同步至最终一致性的状态,也就是各个用户之间可以直接P2P
进行数据合并而不需要中央服务器进行调度,由此CRDT
可以很好地支持去中心化的应用,即使没有中心化服务器各端之间也能完成同步。
对于后端比较熟的朋友一定很了解CAP理论,因为这是了解分布式系统的关键基础。CAP
理论指出在一个分布式系统中,最多只能同时满足Consistency
(一致性)、Availability
(可用性)和Partition Tolerance
(分区容错性)中的两项。
数据一致性(C):确保每次获取的数据要么是最新的,要么读取失败,保证数据的正确性。
可用性(A):任何客户端的请求都能得到响应数据,不会出现响应错误。
分区容错性(P):主要是强调系统的稳定性,不会挂掉。
下面是一个经典的银行存钱的例子:
假设在某个时刻t1,原始金额为100,A存入了10块钱,B取了10块钱,C查询还剩下多少钱,如果这个操作同时发生,我们就需要保障数据的一致性,同时都可以完成。
A:T + 10 => op1(T+10)
B:T - 10 => op2(T-10)
C:T => op3(T)
如何保障数据的正确性,此时我们就需要进行合并也就是newStatus = Merge(op1,op2,op3)。为了确保数据的合并正确,通常是加入额外的flag来区分,这也就是CRDT的重点了。
在CRDT传输过程中需要满足:交换律,结合律,幂等律这样才能确保最终数据的一致性。
CRDT的分类
类型 | 基本思路 | 满足条件 |
---|---|---|
基于状态的CRDT(State Based CRDT) | 节点状态和传输状态做Merge | 交换律,结合律,幂等律 |
基于操作的CRDT(Op Based CRDT) | 以Operation的层次进行操作 | 交换律,结合律,幂等律 |
如果想进一步学习CRDT可以参考yjs的源码,后续我也会出一些Yjs的源码分析教程。
OT算法
相比于CRDT,OT就相当的成熟了,同时也非常的简单,目前已经发展出了ot.js, shareDB, ot-json, EasySync等一大批协同算法,可以直接使用。在富文本领域Op最经典的模型有quill的delta模型,这也是现在在线文档领域大部分产品多使用的模型,即通过,retain,insert,delete来完成整个文档的描述与操作,当然还有slate的JSON模型(树型模型)通过insert_text, split_node, remove_text等操作来完成文档的描述。
这里针对在线文档的实现就有2条路线可以走,1是线性结构,2是树型结构,二者的优缺点如下。
数据结构 | 优点 | 缺点 | 可扩展性 |
---|---|---|---|
线性 | 结构简单,所需要的op不多。 | 结构复杂,例如EasySync需要一定的理论基础才能理解数据组合。 | 可扩展性堪忧,如果不适合office级别的复杂文档,插件,视频这类元素在整个结构上描述起来会非常的麻烦,而且很容易出现BUG。 |
树形 | 结构清晰,很容易让人理解,一眼就可以掌握其数据组织。 | 结构复杂,需要更多的op组合,比如节点内的op,节点与节点之间的op。 | 可以描述更多的元素,可以以子树的形式进行无限扩展。 |
OT算法同样是需要保证最终一致性的,所以也需要满足:交换律,结合律,幂等律。通常情况下我们是使用的双边OT,简单来说就是:AB == A
B,这里我们可以用一个菱形图来描述:
接下来,我会给一个更简单的例子来帮助大家理解OT的核心思想 ⬇️
OT 算法可以解决一致性问题,我们来看看 OT 到底做了什么。
同样,原始内容是 “12”。
insert(2, A)
,这个操作计作 OA;insert(2, B)
,这个操作计作 OB;transform(OA, OB)
,得到修正后的操作 insert(3, B)
,记为 OB',相比 OB,它将插入位置从 2 修正为 3,于是 "12A" 变成了 “12AB”;transform(OA, OB)
,得到修正操作 insert(2, A)
,记为 O1',让内容从 "12A" 变成 “12AB”。transform 方法会同时产生 OA' 和 OB'。我相信通过上述例子,应该可以很好的理解了OT的原理,今天的水文就写到这里,下期会更新关于EasySync和Yjs的源码分析文档。
参考资料
www.cnblogs.com/WindrunnerM…
www.cnblogs.com/WindrunnerM…
cloud.tencent.com/developer/a…