news 2025/12/26 10:31:02

Excalidraw云端部署方案:支持万人级并发协作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Excalidraw云端部署方案:支持万人级并发协作

Excalidraw云端部署方案:支持万人级并发协作

在远程办公常态化、跨地域团队协作日益频繁的今天,一个轻量但强大的实时白板工具,早已不再是“锦上添花”,而是产品设计、技术讨论甚至教学培训中的核心基础设施。Excalidraw 以其极简界面和独特手绘风格,在开发者圈层迅速走红——它不像 Figma 那样复杂,也不像 Miro 那样臃肿,却能在架构图、流程草图、头脑风暴等场景中提供恰到好处的表达力。

但问题也随之而来:如何让这个原本为小团队本地协作而生的开源项目,扛住成百上千甚至上万名用户同时在线编辑一张画布的压力?这不仅仅是“加台服务器”就能解决的事。真正的挑战在于——高并发下的数据一致性、低延迟交互体验、以及系统可伸缩性之间的平衡

我们曾在一个大型教育平台试点中遇到这样的需求:一场线上讲座需要支持 5000 名学员同步参与互动白板,讲师每画一笔,所有学生都能实时看到;同时允许部分助教进行协同标注。最终我们基于 Excalidraw 构建了一套云原生部署架构,成功支撑了峰值超过 1.2 万长连接的稳定运行。本文将拆解这套系统的底层逻辑,从实时引擎选型到 AI 能力集成,还原一次工业级协同系统的演进路径。


实时协作的核心:不是“推送”,而是“自动合并”

很多人对“多人协作”的第一反应是“WebSocket 推送更新”。确实,传统做法是客户端发变更 → 服务端广播 → 其他客户端刷新 UI。但在高并发场景下,这种简单粗暴的方式很快就会暴露问题:消息乱序、冲突覆盖、状态不一致……更别说当网络抖动时,谁的操作应该优先?

真正可靠的解决方案,不是靠“锁”或“排队”,而是让每个客户端具备自主合并能力。这就是 CRDT(Conflict-free Replicated Data Type)的价值所在。

Excalidraw 官方推荐使用 Yjs —— 一种基于 CRDT 的共享文档框架。它的精妙之处在于,每个客户端都维护一份本地副本,任何修改都会生成一个“增量更新包”并通过 WebSocket 同步出去,其他客户端收到后无需中央协调即可自动融合进自己的状态树中,最终所有人看到的内容完全一致。

举个例子:两个用户同时在不同位置添加矩形。由于 Yjs 使用逻辑时钟(Lamport Timestamps)和唯一 ID 机制,即便两条消息到达顺序颠倒,也能正确还原出两个独立元素,不会互相覆盖。

import * as Y from 'yjs'; import { WebsocketProvider } from 'y-websocket'; const doc = new Y.Doc(); const provider = new WebsocketProvider('wss://your-server.com', 'room-123', doc); // 将画布元素绑定到 Y.Map const elementsMap = doc.getMap('excalidraw'); // 监听变化并更新 UI elementsMap.observe((event) => { const currentElements = event.target.get('elements'); excalidrawRef.current?.updateScene({ elements: currentElements }); }); // 本地操作触发同步 function onElementChange(elements) { elementsMap.set('elements', elements); }

这段代码看似简单,实则承载了整个协同系统的基石。Y.Doc是一个分布式状态容器,WebsocketProvider负责网络传输层,而Map类型提供了键值监听能力。一旦调用set,Yjs 会自动计算差异,并以二进制格式(可通过lib0编码进一步压缩)发送最小更新包。

⚠️ 实践建议:生产环境务必配置心跳保活与断线重连策略。我们曾因默认超时设置过长导致部分移动设备连接僵死,后通过引入pingInterval: 15000和自动重连队列修复。

更重要的是,CRDT 天然支持离线工作。即使某个用户短暂断网,其操作仍会被记录在本地文档中,恢复连接后自动与其他节点同步,无需手动“拉取最新版本”。


视觉的灵魂:不只是“画得像”,更是“让人愿意用”

如果说实时同步是骨架,那手绘风格就是 Excalidraw 的灵魂。为什么人们喜欢它?因为它打破了数字工具冰冷精确的刻板印象,用轻微抖动的线条、模糊的圆角和随机纹理,唤起了纸笔书写的亲切感。

这一切的背后,是 rough.js 在发力。它不是一个简单的滤镜库,而是一套图形扰动生成引擎。比如绘制一条直线时,rough.js 不会输出 SVG 中的<line>标签,而是生成一条带有波纹扰动的<path>,并附加阴影模拟铅笔压痕效果。

function renderHandDrawnRectangle(canvas, x, y, width, height) { const rc = RoughRenderer.canvas(canvas); rc.rectangle(x, y, width, height, { roughness: 2.5, stroke: '#000', strokeWidth: 2, fillStyle: 'hachure' }); }

