Excalidraw冲突解决算法:多人编辑不丢更改
在远程协作成为常态的今天,团队成员常常需要同时对同一份图表或设计稿进行修改。设想这样一个场景:你正在和同事一起在线绘制系统架构图,你们几乎在同一秒拖动了同一个组件——如果系统处理不当,其中一人的操作就会被覆盖,甚至引发画布状态混乱。而使用 Excalidraw 时,这种情况极少发生。它的多人实时编辑体验流畅得仿佛所有人都在操作同一台设备,所有变更都被完整保留。
这背后并非魔法,而是一套精密设计的冲突解决机制在默默工作。正是这套机制,让“多人编辑不丢更改”从一句宣传语变成了可信赖的技术现实。
要理解 Excalidraw 是如何做到这一点的,我们得先看看它面对的是什么问题:多个用户、不同网络环境、异步操作、潜在的时间偏移……在这种分布式环境下,传统锁机制显然行不通——没人愿意每次想移动一个方框前还得“申请编辑权”。因此,Excalidraw 必须采用一种无需阻塞、能自动合并变更的同步策略。
目前主流的解决方案主要有两种:Operational Transformation(OT)和CRDT(Conflict-Free Replicated Data Type)。虽然官方并未完全公开其底层协议细节,但从行为特征与社区实现来看,Excalidraw 极有可能采用了 CRDT 或其变体作为核心同步模型,尤其适合其以独立图形元素为主的白板数据结构。
先来看看这两种技术的本质差异。
OT 的思路是“变换操作”。比如两个人同时在一个字符串中插入字符,系统会根据操作发生的逻辑顺序,动态调整后到操作的作用位置,确保最终结果一致。这种方法最早应用于 Google Docs 等文档协同工具,在文本连续性强的场景下表现优异。但它的问题在于复杂度高,尤其是在处理嵌套结构或多维空间操作时,变换规则容易出错,且通常依赖中心服务器来协调操作顺序。
相比之下,CRDT 更像是“自带共识的数据类型”。它的设计理念是:只要每个副本都遵循相同的合并规则,无论更新顺序如何,最终都能收敛到一致状态。这种去中心化的特性让它非常适合像 Excalidraw 这样的 P2P 或弱中心化架构。例如,每个图形元素都可以看作一个带有版本号的对象,当两个客户端并发修改同一属性时,系统只需比较时间戳或逻辑时钟,保留“最新”的那次更新即可。
举个简单的例子:用户 A 把一个矩形的 X 坐标改为 100,用户 B 改为 150。如果 B 的操作发生在 A 之后(通过逻辑时钟判定),那么无论消息到达顺序如何,最终坐标就是 150。整个过程无需协商,也不需要回滚重试。
下面这段 Python 代码模拟了这种基于时间戳的合并逻辑:
from datetime import datetime from typing import Dict, Any class CRDTElement: def __init__(self, element_id: str): self.id = element_id self.state: Dict[str, Any] = {} self.timestamp: Dict[str, datetime] = {} def update(self, key: str, value: Any, ts: datetime): if key not in self.timestamp or ts > self.timestamp[key]: self.state[key] = value self.timestamp[key] = ts def merge(self, other: 'CRDTElement'): for key, value in other.state.items(): other_ts = other.timestamp[key] if key not in self.timestamp or other_ts > self.timestamp[key]: self.state[key] = value self.timestamp[key] = other_ts # 示例:两个客户端更新同一个矩形的位置 rect_A = CRDTElement("rect1") rect_B = CRDTElement("rect1") rect_A.update("x", 100, datetime(2025, 4, 5, 10, 0, 0)) rect_B.update("x", 150, datetime(2025, 4, 5, 10, 0, 5)) # 更新更晚 merged = CRDTElement("rect1") merged.merge(rect_A) merged.merge(rect_B) print(merged.state["x"]) # 输出: 150 —— 正确合并为最新值这个模型虽然简化,却揭示了 Excalidraw 同步机制的核心思想:将每一个可变属性视为独立的状态单元,用元信息控制合并优先级。当然,实际系统不会直接依赖物理时间,而是采用 Lamport 时钟或向量时钟来避免因设备时间不同步导致的错误排序。
再深入一层,Excalidraw 的数据模型本身也为这类算法提供了良好支撑。整个画布本质上是一个由 JSON 对象组成的集合,每个图形元素都有唯一 ID 和明确属性,如x,y,width,text等。这种扁平化、离散化的结构天然契合 CRDT 的应用场景——毕竟,你在白板上画的不是一个连贯的文本流,而是彼此独立的形状与注释。
每当用户做出改动,比如移动一个箭头或修改文字内容,前端并不会发送整张画布,而是生成一个增量操作指令,通过 WebSocket 推送到协作服务器,再广播给其他参与者。典型的操作结构如下:
function broadcastElementUpdate(updatedElement) { const operation = { type: 'UPDATE_ELEMENT', payload: { id: updatedElement.id, properties: pick(updatedElement, ['x', 'y', 'width', 'height', 'text']), clientId: getCurrentClientId(), timestamp: Date.now(), // 实际使用逻辑时钟 version: updatedElement.version + 1 } }; socket.emit('operation', operation); }接收端收到消息后,并非无条件应用,而是进行一次“版本仲裁”:
socket.on('operation', (op) => { if (op.type === 'UPDATE_ELEMENT') { const localEl = elements.get(op.payload.id); const remoteVersion = op.payload.version; if (!localEl || remoteVersion > localEl.version) { applyRemoteUpdate(op.payload); reconcileCanvas(); } } });这里的version字段起到了关键作用。它保证了状态只会向前推进,永远不会因为迟到的操作而回退。这也意味着即使某个用户在网络恢复后重新连接,只要本地保存了未确认的操作日志,就可以按正确顺序重放并合并,实现真正的离线可用性。
不过,真实世界的挑战远比理论复杂。比如,当多个用户同时拖拽同一个元素怎么办?总不能让图形来回跳动吧。Excalidraw 的做法很聪明:引入“操作归属”概念。只有发起拖动的客户端才拥有该元素的临时控制权,其他客户端则显示“正在被编辑”状态,并禁用本地交互。这既避免了冲突,又保持了视觉一致性。
又比如,AI 自动生成图表时可能一次性添加几十个元素,若逐个发送会造成“同步风暴”。为此,系统会对这类批量操作进行打包节流,平滑推送,防止瞬时负载过高影响体验。
整个协作系统的架构也经过精心设计:
[Client A] ←───┐ ├──→ [Collaboration Server (WebSocket)] [Client B] ←───┘ │ ↓ [Presence Service] // 显示谁在线 [Persistence Layer] // 存储画布快照客户端负责渲染和输入捕捉,服务器承担消息路由与轻量协调,持久层定期保存快照用于恢复和分享。整个链路基于 WebSocket 实现低延迟通信,典型操作传输延迟控制在 100ms 以内,接近本地响应水平。
值得一提的是,这套机制还充分考虑了工程实践中的各种边界情况:
- 防抖优化:对于连续操作(如快速拖动),采用 debounce 聚合机制,避免频繁发送微小变动;
- 安全验证:服务端会对所有 incoming 操作做合法性检查,防止恶意注入非法元素或越权修改;
- 格式兼容:通过 schema migration 机制支持未来字段扩展,确保旧版客户端仍可读取新格式数据;
- 可扩展性:同步引擎模块化设计,便于后续接入评论、批注等新型协作功能。
这些细节共同构成了一个健壮、高效、用户体验友好的实时协作体系。
回到最初的问题:为什么 Excalidraw 能做到“不丢更改”?答案并不在于某一项炫技式的技术突破,而是在于对问题本质的准确建模与多种成熟机制的巧妙组合。它没有强行套用 OT 那样复杂的变换逻辑,也没有追求完全去中心化的极端方案,而是选择了最适合自身业务形态的技术路径——用轻量 CRDT 思想处理离散对象,辅以版本控制与因果排序,构建出一套兼顾性能与可靠性的同步协议。
这种设计哲学也给开发者带来了重要启示:在构建协同系统时,不必盲目追随大厂方案。文档类工具或许需要 OT,但图形白板完全可以走另一条路。关键是要清楚自己的数据模型是什么样的,用户的操作模式是怎样的,然后选择最匹配的同步范式。
如今,随着 AI 功能的集成,Excalidraw 已不仅是人工协作的平台,更成为人机协同的新载体。当你输入“画一个微服务架构图”,AI 瞬间生成草图,紧接着你和团队成员就开始在其基础上迭代修改——这个过程中,人工操作与机器生成的内容必须无缝融合在同一同步流程中。而现有的冲突解决机制恰好为此做好了准备:无论是人还是 AI 发起的变更,都被统一抽象为“操作事件”,平等参与合并。
可以说,Excalidraw 不仅解决了当下多人编辑的技术难题,更为下一代智能协作工具铺好了道路。它的价值不仅体现在功能上,更在于提供了一个开源、透明、可复用的协作架构样板。对于那些希望打造自己协同产品的工程师来说,它的实现思路值得反复咀嚼。
最终,一个好的协同系统应该让人忘记它的存在。你不会去想“我的修改会不会被覆盖”,也不会担心“他改了我的东西怎么办”。你只是专注于表达想法,而技术在背后默默守护每一次创造。而这,正是 Excalidraw 所抵达的境界。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考