阅读 833

协同编辑场景的基础分析及方案设计

Github Blog: 协同编辑场景的基础分析及方案设计

背景

最近笔者在做一款具有协同编辑功能的思维导图时。在实现协同编辑的过程中对这一场景中笔者有了一些自己的理解,于是便在这里抛砖引玉。

协同编辑是什么:

协同编辑,即多个操作端同时对一个对象进行操作。在协同编辑的过程中,有俩个首要的性能指标,一致性与实时性。二者相互制约,不同的场景有不同方案。

协同编辑中最容易出现的问题就是由于多个操作端状态不一致导致的操作冲突

所以,如何解决具体某个具体模式协同编辑中的冲突,首先的就是了解该事物的状态与操作

“状态”与“操作”:

状态,即事物表现出来的状况与形态。操作,这里我把操作定义为改变状态的动作。

在一次操作改变状态的过程中,有以下四个要素:

1、原始状态

2、触发操作的条件

3、操作

4、新的状态

以上述过程为例,完整即为馒头的初始状态,触发操作的条件为饥饿,吃为该操作的具体动作,而半个则是馒头在经历这次操作后的后续状态。

所以为了确保协同编辑的一致性,我们就要 确保每一次事件发生在不同端时,四要素一致

设计目标

不同的业务场景下,我们往往对一项技术的性能要求也不同。

在思维导图协同编辑的过程中,我们 重一致性,轻实时性

设计方案

在明确重一致,轻实时这一目标后。我们就对目标进行解构。

所谓的重一致性,经前文分析,也就是说要 确保事件发生的四要素一致

在设计一款需要重一致的产品时,我们可以先抛出一个问题。如何设计一款绝对一致的产品?在得到方案后,我们对其中要求过高的条件进行弱化,就可以得到一个重一致,但不偏执的方案。

(1)设计一套适用于大多数协同编辑的只考虑可靠性的理论方案:

我们从四要素出发:

1、原始状态

2、触发操作的条件

3、操作

4、新的状态

其中 触发操作的条件 是最容易把持一致的,只要我们在各个端采用相同的逻辑片段判断即可。所以我们把重点放在1, 2 , 4。

以下是一套确保某个单一操作在各个端都可以按预期执行的理论方案,该方案不考虑性能等问题,单纯的为确保四要素一致而构想。

该方案主要利用ACK机制来确保要素一直:

通过以上流程,我们可以确保某一次操作是强一致性的。

(2)删繁就简得出一套可工程化的方案:

根据上文设想,当我们设计出一套理想模型时,我们可以通过对其中过于苛刻的条件进行弱化,从而得到一个可工程化的方案。

那么首先要进行的就是对方案本身进行解构,从而了解那些部分是不适合工程化的。

下面以时间发生的顺序我们依次分析:

步骤1:用户想要执行一个操作C,去修改A对象(客户端操作,无风险)

步骤2:发送操作申请,及操作对象A的标识(socket消息,可工程化,低风险)

步骤3:在服务器上建立A对象临时控制区(在服务器上开辟内存,可能导致内存暂用过高或内存泄露,高风险)

步骤4, 5, 6 :上传所有协同端的A对象状态至服务器(等待所有对象的上传需要耗费大量时间,高风险)

步骤7:对比所有端的A对象状态(对比操作可能会消耗大量服务器资源,高风险)

分支1:

​ 步骤8:广播操作C (socket消息,可工程化,低风险)

​ 步骤9:各客户端执行操作C(客户端操作,无风险)

​ 步骤10:上传操作后所有协同端的A对象至服务器(等待所有对象的上传需要耗费大量时间,高风险)

​ 步骤11:对比所有端的A对象(对比操作可能会消耗大量服务器资源,高风险)

​ 分支1-1:

​ 步骤12:允许下次操作(本身无风险,但这意味着在这次操作之前不允许其他任何操作,高风险)

​ 分支1-2:

​ 步骤12:通知撤回A对象状态(客户端操作,但需设计操作的撤销事件,低风险)

分支2:

步骤8:合并所有A对象的状态 (需要设计一套合并方法,低风险)

步骤9:驳回此次动作申请 (socket消息,可工程化,低风险)

步骤10:客户端拉取最新状态并覆盖对象A状态(客户端操作,无风险)

经上分析,我们可以看到该方案不适合工程化的主要俩点:

1、在服务器开辟内存空间,及对比文件带来的服务器资源消耗问题

2、等待各端上传文件造成的时间消耗问题(会极大降低协同的可用性)

深入剖析,以上俩点主要是因为方案把确认要素一致的动作交给服务器端执行,而各个客户端近乎只承担了执行动作的功能。

针对以上问题有俩种角度:

1、部分确认机制是否必要,是否可以进行简化

2、是否可将部分逻辑转移至客户端执行,或简化

角度1的思路:

整个链条中确认了俩次状态一致,如果有任意一次不同都会取消这次操作及其影响。

1、执行前状态(要素1)

2、执行后状态(要素4)

理论上,在一个 output = ƒ(input) 的程序中,当给定输入一定是,输出也是一定的。所以要素4的确认机制在某些场景可以舍去。

角度2的思路:

目前在服务端执行的主要职责有:

1、消息传递

2、文件暂存

3、文件对比及合并

职责1不建议,也不适合放在客户端。职责2本质上是职责3可以执行的前提。

职责3:文件对比是为了确认要素,即便把这一步骤放在客户端执行。在不考虑端对端通信的前提下,同样需要借助服务器进行文件存储。

服务器进行文件合并的初衷:扮演第三方(除协同者外)的角色确定文件的最终形态。

这一步,在俩种场景下可以发生变化。

1、状态本身无法合并(比如思维导图中一个节点在不同端绑定了俩份完全无关的笔记,笔记之间无法合并)

2、通过某种机制避免产生冲突,极端情况发生时采用覆盖策略。(一般意味着某种形式的操作加锁,也是市面上最常见的协同策略)

如果在这一步可以处理不用合并而只是简单的对比,可将上传对象所有状态转为,通过socket上报状态的md5或哈希值,可以大幅的降低时间延迟。

上传状态md5从本质上来说是把对比这个职责进行分工,客户端负责特征值提取,服务器进行简单对比。

优化后的流程