虽然 Excalidraw 已内置 rough.js 支持,只需设置rough: true即可启用,但我们发现,在低端设备或复杂图表场景下,频繁重绘可能导致帧率下降。为此,我们在实际部署中加入了动态降级策略:

  • 检测设备性能(如通过navigator.hardwareConcurrency和内存信息)
  • 对于双核以下、内存小于 2GB 的设备,自动降低roughness值至 1.0 或关闭填充样式
  • 在缩放级别较高时缓存渲染结果,避免重复计算

此外,我们还扩展了插件系统,允许企业客户加载自定义绘图模板(如公司标准架构图标),进一步提升专业场景下的实用性。


让 AI 成为你的协作者:从“动手画”到“动嘴说”

最令人兴奋的变化,莫过于将大语言模型(LLM)融入创作流程。过去画一张微服务架构图可能要花十几分钟拖拽连线,现在只需要一句话:“帮我画一个包含 API 网关、用户中心、订单服务和 MySQL 数据库的系统架构图。”

我们的实现方式是在后端搭建一个 AI 图形生成服务,接收自然语言指令,由 LLM 解析语义并输出符合 Excalidraw 数据结构的 JSON 元素数组。

from fastapi import FastAPI from pydantic import BaseModel import openai app = FastAPI() class SketchRequest(BaseModel): prompt: str diagram_type: str = "flowchart" @app.post("/generate") async def generate_diagram(request: SketchRequest): system_prompt = f""" You are an assistant that generates Excalidraw-compatible diagrams. Given a user's description, output ONLY a JSON object matching the Excalidraw element schema. Rules: - Use realistic coordinates starting from (100, 100) - Space out elements logically - Prefer hand-drawn style attributes (roughness > 1.5) - Return valid JSON only """ response = openai.ChatCompletion.create( model="gpt-4o-mini", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": request.prompt} ], temperature=0.5, response_format={"type": "json_object"} ) return {"elements": response.choices[0].message['content']}

关键点在于提示词工程(Prompt Engineering)的设计。我们强制要求模型返回结构化 JSON,并预设了字段约束(如坐标范围、颜色编码、类型枚举),确保输出可直接被前端消费。

当然,AI 并非万能。我们观察到几个典型问题:
- 模型偶尔会生成非法坐标(如负数过大)
- 连线关系可能出现逻辑错误(A 指向 B,B 又指向 A 形成环路)
- 文本换行处理不佳导致文字溢出框体

因此,我们在接入层增加了一道校验中间件:

def validate_elements(elements): for el in elements: if el['x'] < 0 or el['y'] < 0: el['x'], el['y'] = max(el['x'], 50), max(el['y'], 50) if el['width'] > 800: el['width'] = 600 # 更多边界检查... return elements

经过验证的元素才会返回给前端插入画布。这样既保留了 AI 的高效性,又避免了“智能但不可靠”的尴尬。


系统架构全景:如何撑起万人并发

把单点技术串起来容易,难的是构建一个整体可用、弹性可扩的系统。以下是我们在生产环境中落地的整体架构:

graph TD A[Client Web Browser] --> B[Load Balancer] B --> C[API Gateway & Auth Service] C --> D[Realtime Engine Cluster] C --> E[AI Generator Service] D --> F[(Redis Presence)] D --> G[(PostgreSQL Rooms)] E --> H[(OpenAI / Local LLM)] C --> I[CDN - Static Assets] subgraph Infrastructure D --> J[Kubernetes Autoscaler] F --> K[Sentinel Monitoring] G --> L[Backup & TTL Cleanup] end

分层解析

  • 前端层:React + Excalidraw 官方库,集成 Yjs 提供商。首次加载时优先获取快照(snapshot),而非重放全部历史操作,显著提升冷启动速度。
  • 接入层:Nginx 做 TLS 终止与负载均衡,JWT 认证网关拦截非法请求,支持 RBAC 权限控制(查看/编辑/管理员)。
  • 服务层
  • 实时引擎集群采用 Node.js +y-websocket,每实例承载约 5000 长连接,通过 Redis Pub/Sub 实现跨实例消息路由;
  • AI 服务基于 FastAPI 构建,支持 OpenAI 和本地部署的 Llama 3 模型切换,响应时间控制在 1.5 秒内。
  • 存储层
  • Redis 存储房间活跃状态、在线用户列表、光标位置;
  • PostgreSQL 持久化房间元数据(创建者、权限、TTL),定期清理过期房间(默认7天);
  • 扩展能力:开放插件接口,支持第三方接入 OCR 识别、PDF 导出、语音转注释等功能。

关键设计决策

  1. 连接分片:单个 WebSocket 实例无法承载万级连接,我们采用“房间哈希 → 实例映射”策略,将不同 room-id 分配到不同后端节点,结合 Kubernetes HPA 实现自动扩缩容。
  2. 消息压缩:Yjs 默认使用二进制编码(lib0),相比 JSON 减少约 60% 流量,在千人房中尤为明显。
  3. 权限隔离:JWT payload 中携带room_idrole声明,服务端据此过滤可操作范围,防止越权访问。
  4. 监控体系:Prometheus 抓取各节点指标(连接数、消息吞吐、P99 延迟),Grafana 展示实时大盘,异常时触发告警。

实战痛点与应对策略

问题现象根本原因解决方案
用户退出后光标残留客户端未发送 leave 事件Redis 设置 presence key TTL,定时清除离线状态
百人以上房间卡顿频繁 re-render 触发重排使用requestAnimationFrame节流更新,合并批量变更
AI 生成内容偏移画布中心模型未考虑现有布局在 prompt 中加入上下文:“请将新元素放置在当前空白区域右侧”
移动端触控延迟Canvas 事件未优化启用touch-action: none,监听 pointer events 替代 mouse
数据泄露风险公共链接无访问控制引入短链加密 + 一次性令牌机制,支持私有化部署

值得一提的是,我们在某金融客户项目中实现了全链路内网部署:前端静态资源托管于内部 CDN,WebSocket 服务运行在隔离 VPC,AI 模型替换为本地微调后的 Qwen,敏感数据全程不出内网,满足合规审计要求。


结语:从工具到平台的跃迁

Excalidraw 的魅力在于“简单”,但它的潜力远不止于此。通过引入 Yjs 实现强一致协同、利用 rough.js 保持视觉温度、结合 LLM 实现意图驱动创作,我们见证了一个开源项目如何蜕变为支撑万人级并发的企业级协作平台。

这套架构不仅适用于教育、研发、咨询等行业的大规模互动场景,也为未来智能协作空间打开了想象空间——试想,未来的白板不仅能理解你说的话,还能听懂会议录音自动生成纪要图谱,甚至通过摄像头识别人手势完成操作。

技术的终点,从来不是替代人类,而是让人更专注于创造本身。而 Excalidraw 正走在通往这一愿景的路上。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/21 7:49:43

RAG 的基石:文本嵌入模型与向量数据库

前言为什么 RAG 离不开 Embedding 与向量数据库&#xff1f;在上一篇文章中&#xff0c;我们已经讲过&#xff1a; RAG&#xff08;Retrieval-Augmented Generation&#xff09;本质上是“先找资料&#xff0c;再让大模型回答问题”。而“找资料”这一步&#xff0c;背后最关键…

作者头像 李华
网站建设 2025/12/21 7:49:15

Excalidraw历史快照功能:关键时刻找回丢失内容

Excalidraw历史快照功能&#xff1a;关键时刻找回丢失内容 在一次深夜的产品评审会前&#xff0c;团队正在用 Excalidraw 协同绘制系统架构图。突然&#xff0c;有人误触删除键&#xff0c;整个模块区域瞬间消失——但只需轻点几下“撤销”&#xff0c;再从自动保存的快照中恢复…

作者头像 李华
网站建设 2025/12/21 7:46:47

Excalidraw动画功能探索:让静态图表动起来

Excalidraw动画功能探索&#xff1a;让静态图表动起来 在技术分享或产品评审会上&#xff0c;你是否曾遇到这样的尴尬——精心绘制的架构图刚展示一半&#xff0c;同事就问&#xff1a;“这一步是在哪个阶段发生的&#xff1f;” 静态图表擅长呈现“结构”&#xff0c;却难以表…

作者头像 李华
网站建设 2025/12/21 7:43:35

Excalidraw插件生态盘点:哪些AI扩展最值得安装?

Excalidraw插件生态盘点&#xff1a;哪些AI扩展最值得安装&#xff1f; 在技术团队的日常协作中&#xff0c;你是否经历过这样的场景&#xff1f;一场架构评审会议正在进行&#xff0c;讨论逐渐深入&#xff0c;但白板上的草图却迟迟无法跟上思路——画得太慢、结构混乱、表达不…

作者头像 李华
网站建设 2025/12/21 7:43:07

35、Windows 10 电脑数据迁移与求助指南

Windows 10 电脑数据迁移与求助指南 一、旧电脑数据迁移到新 Windows 10 电脑的方法 在更换电脑时,将旧电脑的数据迁移到新电脑是一项重要任务。以下介绍几种常见的迁移方法。 (一)PCmover 软件 PCmover 软件适合那些有耐心且具备一定电脑使用经验的人。如果使用过程中出…

作者头像 李华
网站建设 2025/12/21 7:42:08

钉钉占用C盘空间太大怎么办?

钉钉占用C盘过多&#xff0c;优先通过客户端修改下载文件保存路径&#xff0c;再用符号链接迁移核心缓存/数据目录&#xff0c;以下是Windows系统的完整操作步骤。一、快速修改下载文件路径&#xff08;推荐&#xff09; 打开钉钉&#xff0c;点击左上角头像 → 设置 → 通用。…

作者头像 李华