在现代 Web 软件工程的宏大叙事中,Figma 凭借其丝滑的多人协作体验,不仅重塑了设计工具的行业标准,更让“Figma 如何实现多人实时编辑”成为了高阶系统设计(System Design)面试中一道极具分量的必考题。虽然 CRDT (Conflict-free Replicated Data Types) 常被视为这一技术难题的标准答案,但其背后的工程实现远比单纯的算法选择更为复杂精妙。Figma 的核心突破在于彻底摒弃了传统屏幕共享的像素流传输模式,转而构建了一套基于 对象树(Object Tree) 和 属性增量(Property Deltas) 的高效数据同步机制。与 Google Docs 等文本编辑器所依赖的 Operational Transformation (OT) 算法不同,Figma 面对的是由 UUID 标识的非线性图形对象,这促使工程团队放弃了依赖中央服务器进行复杂排序的传统路径,转而拥抱更适合分布式环境的最终一致性模型。
通过采用 Last-Writer-Wins (LWW) 策略与原子化属性更新,Figma 成功将复杂的冲突解决逻辑简化为数学上的自动收敛过程。这种架构设计不仅消除了文件加锁带来的协作摩擦,实现了毫秒级的 实时同步,更赋予了应用原生级的 离线支持 能力——即便在网络断连的情况下,用户的操作序列也能在本地被完整保留,并在重连瞬间无缝合并至主文档。深入理解 Figma 这一基于 WebGL 渲染技术与分布式算法的混合架构,不仅能帮助开发者厘清 OT 与 CRDT 的技术选型边界,更能揭示现代协同软件如何在无锁并发与数据一致性之间找到完美的平衡点,为构建下一代即时协作应用提供极具价值的架构蓝图。
核心原理直击:Figma 多人协作的“幕后黑手”是 CRDT
在系统设计(System Design)面试中,当面试官问到“Figma 如何实现多人同时编辑不冲突”时,最核心的关键词就是 CRDT。
CRDT (Conflict-free Replicated Data Types) 是一种用于分布式系统的数据结构,它允许网络中的多个副本(Clients)在没有中央锁的情况下独立并发地修改数据,并通过数学规则自动合并这些修改,最终保证所有副本的数据状态一致(Eventual Consistency)。
并非“屏幕共享”,而是“数据同步”
理解 Figma 协作机制的第一步,是将其与传统的远程桌面或屏幕共享区分开。
- 屏幕共享(Pixel Streaming):传输的是视频流或像素帧。对带宽要求极高,且无法获取对象的语义信息(无法选中别人的图层)。
- Figma 的方式(Property Deltas):传输的是极其轻量级的属性变更(Deltas)。
当你在画布上移动一个矩形时,Figma 并没有发送整个文件,而是通过 WebSocket 向服务器发送了一条类似update(layer_id, property="x", value=100)的指令。本地渲染引擎负责以 60FPS 绘制界面,而网络层仅负责同步这些微小的数据变更。
CRDT 带来的三大工程优势
Figma 官方工程团队曾详细披露,其多人协作技术深受 CRDT 概念的启发,这种技术选型直接解决了传统协作工具的三大痛点:
- 无锁实时性(Real-time Availability)
系统不需要对文件或图层进行“加锁”。用户 A 修改图层颜色时,不需要等待用户 B 释放权限。所有客户端都可以随时写入,这种“乐观”的并发模式是流畅体验的基础。 - 原生离线支持(Offline Support)
这是 CRDT 最强大的特性之一。由于 CRDT 的合并逻辑是基于数据结构本身的数学属性,而非依赖中央服务器的实时裁决,因此用户可以在断网状态下继续编辑。一旦网络恢复,本地积累的操作序列(Operations)会自动同步并合并到主文档中,实现无缝重连。 - 自动冲突解决(Conflict Resolution)
在分布式环境中,冲突不可避免(例如两人同时修改同一个矩形的颜色)。CRDT 保证了只要所有客户端接收到了相同的操作集合,无论接收顺序如何,最终都能计算出相同的状态。Figma 针对不同属性采用了特定的策略(如 Last-Writer-Wins),确保系统能自动收敛,而无需弹出“版本冲突,请手动合并”的对话框。
为什么是 CRDT?与 OT 算法的技术选型对比

