---
title: Staff 工程师视角下协同设计系统大规模分布式状态同步的深度拆解与架构考量
created: 2026-03-12
updated: 2026-03-12
type: summary
tags: ["architecture", "collaboration", "distributed", "notes", "telegram"]
external: http://go/ui/posts/2160/
---

设计像 Canva 这样的协同设计系统（Collaborative Design Tool），本质上是在解决**大规模分布式状态同步**的问题。

作为 Staff Engineer，你不能只看“怎么画图”，要看“**如何保证 100 个人同时修改一个复杂的 JSON 文档时，数据不冲突、不丢失，且延迟极低**”。

以下是按照我们之前的“万金油框架”进行的深度拆解：

---

## 1. 定义边界与规模 (Scope & Scale)

* **核心挑战：** 实时性（Latency < 100ms）、最终一致性（Eventual Consistency）、高并发下的冲突解决。
* **规模估算：** 假设 10M DAU，平均每个设计文件 2MB。并发最高峰可能需要处理数万个活跃的“协作房间”。
* **Staff 视角问句：** “我们是优先保证强一致性（导致卡顿），还是优先保证可用性（导致短暂的视觉分叉）？”（通常选择后者，即 **AP 系统**）。

---

## 2. 核心架构设计 (High-Level Architecture)

### 关键组件：

1. **WebSocket Gateway (Stateful):** 负责维持客户端的长连接。
2. **Room/Session Server:** 管理哪些用户在编辑同一个文件。
3. **Pub/Sub (Redis/Kafka):** 用于在不同的 Gateway 节点之间广播用户的编辑动作。
4. **Snapshot Service:** 定期将当前的内存状态（JSON）持久化到数据库（NoSQL 如 MongoDB 或 DynamoDB）。
5. **Asset Service:** 处理图片、字体的上传和 CDN 分发。

---

## 3. 数据模型与 API (Data Model)

设计文件不是一张图，而是一棵 **层级树 (Tree Structure)**：

* **Design Entity:** `{ id, owner, version, elements: [...] }`
* **Element Entity:** `{ id, type: "rect/text", x, y, width, height, color, z-index }`

### API 选型：

* **HTTP (REST/gRPC):** 用于打开文件、列出文件、导出结果。
* **WebSocket/WebRTC:** 用于发送操作指令（Operations），如 `{ type: "MOVE", id: "elem_1", dx: 10, dy: 5 }`。

---

## 4. 深度钻取：Staff 级别的核心考点 (The Hard Parts)

这是面试官判断你是否资深的关键。

### A. 冲突解决：OT vs. CRDT (必考点)

当两个用户同时移动同一个方块，系统该听谁的？

* **Operational Transformation (OT):** 像 Google Docs 一样。由服务器作为“单一真理来源”，对收到的操作进行转换和排序。实现极难。
* **Conflict-free Replicated Data Types (CRDT):** 像 Figma/Canva。数据结构本身就支持并发合并，不需要中央服务器排序。
* **Staff 建议：** 推荐 **CRDT** (如 LWW-Element-Set)。对于图形设计，通常采用 "Last Write Wins" (最后一次写入获胜) 策略配合唯一时间戳，实现起来更具扩展性。



### B. 协同感知 (Presence & Cursor Tracking)

如何实时看到别人的鼠标在动？

* **策略：** 鼠标轨迹数据不需要持久化。使用 **Ephemeral State (临时状态)**。
* **优化：** 通过 WebSocket 每 50ms 采样一次坐标，客户端使用“插值动画（Interpolation）”来让轨迹看起来平滑，而不是每次坐标跳动都同步。

### C. 文档快照与回滚 (Snapshotting)

如果用户操作了 10,000 步，新进来的协作人员需要把 10,000 步都重演一遍吗？

* **方案：** 引入 **Snapshot + Op Log**。每 50 个操作生成一个快照。新用户载入：`最新快照 + 剩余的少量增量操作`。

---

## 5. 遇到盲区时的第一性原理推导 (Example)

**面试官问：** “如果用户离线了，在本地编辑了半小时，重新上线后如何合并？”
**你的 Staff 级推导：**

1. **认清本质：** 这是一个典型的 **分布式系统版本分叉 (Branching)** 问题。
2. **回归原理：** 离线编辑时间越长，冲突概率越大。
3. **推演决策：**
* “虽然我没做过离线同步，但参考 Git 的原理，我们需要记录一个 `Base_Version_ID`。”
* “当用户重连时，我们对比 `Current_Global_Version`。如果中间有断档，我们尝试 **三路合并 (3-way merge)**。”
* “如果 CRDT 无法自动解决（比如背景图被删了但离线用户在背景上画画），我们需要 **UI 层的干预**，提示用户选择保留哪个版本。”



---

## 总结：为什么这个回答很 Staff？

* 你没有只讨论 UI，你讨论了 **分布式算法 (CRDT/OT)**。
* 你区分了 **持久化数据 (Design)** 和 **临时数据 (Cursor/Presence)**，使用了不同的技术栈（DB vs Memory）。
* 你考虑了 **冷启动性能 (Snapshotting)**。

**下一步建议：**
协同系统往往涉及大量的**前端性能优化**（例如 Canvas 渲染 vs DOM 节点）。既然你有 DevOps 背景，是否需要我聊聊**如何在全球范围内部署这些有状态的 (Stateful) WebSocket 服务器，并实现“就近接入”？**
