避免“工作撞车”——多人协同编辑引擎在基于AST的低代码平台上的设计实践
1. 背景:低代码开发的协同之痛
当下,低代码开发平台凭借其可视化构建与快速交付的能力,日益成为企业提升效率、降低开发门槛的核心工具。然而,目前市面上的大多数低代码平台都忽视了多人协同编辑这一关键能力,这已成为制约团队效能的突出短板。
“工作撞车”带来的协作之困
在缺乏实时协同机制的环境中,开发者常常陷入“工作撞车”的尴尬局面:精心配置半天的组件,可能因同事的无意间的覆盖操作而在瞬间清零。这种频繁发生的版本冲突与数据覆盖,不仅带来极大的挫败感,更显著增加了团队沟通与版本管理成本。
受限的“流水线”协作模式
跨职能协同已成为现代应用开发的常态。从产品、设计到开发、测试及业务人员,往往需在同一平台上紧密配合才能高效完成应用交付。然而,目前大多数低代码平台仍依赖于传统的“文件锁–上传–合并”机制规避冲突,无法实现真正意义上的实时协同。这种类似于“流水线轮流作业”的协作模式,严重制约了团队并行输出的能力,已成为提升交付效率的主要瓶颈。
用户期待被重新定义的协同体验
如今用户已被 Google Docs、腾讯文档、Figma、Notion 等优秀协同工具“教育”并“惯坏”,他们对协同操作的体验预期已被提升至前所未有的高度。
用户期待的低代码协同体验应包括:
- 零延迟的实时响应:期望同事的操作能够实时、可视化地呈现在同一画布上,如同置身同一工作空间。任何需要手动保存、刷新或解决冲突的流程,都被视为体验的倒退。
- 无声的劳动成果保护:协同的核心价值之一是彻底杜绝“工作撞车”。借助细粒度的操作日志与精准的协同算法,系统应在幕后默默守护每一位开发者的劳动成果。
- 增强团队感知的能力:实时显示何人在线、何人正在编辑何组件、光标位置等信息,可显著增强团队的空间共在感,减少重复操作与盲目协作,提升协同过程的透明与有序性。
这些来自消费级协同工具的优秀体验重塑了用户的使用习惯,正不断推动低代码平台由传统的 “开发效率工具” 向真正的 “团队协作平台” 演进,也使高质量的多人在线协同从“加分项”转变为“必选项”。
如下图所示,是我们在基于AST的低代码项目中的协同实现效果:
在此前的文章中,我们曾深入介绍过基于 AST 的低代码平台的整体架构与实现原理。与传统低代码平台普遍基于私有 DSL 的方式不同,该平台依托源码级的 AST 驱动引擎,不依赖任何私有 DSL 或封闭协议,真正实现了“源码进、源码出”,具备实时出码与多场景源码适配能力。这种架构在为开发者提供高度灵活性和透明度的同时,也对多人实时协同提出了更高要求——如何在不破坏 AST 结构一致性的前提下,实现操作的同步与合并成为关键挑战。
本文将系统性地介绍我们如何在这样的技术基底上,构建一套兼顾实时性、一致性与丰富功能的多人协同操作引擎。从技术选型、架构设计到核心算法实现,分享我们在此过程中的探索路径与最终的成功实践。
2. 协同算法选型:为什么是CRDT?
2.1 协同编辑的核心挑战:实时同步与冲突处理
实现协同编辑需解决两大核心问题:实时同步与冲突处理。很多人会联想到《王者荣耀》等 MOBA 游戏中使用的帧同步技术,但游戏同步是强制单时间线,而协同编辑允许多时间线并行,还需支持“回滚”等高阶操作,因此复杂度显著更高,具体挑战体现在:
- 操作时序问题:例如因索引变动引发的脏路径问题;
- 并发冲突:多用户同时修改导致的写入冲突;
- 撤销/重做的逆操作复杂性:Undo/Redo 需在分布式环境中保持语义一致性。
2.2 主流协同方案对比概览
当下协同的主流解决方案包括以下三类:
| 方案 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| ❌ 悲观锁 | 同一时间只允许一位用户编辑 | 实现简单,保证强一致性 | 严重限制协作流畅性 |
| ❌ 自动合并 | 如Git机制 | 适用于异步场景,体验较好 | 对结构化数据冲突处理能力弱,合并复杂 |
| ✅ 协同算法 | 基于数学理论实现实时一致性 | 支持实时协同,保留操作意图 | 实现复杂,高度依赖数据结构 |
2.3 协同算法对比:OT vs CRDT
分布式系统一致性核心算法主要包括 OT(Operational Transformation) 与 CRDT(Conflict-free Replicated Data Type)。OT 是1989年提出的经典算法,而CRDT在2011年被系统化总结,代表了更现代的、天生去中心化的协同理论。
OT(操作转换)
- 原理:通过转换操作顺序,使各副本最终状态一致
- 特点:依赖中心服务器进行转换计算,适用于文本等有序序列
- demo:可以在这个站点中直观地操作体验:https://operational-transformation.github.io/
CRDT(无冲突复制数据类型)
- 原理:通过数学建模使操作满足交换律、结合律和幂等律,天然避免冲突。
- 特点:天生去中心化,支持离线编辑,自动解决冲突。
- demo:CRDT 分为基于状态和基于操作的CRDT,基于状态的CRDT可以在这个站点中交互式学习:https://lars.hupel.info/topics/crdt/01-intro/
核心差异对比
| 特性 | OT (Operational Transformation) | CRDT (Conflict-free Replicated Data Type) |
|---|---|---|
| 提出时间 | 1989年 | 2011年(理论体系化) |
| 哲学 | 避免冲突(Avoidance) | 容忍冲突(Tolerance) |
| 架构 | 中心化,依赖服务器 | 去中心化,服务器仅中继 |
| 复杂度 | 算法复杂,转换规则难设计 | 数据结构复杂,设计门槛高 |
| 网络要求 | 需要稳定连接,对服务器性能要求高 | 支持离线编辑,恢复连接后自动同步 |
| 适用场景 | 文本协同 | 复杂结构、去中心化应用、物联网、边缘计算等 |
2.4 主流应用技术方案调研
2.4.1 早期的腾讯文档slide:基于JSON Patch的单边OT方案
基于 JSON Patch 实现客户端冲突处理。Server 仅接受同一版本下的一个 ChangeSet,冲突由后提交的客户端单边处理。
客户端将PPT文档的XML数据转换为JSON格式进行操作,通过差分算法生成 ChangeSet 数据差集。例如,当客户端A、B基于同一版本v100生成 ChangeSet 时,若B的请求先到达,服务端将版本更新为v101并广播通知;A提交时因版本过期被拒绝,需在本地解决冲突后重新生成v101'提交。
该方案基于 JSON Patch 标准(定义了add、delete、replace、move、copy、test六种操作类型),仅同步差异内容而非全量数据,高效支持协同编辑、撤销/重做等操作,详见:https://jsonpatch.com/ 。
2.4.2 腾讯文档3.0:升级双边OT与原子化指令集
腾讯文档3.0升级为双边OT架构,采用 Mutation 原子指令集描述变更,服务端与客户端共同通过转换函数(T-function)实现高效冲突处理与离线支持。
数据模型升级:
-
Doc:一维数据流,结合文本流、属性流与多线段树,实现 O(log n) 操作;
-
Sheet:二维表格,使用分块稀疏矩阵与懒加载,支持海量数据高效存取;
-
Slide:树形结构,通过 Map 与虚拟树结合,实现 O(1) 访问与结构维护。
核心改进:
- 冲突处理由单边升级为双边OT,变更描述从 JSON Patch 转为细粒度 Mutation 指令;
- 所有文档仅存一份原子数据,无冗余版本存储,附件独立管理;
- 版本迭代依赖协同数据描述,合成性能高。
冲突解决依托为各类 Mutation 开发的转换函数,例如状态 X 下操作 O₁、O₂ 满足:
X ← O₁ ← T(O₂, O₁) ≡ X ← O₂ ← T(O₁, O₂)
使得前后端可自主实现操作还原、序列化与冲突解决,离线编辑亦不受阻塞。
2.4.3 Figma:面向设计协作的中心化CRDT实践
Figma 未采用 OT 方案,主要因其在离线缓存等场景下易引发操作组合爆炸,复杂度高。虽然 CRDT 设计初衷为去中心化系统,会带来一定性能与内存开销,但 Figma 仍借鉴其思想,构建了中心化 CRDT 协同系统。
该系统将设计文件抽象为图形树,以对象属性为最小操作单元。
冲突处理采用 LWW(最后写入获胜) 策略:
- 修改同一对象的不同属性无冲突;
- 并发修改同一属性时,最后提交至服务端的值覆盖之前的值。
例如某文本属性值为“B”,用户A改为“AB”,用户B改为“BC”,最终结果为“AB”或“BC”,取决于谁的修改最后送达服务端。此机制与多数协同表格应用一致,仅富文本协同需合并结果。
2.5 选择 CRDT 作为 AST 低代码协同算法
基于 AST 的低代码平台需处理结构化数据协同,其复杂性要求算法具备以下能力:
- 解决复杂冲突:悲观锁和 Git 式合并无法满足实时结构化冲突处理;
- 保证实时性:需实现毫秒级操作同步,要求高性能网络通信;
- 支持多元状态同步:需同步数据模型、意识感知、操作历史等多维状态。
基于以下优势,CRDT成为理想选择:
- 数据结构契合:AST 的树形操作与 CRDT 状态模型高度契合;
- 去中心化扩展:支持多时间线、离线编辑,降低服务端压力;
- 数学保证:通过数学定律保证最终一致性,无需复杂转换计算。
CRDT 在处理非线性数据结构时展现出显著优势,成为 AST 低代码平台实现实时协同的首选方案。
3. 协同算法与数据结构设计
3.1 选择基于操作的 CRDT 模式
在 CRDT 的两种实现模式中,选择 基于操作的模式(Op-based CRDT),主要基于以下考量:
• 基于状态(State-based)CRDT 的局限性
节点间需同步完整状态(如整个集合、计数器)。当 AST 结构复杂、文档体量较大时,完整状态的传输将占用大量带宽,同步延迟高,因此在实际生产环境中较少采用。
• 基于操作(Op-based)CRDT 的优势
仅需同步增量操作指令(如"创建组件"、"更新属性"),传输数据量极少。其核心设计要求是:所有操作必须满足交换律(Commutative),即改变操作顺序不影响最终结果。
基于操作的模式天然适合 AST 结构:操作粒度细、传输效率高,支持离线编辑与自动冲突解决,为低代码平台提供了高效可靠的协同基础。
为什么需要交换律?
由于网络延迟与不确定性,各副本接收操作的顺序可能不同。交换律保证了无论顺序如何,最终状态一致。这类似于数学中的加法:a + b 与 b + a 的结果相同。
3.2 核心设计思想:将 UI 操作转化为满足交换律的原子操作并作为CRDT同步数据
在低代码平台的协同设计中,传统方案通常基于私有 Schema 协议,可通过 JSON Patch 等方式操作 JSON 数据还原用户操作。然而,在基于 AST 的低代码平台中,用户在画布中的交互(如拖拽、属性配置)涉及更复杂的结构化操作,直接使用 CRDT 原语难以完整描述。因此,我们需要将 UI 操作解构为一组定义良好的原子操作(Mutation),并使其满足交换律,作为 CRDT 同步的基本单元。
CRDT 特别适合此类场景,原因如下:
- 基于 ID 而非索引定位节点:各 AST 节点具备全局唯一 ID(如本项目中每个节点含有全局唯一的
tid),避免因数组索引变动引发脏路径问题。 - 天然面向树结构操作:AST 协同关注节点增、删、移及属性修改,更契合 CRDT 处理非线性数据的能力。
- 操作具备分区特性:多数 UI 操作作用于树的不同分支,天然支持操作交换。
- 支持离线编辑:CRDT 的去中心化特性支持本地操作堆积,网络恢复后自动同步。
- 简化服务端设计:服务端无需复杂操作转换,仅需可靠中继消息,架构更健壮。
因此,CRDT所同步的数据并非完整的AST树状态,而是细粒度的操作指令流。通过预定义原子操作并确保其满足交换律,我们可在分布式环境下实现操作序列的可靠同步与合并。对于无法规避的冲突(如并发修改同一属性),则采用附加元数据(时间戳、客户端 ID)的 LWW(最后写入获胜)策略解决,最终在各客户端重建出一致的 AST 状态。
最终,定义了以下 50 个原子操作,覆盖了低代码平台的各种交互场景:
| 类别 | 操作示例 |
|---|---|
| 视图操作 | AddRoute, AddFragment, AddModal, UpdatePage, UpdateFragment, UpdateModal, RemovePage, RemoveFragment, RemoveModal, RemoveViewFiles, UpdatePageSize |
| 视图节点相关操作 | DropNode, InsertNode, InsertBeforeNode, InsertAfterNode, UpdateAttribute, UpdateAttributes, ReplaceNode, RemoveNode, PasteNode, CloneNode |
| 依赖操作 | UpdateDependency, RemoveDependency |
| store变量处理 | AddStoreFile, RemoveStoreFile, AddStoreState, R |
| function函数处理 | AddGlobalFunctionFile, RemoveGlobalFunctionFile, AddGlobalFunction, UpdateGlobalFunction, RemoveGlobalFunction |
| service服务函数处理 | AddServiceFile, AddServiceFunction, AddServiceFunctions, UpdateServiceFunction, RemoveServiceFunction, UpdateServiceBaseConfig |
| 插件处理 | AddPlugin, RemovePlugin, UpdatePluginParams, UpdatePluginState |
| 配置文件操作 | UpdateConfigFiled, GroupLayers, UngroupLayers |
| 文件操作 | AddFile, RemoveFile, RenameFile, UpdateCode |
这些原子操作共同构成了协同编辑的指令集,确保了操作的可序列化、可同步和最终一致性。
3.3 交换律(Commutative Property):顺序无关性与最终一致
AST 操作天然可分区,大部分UI操作(修改不同组件的属性、在不同区域添加节点)本身就是针对树的不同分支,因此天然具有可交换性 A ⊕ B = B ⊕ A。
例如,两个用户同时操作一颗AST树,用户A更新button2组件的属性,用户B在根节点下创建button3组件:
- 用户 A:
UpdateProperty(id: "button2", property: "text", value: "Close") - 用户 B:
CreateComponent(parentId: "Page", component: { id: "button3", ... }, ...)
无论操作依何种顺序执行,因两者通过 ID 定位、修改树的不同部分,最终 AST 状态一致。交换律保障了操作顺序不影响结果。
3.4. 结合律 (Associative Property) 与幂等律 (Idempotent Property) 的辅助作用
虽然基于操作的 CRDT 仅强制要求交换律,但结合律和幂等律能提供额外的健壮性保障:
结合律使系统能更灵活地处理批量到达的独立操作,操作分组合并不受顺序影响 (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)。
在 AST 协同中,结合律适用于修改不同对象或属性的独立操作。例如:
- 操作A:
UpdateProperty(id: "comp1", key: "color", value: "red") - 操作B:
UpdateProperty(id: "comp2", key: "text", value: "Hello") - 操作C:
UpdateProperty(id: "comp3", key: "visible", value: false)
无论按 (A⊕B)⊕C或 A⊕(B⊕C)的顺序合并,最终 AST 状态一致。结合律使系统能灵活处理批量到达的独立操作。
幂等律能有效应对网络重复消息、客户端重试等异常场景,同一操作多次应用效果与一次相同 A ⊕ A = A。
例如重复执行 UpdateProperty(id: "button2", key: "color", value: "blue"),无论执行次数,按钮颜色终为蓝色。再如重复执行 CreateComponent(parentId: "Page", component: { id: "button3", ... }, ...) 由于 id 的唯一性,导致重复 id 的组件插入失败。
幂等律能有效应对网络重复消息、客户端重试等异常场景,提升系统鲁棒性。
3.5 冲突解决策略
在基于操作的 CRDT 系统中,针对无法通过交换律解决的冲突(如并发修改同一属性),采用以下策略收敛:
• 最后写入获胜(LWW - Last Write Wins)
为每个操作分配全局唯一且单调递增的时间戳(或逻辑时钟)。当检测到针对同一属性的并发修改时,系统自动选择时间戳最大的操作作为最终结果。这是实现最简单、应用最广泛的冲突解决策略。
• 确定性操作转换
对于某些特定类型的操作,可以预定义转换规则。例如,当两个用户同时对同一数值属性进行"增加"和"减少"操作时,系统可以按照确定性的数学规则合并这些操作,而不是简单地选择最后写入的值。
• 操作依赖关系管理
通过维护操作间的依赖关系,确保存在逻辑顺序约束的操作能够被正确执行。例如,针对某个组件的删除操作应该优先于针对该组件的属性修改操作。
在基于 AST 且有中心服务的场景中,LWW 因其实现简单、决策明确成为最合适的选择,能够有效解决大多数属性级别的冲突情况。
3.6 协同保障机制
设想一个符合逻辑的典型场景:多个操作乱序到达,包含重复传输,且存在需要中心化仲裁的属性冲突:
- 操作A:
CreateComponent(parent: "Form", id: "Input1", type: "InputField")在表单中创建一个输入框 - 操作B:
UpdateProperty(id: "SubmitBtn", key: "color", value: "blue", timestamp: 1001)将按钮颜色改为blue(时间戳1001) - 操作C:
UpdateProperty(id: "SubmitBtn", key: "color", value: "green", timestamp: 1002)将同一按钮改为green(时间戳1002) - 操作A': 操作A的重复消息
假设操作到达顺序为:B → C → A' → A,系统的处理流程如下:
- 交换律保障操作B、C可乱序执行
- LWW策略比较时间戳(1002>1001),采纳操作C的绿色
- 幂等律过滤重复创建操作A'
- 最终状态:按钮为绿色,组件创建一次
交换律确保了基础操作的可靠性,而LWW策略则提供了冲突的仲裁方案,二者结合为平台提供了完整的协同解决方案。
4. 系统架构与核心引擎设计
4.1 总体架构概述:三层协同模型
为满足低代码平台复杂场景下的实时协同需求,设计了清晰的三层架构,在保证功能完备性的同时,兼顾了性能与可扩展性:
- 客户端 (Client-Side): 客户端画布作为交互与计算的核心,负责捕获UI操作、将其转换为AST原子操作、在本地CRDT文档(Y.Doc)中进行状态管理,并通过WebSocket连接实现与服务端及其他客户端的实时同步。同时,集成Awareness协议以同步用户光标、选区等元信息。
- WebSocket同步服务 (WS-Sync Service): 作为轻量化的信令中继,其核心职责是维护协作房间(Room)、广播来自各客户端的操作更新与感知状态。该层被设计为无状态服务,不处理任何业务逻辑或冲突解决,从而实现了高可用性与水平扩展能力。
- Git版本管理服务 (Git Version Service): 作为系统的持久化层与唯一真相源(Source of Truth),负责存储完整的版本快照(Commit)、管理版本历史图谱,并提供精确的版本拉取与回退能力,为操作日志(OpLog)提供可靠的基准点。
4.2 技术选型:Yjs与YATA模型
在众多 CRDT 实现中,Yjs 以其成熟性和完整性脱颖而出。它是一个高性能的 CRDT 库,通过共享类型(Shared Types)(如 Y.Array、Y.Map)抽象数据结构,使得任何变更都能自动、无冲突地同步到所有对等节点。
Yjs 具备以下核心特性与优势:
- 网络无关性:不依赖网络类型,天然支持离线编辑、版本快照与撤销/重做;
- 扩展性强:可高效支持大规模协同,即便处理大型文档也表现优异;
- 模块化设计:其共享类型与网络传输、数据持久化模块解耦,支持灵活集成与定制。
在 AST 低代码项目中选择 Yjs 作为协同框架,主要基于以下原因:
- 模型匹配:内置 YATA 冲突处理模型非常适用于 AST 树等半结构化数据操作,后面会继续介绍;
- 开箱即用:提供完善的数据类型(如
Y.Array、Y.Map)、高效的二进制编码及 WebSocket 集成,显著降低开发复杂度; - 生态成熟:拥有丰富的社区实践与生产案例,提供可靠的稳定性保障。
4.3 原子操作设计:精准映射源码变更
前面已经提到,系统将复杂的协同操作收敛为50种定义良好的原子操作(Mutation),用于描述对项目源码的所有变更。这些操作通过有限集合的排列组合,可精确还原任意操作后的源码状态。原子操作的设计遵循以下原则:
- 原子性:每个操作对应AST树的最小变更单元,确保操作粒度精细、语义明确。
- 无副作用:操作保持独立性与幂等性,可基于同一初始状态无限次重放并得到相同结果。
- 统一序列化:所有操作以
IOperateData格式持久化于Y.Array中,作为协同唯一数据源。本地操作通过addOpData方法序列化并 push 至该数组,由Yjs负责同步,利用数组有序性保证各客户端日志一致。 - 版本锚点:特定操作(如用户保存)生成携带Git Commit ID的
commit记录,作为版本恢复与回溯的基准。 - 数据精简:结合组件原型(prototype)等信息优化数据设计,仅记录变更内容,减少协同数据体积。
4.4 混合持久化策略:OpLog + Snapshot 模式与Git集成
为解决CRDT操作日志无限增长带来的性能瓶颈,系统采用操作日志(OpLog)与版本快照(Snapshot)相结合的混合持久化策略,并与Git版本管理系统深度集成:
操作日志(OpLog)
- 所有原子操作持久化于CRDT文档的
Y.Array中,形成有序操作流水,用于实时协同与增量同步 - 所有客户端同步保持完全一致的日志记录
- 通过定期清理策略控制容量:在执行保存操作时自动清理远古记录,仅保留最近N个版本点之间的操作
版本快照(Snapshot)
- 定期(如手动保存)将当前AST状态对应的完整源码提交至Git仓库,生成版本快照(Git Commit)
- 提交时在OpLog中插入
type: commit记录作为检查点 - 作为可靠的恢复基点,在冲突处理或版本回滚时用于重放(Replay)后续操作
版本管理机制
系统以Git Commit ID作为数据一致性的黄金标准,实现精确的版本控制:
- 版本序列表现为:
[V0]-[op1]-[op2]-[v1]-[op1]-[op2]-[v2]... - OpLog本质是记录快照点之间的操作流水
- 每个保存操作与Git Commit ID强绑定,支持精准版本回退(Revert)与状态追溯(Revert+Replay)
- 自动根据当前版本包含的OpLog生成语义化的commit message,便于版本历史查阅
协同恢复流程
新客户端加入或进行版本回退时执行:
- 拉取指定Git版本快照(完整源码)作为基准状态
- 同步协同服务中的操作记录
- 按序重放(Replay)该版本之后的OpLog操作流水,快速同步至最新状态
4.5 冲突处理:基于双向链表的YATA模型与墓碑机制
系统采用Yjs库内置的YATA (Yet Another Transformation Approach) 冲突处理模型。该模型基于双向链表数据结构,将操作抽象为“插入”与“删除”两种类型,并通过以下核心机制确保所有客户端最终状态一致:
- 插入冲突处理:逻辑时间戳排序,Yjs为每个操作分配全局单调递增的逻辑时间戳。当发生插入冲突时,模型根据时间戳决定节点在底层链表中的最终顺序,本质上是一种最后写入获胜(LWW) 策略,确保操作在全网范围内具有确定的顺序。
- 删除冲突处理:墓碑标记机制 (Tombstone),对于删除操作,Yjs采用逻辑删除方案:并非立即从链表中物理移除节点,而是将其标记为已删除(
item.deleted = true)并记录到删除集合(DeleteSet)中。此机制确保了后续可能到达的、针对该节点的操作仍能正确定位到目标,从而彻底避免因节点缺失而导致的数据不一致性问题。
性能考量与优化:
- 该模型的主要权衡(Trade-off)在于,CRDT数据结构需要维护额外的元数据(如唯一ID、时间戳、删除标记),在数据量庞大或结构复杂的场景下会带来一定的内存开销。
- 实践中,Yjs通过结构共享、延迟初始化、节点合并等优化技术,有效控制了内存占用与计算开销,使其性能足以应对绝大多数协同编辑场景。经验表明,CRDT协同的性能瓶颈通常可通过针对性优化得以解决。
4.6 操作同步与状态感知
系统通过 Yjs 的 WebSocket 同步服务实现高效的状态同步,服务端作为无状态中间层,其核心职责包括:
- 房间管理:为每个协同文档创建独立房间(Room),管理客户端连接与消息路由。
- 消息广播:将接收到的操作更新与感知状态实时广播至房间内所有其他客户端。
- 状态同步:确保所有客户端的 AST 数据与感知状态最终一致。
AST 操作同步与 Delta 数据处理
系统利用 Delta 格式(一种基于 OT 算法设计的用于描述序列化数据结构变化的标准化格式,详见 Quill Delta https://quilljs.com/docs/delta/)来精确描述和传递操作变化。在 observe 回调中,通过解析 event.changes.delta 来识别多种操作场景,例如:
-
从指定版本重做历史记录
-
处理客户端保存操作
-
处理操作追加、插入及并发冲突整理
-
执行回滚操作或历史记录清理
-
……
系统据此采取相应的同步策略(如版本回退+强制重做),并驱动UI更新。
Awareness 状态同步
除AST数据同步外,为提升多用户协作体验,系统基于 Yjs Awareness 协议实现了低延迟的元状态同步:
- 功能范围:实时同步用户光标位置、选中组件、在线状态及当前页面等非AST元数据。
- 实现机制:各客户端通过 Yjs 提供的 Awareness API 广播自身状态并接收他人状态,最终在UI层实时渲染用户光标、选中态等视觉反馈。
4.7 协同场景下的撤销与重做设计
在多人协同编辑环境中,实现撤销(Undo)与重做(Redo)功能面临核心挑战:传统单机撤销模型通过回退全局操作队列实现状态还原,这会直接影响所有协作者,导致其他用户的操作被意外覆盖。
为解决这一难题,系统基于 Yjs UndoManager 构建了客户端粒度的撤销重做机制,其核心设计原则为:仅回退当前客户端自身发起的操作,避免干扰其他协作者。
具体实现机制:
- 操作溯源(Origin Tracking):利用 Y.UndoManager 的
trackedOrigins选项为操作标记来源(使用客户端ID而非用户ID,以区分同一用户的多终端会话),精准隔离不同客户端的操作序列。 - 独立操作栈管理:为每个客户端维护独立的撤销/重做栈,执行时不处理其他客户端的操作条目。
- 状态一致性保障:执行撤销/重做后,系统基于最近版本快照重放后续操作流水,快速同步至最新状态,并遵循统一的冲突解决策略(LWW),确保协同终态一致。
该方案在保证协同数据最终一致性的前提下,为用户提供了符合直觉的独立撤销/重做体验,有效简化了复杂协同环境下的撤销/重做逻辑。
4.8 系统优化与增强特性
基于协同编辑场景的特殊性,从多个维度对系统进行了深度优化,具体包括以下方面:
4.8.1 协同通信优化
通过操作批处理机制,将短时间内的高频操作在客户端进行合并,显著减少网络请求次数,降低服务端压力。同时采用操作差分与压缩算法,过滤连续重复的操作提交,仅同步有效变更内容,大幅减少网络传输数据量。在网络连接方面,实现智能重连机制,基于指数退避算法自动处理网络异常,保障协同连接的稳定性。
4.8.2 计算与查询优化
引入全局节点缓存索引,通过 Map结构以 tid为键缓存所有 AST 节点,将节点查询时间复杂度从 O(n) 优化至 O(1),极大提升了节点定位效率。同时建立分层更新机制,通过细粒度的依赖分析,确保每次操作仅触发最小范围的组件更新,避免不必要的计算开销。
4.8.3 渲染与更新优化
实施差异化渲染策略,基于操作流分析精准识别变更范围,实现 UI 最小化 HMR。AST状态更新时,结合节流防抖机制仅同步变更文件列表,大幅降低内存占用与网络传输开销。
4.8.4 一致性保障机制
构建多重校验体系,在客户端同步时进行 Commit ID 与操作序列长度的双重验证,有效识别和防止状态分叉;客户端提交时比对代码仓库与协同记录的Git Commit ID,解决用户脱离协同系统提交git版本且未重新初始化低代码编辑器,在提交保存时导致的数据不一致问题。建立快速恢复机制,在服务端异常或网络中断后,能够基于最新版本快照和操作日志快速重建协同状态,确保数据一致性。
5. 实践总结:从“工作撞车”到“无缝协同”的技术跃迁
通过将CRDT协同算法与AST驱动的低代码平台深度集成,我们成功构建了一套高性能、高可用的多人实时协同编辑引擎。这一技术方案不仅彻底解决了传统低代码平台中的“工作撞车”问题,更在多个维度实现了突破性进展:
技术架构的革新性融合
- 通过操作原子化设计,将复杂的UI交互转化为满足CRDT数学定律的精细操作指令
- 创新性地采用OpLog + Git快照的混合持久化策略,兼顾实时协同与版本管理需求
- 基于Yjs的YATA模型提供了理论保障的冲突解决能力,确保数据最终一致性
用户体验的质的飞跃
- 实现真正的零延迟实时协同,支持多用户在同一画布上无缝协作
- 提供符合直觉的独立撤销/重做体验,避免干扰其他协作者
- 通过意识感知协议增强团队协作的临场感与透明度
工程实践的卓越表现
- 整套方案具备生产级可靠性,已在大型低代码平台上稳定运行
- 通过多层次优化策略,确保系统在性能、内存和网络效率上的最佳平衡
- 架构设计支持水平扩展,为未来功能演进预留充足空间
这项实践不仅证明CRDT在复杂结构化数据协同场景下的可行性,更为整个低代码行业提供了协同编辑的系统性解决方案。从“避免撞车”到“愉悦协同”,我们正在重新定义团队协作的开发体验与工作流,让低代码平台真正成为赋能团队创新的协作平台而非单兵作战工具。
展望未来,随着协同算法的持续优化和硬件能力的提升,我们相信实时协同将成为所有低代码平台的标配能力。而基于AST与CRDT的架构方案,无疑为这一演进方向提供了坚实的技术基石。
综合参考与推荐阅读
工业实践:Figma 相关
- Figma多人协作技术的实现原理(How Figma’s Multiplayer Technology Works) https://www.figma.com/blog/how-figmas-multiplayer-technology-works/
- 有序序列的实时编辑(Realtime Editing of Ordered Sequences) https://www.figma.com/blog/realtime-editing-of-ordered-sequences/
CRDT(无冲突复制数据类型)
- Are CRDTs suitable for shared editing?(Yjs 作者对CRDT性能的详细分析)https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/
- 无冲突复制数据类型简介 https://lars.hupel.info/topics/crdt/01-intro/
- 不同CRDT算法对比 https://text-crdt-compare.surge.sh/
JSON Patch & 差分处理
- JSON Patch 官方标准与资源 http://jsonpatch.com
- ASTS: 一种实时协同编辑的字符串地址空间转换算法 2017 IEEE 21st International Conference on Computer Supported Cooperative Work in Design (CSCWD)
Yjs 框架与实战
- Yjs 官方文档 https://docs.yjs.dev/
- Yjs 深度解析 https://www.tag1consulting.com/blog/yjs-deep-dive-part-2
- 探秘Yjs工程实现 https://zhuanlan.zhihu.com/p/452980520