在实时协作领域,Operational Transformation (OT) 曾是无可争议的行业标准,它是 Google Docs 等协同文档编辑器的核心基石。然而,当 Figma 决定构建基于浏览器的多人设计工具时,他们并没有盲目沿用 OT,而是选择了 CRDT (Conflict-free Replicated Data Types) 及其变体作为技术底座。
这一技术选型的背后,是文本编辑与图形设计在数据模型上的本质差异,以及对工程复杂度的权衡。
1. 线性流 vs. 对象属性:OT 的局限性
OT 算法最擅长处理的是线性数据流(Linear Data Stream)。在 Google Docs 中,文档本质上是一个长字符串。
- 场景:用户 A 在第 5 个字符处插入 "Hello",用户 B 同时删除了第 3 个字符。
- OT 的解法:算法必须通过复杂的转换逻辑(Transformation),调整索引位置,确保两个操作在不同客户端应用后得到相同的结果。
然而,Figma 的设计文件并非线性字符串,而是由独立对象构成的复杂集合。
- 场景:用户 A 修改了 "图层 1" 的颜色,用户 B 修改了 "图层 1" 的 X 轴坐标。
- Figma 的解法:这两个操作是针对同一对象的不同属性(Property),它们在逻辑上是互不干扰的。
如果强行使用 OT 处理这种二维甚至多维的对象层级,工程复杂度会呈指数级上升。正如 How Figma handles edit conflicts 中所述,对于非文本工具而言,OT 往往显得“杀鸡用牛刀”(overkill)。Figma 需要处理的是对象属性的原子化变更,而不是字符流的相对位置变换。
2. 中央权威 vs. 分布式一致性
OT 算法通常依赖于一个中央权威(Central Authority)来决定操作的顺序。
- OT 模式:所有操作必须经过服务器的“盖章”和转换,服务器是绝对的真理中心。这在网络延迟较高或离线状态下,用户体验会大打折扣。
- CRDT 模式:CRDT 的设计初衷就是为了支持分布式系统和离线优先(Offline-first)。它允许不同客户端在本地先行应用变更,随后通过数学上可交换(Commutative)的规则自动合并,最终达到一致性(Eventual Consistency),而无需依赖中央服务器进行复杂的冲突仲裁。
3. 工程权衡:Last-Writer-Wins (LWW)
在具体的工程实现中,Figma 采用了一种简化的 CRDT 策略,即针对对象属性的 Last-Writer-Wins (LWW) 策略。
特性 | Operational Transformation (OT) | Figma 的 CRDT 策略 |
|---|---|---|
核心对象 | 线性文本(String) | 独立对象(Object/Node) |
冲突处理 | 转换操作索引(Transform) | 属性覆盖(LWW) |
复杂度 | 极高(需处理所有操作组合) | 较低(基于时间戳或逻辑时钟) |
离线支持 | 困难(需服务器协调) | 原生支持(同步后自动合并) |
这种策略意味着,如果两个设计师几乎同时修改同一个矩形的颜色,Figma 不会像 Git 那样要求用户“解决冲突”,而是直接采纳最后到达的那个值。对于视觉设计工具而言,这种处理方式既符合用户的直觉,又避免了 OT 带来的复杂意图保留问题。
通过放弃对“字符级精确顺序”的执着,转而拥抱针对对象属性的最终一致性,Figma 成功将实时协作的性能瓶颈从算法计算转移到了单纯的网络传输上。
Figma 的数据结构:对象树(Object Tree)模型

要理解 Figma 为什么能通过 CRDT 实现高效协作,首先需要理解其底层的数据模型。与 Google Docs 等文档工具处理线性的“字符流”不同,Figma 处理的是一个层级化的对象树(Object Tree)。
1. 类似 DOM 的树状结构与 UUID
Figma 的文档结构在概念上非常类似于浏览器的 DOM 树,由 Page、Frame、Group 和 Layer(如矩形、文本、矢量路径)组成。
但在分布式系统中,最关键的设计在于身份标识(Identity)。在 Figma 中,每一个对象在创建时都会被分配一个全局唯一的 UUID。这与文本编辑器通常依赖的“数组索引”或“行号”截然不同。无论该对象在图层面板中被移动到哪里,它的 UUID 始终不变。这使得 CRDT 算法能够精准定位操作对象,而无需关心它当前的相对位置。
2. 基于属性的原子化更新(Property Deltas)
基于这种对象模型,Figma 的每一次编辑操作实际上都是针对特定 UUID 对象的属性变更(Property Delta),而不是重发整个文件或视口像素。
例如,当用户将一个矩形向右移动 10px 时,客户端发送给服务器的消息并非“更新屏幕区域”,而是一条类似 JSON 的指令:
{
"op": "update_property",
"objectId": "550e8400-e29b-...",
"property": "x",
"value": 150
}这种数据结构的优势在于极高的正交性:
- 如果用户 A 修改了图层 X 的颜色;
- 同时用户 B 修改了图层 Y 的位置;
- 或者用户 C 修改了图层 X 的 圆角半径;
由于这些操作作用于不同的对象或同一对象的不同属性,它们在数学上是互不干扰的,可以直接应用而无需复杂的冲突合并逻辑。正如 Figma 官方博客所解释的,Figma 的服务器只需追踪每个对象每个属性的“最新值”(Last-Writer-Wins),即可保证最终一致性。
3. 复杂的父子关系处理
对象树模型中最棘手的部分在于结构变更,即“父子关系”的改变(Reparenting)。例如,将一个图层从“Frame A”拖拽进“Frame B”。
在 Figma 的数据结构中,父子关系也被视为一种属性(例如 parent_id)。但这引入了潜在的逻辑陷阱:如果用户 A 将图层 P 设为图层 C 的父级,而用户 B 同时将图层 C 设为图层 P 的父级,就会导致“循环引用”(Cycle),导致渲染引擎崩溃。
为了解决这个问题,Figma 并没有简单地依赖通用的 CRDT 库,而是针对这种 2D 场景定制了规则,确保树状结构的完整性不被并发操作破坏。这种将“视觉层级”抽象为“属性数据库”的设计,正是 Figma 能够承载大规模多人实时编辑的工程基石。
实战中的冲突解决与离线同步机制

在多人实时协作的系统设计中,最棘手的问题往往不是“如何传输数据”,而是“如何处理不一致”。当两名设计师同时修改同一个图层,或者当用户在进入电梯断网后继续编辑,系统必须确保所有人最终看到的文档状态是一致的。这就是分布式系统中的最终一致性(Eventual Consistency)原则。
Figma 并没有采用 Google Docs 那样复杂的 OT(Operational Transformation)算法来处理所有冲突,而是结合了 CRDT(Conflict-free Replicated Data Types)的设计理念与中心化服务器的仲裁能力,构建了一套既轻量又鲁棒的同步机制。
1. 属性级冲突:Last-Write-Wins (LWW) 策略
对于绝大多数设计操作,例如修改颜色、调整不透明度或改变位置,Figma 将其视为简单的属性更新。每个对象(Object)都有一个唯一的 UUID,而属性(Property)则是对象上的键值对。
当冲突发生时——例如用户 A 将按钮改为红色,而用户 B 几乎同时将其改为蓝色——Figma 采用最后写入者胜(Last-Write-Wins, LWW)策略。
根据 Figma 工程团队的解释,服务器是事件顺序的最终仲裁者。Figma 不需要依赖复杂的分布式时钟同步,而是由服务器接收到的消息顺序来决定哪个值是“最新”的。
- 场景:用户 A 和 B 修改同一属性。
- 处理:服务器接收到 A 的请求,更新值为红色;随后接收到 B 的请求,更新值为蓝色,并广播给所有客户端。
- 结果:所有客户端最终都显示蓝色。
这种策略虽然简单,但在高频低延迟的场景下非常有效,因为它避免了复杂的合并逻辑(Merge Hell),对于视觉属性而言,用户通常也能接受“最后的操作生效”这一直觉。
2. 结构性冲突与 CRDT 启示
比属性修改更复杂的是树状结构(Tree Structure)的变更。例如:用户 A 删除了一个 Group,而用户 B 正在往这个 Group 里添加一个图层。如果处理不当,可能会导致数据损坏或图层“幽灵般”地悬空。
Figma 借鉴了 CRDT 的数学特性来保证数据结构的收敛,但为了性能和存储效率做了务实的调整:
- 层级关系即属性:对象的父子关系(Parent-Child)被视为一种特殊的属性(例如
parent_id)。 - 对象存活状态:对象的创建和删除被视为显式的操作。
- 删除优先:如果一个父对象被删除,根据 Figma 的逻辑,该对象的所有数据(包括其属性和子级引用)会从服务器上移除。这意味着在上述冲突场景中,用户 B 新增的图层如果试图挂载到一个已被删除的 Group ID 上,该操作最终会被修正或视为无效,从而保证文档树的完整性。
值得注意的是,为了防止文档无限膨胀,Figma 不会在服务器上永久保留“墓碑”(Tombstones,即已删除对象的占位符)。相反,删除操作的数据会被移入执行该操作客户端的 Undo Buffer 中。如果该用户撤销删除,客户端负责恢复对象及其属性。这种设计在保证一致性的同时,极大地优化了服务器的存储压力。
3. 离线支持与“隧道”场景
真实的网络环境是不可靠的。当用户带着笔记本进入无信号的隧道时,Figma 依然允许流畅编辑,这种体验依赖于乐观 UI(Optimistic UI)与重连同步机制。
- 乐观更新:当用户在离线状态下操作时,客户端会立即应用变更,让界面保持响应,同时将这些操作存入本地的待发送队列。
- 预测与修正:客户端认为自己的当前状态是“最新”的预测值。
- 重连合并:当网络恢复时,客户端将队列中的变更批量发送给服务器。
- 如果服务器在断网期间没有收到针对同一对象的冲突变更,本地变更会被接受并广播。
- 如果发生了冲突(例如离线修改的对象已被其他在线用户删除),服务器返回的最新状态会覆盖本地的预测状态。
这种机制确保了用户在网络波动时不会感到卡顿,而在网络恢复后,系统能自动“追赶”至最新的一致状态,无需用户手动解决合并冲突。正如 LinkedIn 上的技术分析 所述,这种“原子化同步”和“忽略冲突”的策略,恰恰是 Figma 能够实现多人丝滑协作的关键——它牺牲了部分极端情况下的数据保留(被覆盖),换取了极致的性能和用户体验。
不仅仅是算法:WebGL 渲染与性能架构

虽然 CRDT(Conflict-free Replicated Data Types)解决了“谁的数据是正确的”这一逻辑难题,但对于用户而言,实时协作的流畅度不仅仅取决于数据同步的准确性,更取决于渲染性能。如果数据同步完美但界面只有 10fps,协作体验依然不可用。Figma 之所以能在浏览器中实现媲美原生应用的性能,核心在于其脱离了传统的 DOM 渲染路径,采用了 WebGL 与 WebAssembly (WASM) 相结合的高性能架构。
为什么 DOM 不适合大规模协作?
在传统的 Web 开发中,我们通常使用 HTML DOM 来构建界面。然而,对于一个专业的矢量设计工具来说,DOM 的开销过大。每一个图层如果都对应一个 DOM 节点,当设计稿包含成千上万个图层时,浏览器的 Layout(重排)和 Paint(重绘)计算量将呈指数级上升。
为了突破这一限制,Figma 选择了一条更硬核的路线:直接使用 WebGL 进行渲染。
- GPU 加速:Figma 编写了一个定制的渲染引擎,直接调用 GPU 绘制 2D 矢量图形,而不是依赖浏览器的渲染管线。这使得它能够轻松处理包含数万个对象的复杂场景,同时保持 60fps 的流畅度。
- 虚拟化渲染 (Virtualized Rendering):正如地图应用一样,Figma 的画布理论上是无限大的。引擎只渲染视口(Viewport)内可见的内容,通过“分块渲染”技术,确保无论文件多大,缩放和平移操作都能保持丝般顺滑。
C++ 与 WebAssembly:分离逻辑与视图
高性能渲染的背后,是更为底层的架构决策。Figma 的核心逻辑(包括图形算法、数据结构处理)并非直接用 JavaScript 编写,而是主要由 C++ 编写,并通过 WebAssembly (WASM) 运行在浏览器中。
这种架构带来了两个关键优势:
- Headless Core(无头核心):
由于核心逻辑是用 C++ 编写且不依赖于具体的渲染平台(如 DOM),这使得 Figma 的引擎可以是一个“无头”的纯计算模块。这意味着同一套代码既可以运行在浏览器端,也可以运行在服务器端,甚至被编译为原生应用的一部分。这种一致性对于多人协作至关重要,因为它保证了所有端对数据的处理结果是严格一致的。 - 极致的内存与计算效率:
WASM 允许代码以接近原生的速度运行,这对于处理复杂的 CRDT 合并操作和实时渲染循环至关重要。不过,开发者在面试中也应当了解这种架构的物理限制:浏览器对 WASM 的内存分配通常存在硬性上限(通常为 2GB 活跃内存限制)。当协作文件过大导致内存占用接近此阈值时,用户可能会遇到性能下降甚至崩溃的情况,这是浏览器沙箱机制带来的系统级约束。
架构总结:协作体验的最后一块拼图
在面试中讨论 Figma 的架构时,可以将其总结为“双层架构”:
- 逻辑层 (Logic Layer):由 C++ 编写并通过 WASM 运行,负责处理 CRDT 同步、冲突解决和文档结构操作。它是单一事实来源。
- 表现层 (Presentation Layer):基于 WebGL 的自定义渲染器,负责以极高的帧率将逻辑层的数据绘制到 Canvas 上。
这种分离确保了即使在网络波动导致 CRDT 同步延迟时,渲染线程依然可以独立运行,保证用户的本地交互(如缩放、选中)不会卡顿。正是这种算法与渲染架构的深度配合,才造就了“云端协作却像本地应用”的独特体验。
总结:从 Figma 架构中学到的系统设计经验
在系统设计面试中,当面试官问出“如何设计一个实时协作系统”时,Figma 的架构提供了一个教科书级别的参考答案。这不仅仅是关于选择 WebSocket 还是 HTTP 长轮询的问题,而是关于如何通过底层技术的组合来解决一致性(Consistency)、可用性(Availability)和性能(Performance)这个不可能三角。
以下是回顾 Figma 架构时,值得在技术面试或架构评审中强调的关键经验:
1. 核心技术栈的“三驾马车”
Figma 的成功并非依赖单一技术,而是三种核心技术的深度整合。在回答相关设计题时,可以总结为以下架构分层:
- 数据层(CRDTs): 解决“谁说了算”的问题。Figma 使用受 CRDT(Conflict-free Replicated Data Types) 启发的策略来处理对象属性的冲突(例如两个用户同时修改颜色),确保所有客户端在离线重连后能自动收敛到最终一致状态,而无需复杂的中央锁机制。
- 逻辑层(C++ & WebAssembly): 解决“浏览器跑不动”的问题。为了在 Web 端复用高性能的图形算法,Figma 将编辑器的核心逻辑用 C++ 编写,并通过 WebAssembly 运行在浏览器中。这种“Headless Core”的设计使得逻辑层与视图层解耦,保证了多端一致性。
- 渲染层(WebGL): 解决“DOM 太慢”的问题。不同于传统的 DOM 操作,Figma 直接利用 GPU 进行绘制,确保在处理成千上万个图层时依然能保持 60fps 的流畅度。
2. 决策法则:何时选 OT,何时选 CRDT?
在系统设计中,盲目堆砌技术名词是大忌。你需要展示出对技术选型的权衡能力。针对协作算法的选择,可以使用以下法则(Rule of Thumb):
场景特征 | 推荐方案 | 理由 | 典型案例 |
|---|---|---|---|
线性数据 (如纯文本) | OT (Operational Transformation) | 文本编辑对“用户意图”的保序性要求极高(如在第5个字符插入)。OT 在中心化服务器的协调下,能更好处理字符级的并发插入。 | Google Docs, VS Code Live Share |
对象/结构化数据 (如图形、JSON) | CRDT | 图形编辑通常是对属性的修改(Last-Write-Wins 往往足够)或独立的层级操作。CRDT 允许分布式、离线操作,且合并逻辑在数学上是可证明的。 | Figma, Linear, Trello |
弱网/离线优先 | CRDT | CRDT 天生支持去中心化和离线提交,客户端可以在本地队列中积累操作,联网后自动合并。 |
3. 系统设计的终极权衡:复杂度守恒
Figma 的架构向我们展示了一个重要的工程哲学:为了用户体验的简单,必须接受实现的复杂。
传统的 Web 开发模式倾向于依赖浏览器 DOM 和现成的框架,这在开发初期很快,但在处理高并发协作时会将复杂度暴露给用户(如频繁的加载转圈、冲突弹窗)。Figma 选择了一条极其艰难的道路——自研渲染引擎和同步协议。正如业界分析所指出的,现代高效能应用(如 Linear, Superhuman, Figma)都倾向于构建自己的 Sync Engine(同步引擎),将数据同步作为一个独立于 UI 的持久化缓冲层来处理。
面试加分项:
在总结时,可以提到“复杂度转移”的概念。Figma 通过引入 CRDT 和 WASM,将处理网络延迟和渲染性能的复杂度从“用户侧”转移到了“架构侧”。对于工程师而言,这意味着我们在设计系统时,不应仅仅关注“最容易实现”的方案,而应关注“最能支撑业务规模”的方案——即使这意味着要重新发明轮子。







