系统设计练习 - 全球实时协同文档平台
背景
设计一个支持多人实时协作编辑的文档系统,不仅支持文本,还支持:
- 表格
- 富文本 + 嵌入组件(图片,代码块,图表)
- 简单的设计组件(类似Figma的block-based layout)
- 支持API和自动化(类似Notion integration)
系统需求和范围定义
功能需求:
- 多人实时写作编辑(<100ms perceived latency)
- 支持离线编辑+自动同步
- 支持评论,@mention,版本历史
- 支持权限模型(文档级,block级)
- 支持嵌入式结构(block tree/graph)
- 支持API/webhook/integration
非功能需求:
- 全球低延迟
- 高可用(99.99%)
- 海量文档 (10B docs,1B DAU)
- 多租户隔离
- 安全(ACL,审计,加密)
API设计
系统架构设计
整体系统分为三个layer:client,实时同步(orchestrator),snapshot+offline。
整体架构图如下所示:
详细说明:
1. client拥有local DB和ops log。对于文档的每一个操作,生成ops,存入ops log,同时将修改应用到本地文档上。并且将ops发送到service端的stream。
2. orchestrator service接收所有client发送的ops,做OT。关于OT的详细讨论,后面会讲到。将转换过的ops发送到redis pub/sub。每个document在pub/sub创建自己的topic。client通过websocket和pub/sub建立订阅关系,这个client可以快速获得一个document的所有ops,然后应用到本地的版本上。
3. 关于存储。orchestrator service将ops存入到ops DB。相当于service端的ops log。在后端启动一个cron job,定期合并ops到document上,建立document的snapshot。并通过sync service同步到全球主要的CDN上。这样一个client需要从零同步一个document时,可以同步snapshot,再从service端读取为应用到snapshot的ops,来建立最新的document。
4. 对于跨region的情况,每个client和自己所在的region的service进行通信,但是我们设立一个primary region,所有的update都发送到primary region进行处理。这样全球只有一个region处理document更改,解决一致性的问题。
5. 权限管理。我们基于document ACL进行权限管理。对于更细粒度的block权限,建立block tree。遵循子节点的权限设定覆盖父节点的权限设定。最终可以fallback到document ACL上。
6. comment管理。comment和document分开存储。comment使用lazy load加载策略。也就是在user load到某部分document,再从service query该部分的comments进行加载。这样减少一次性load的负载,降低latency。
讨论
1. OT和CRDT。 OT和CRDT是两种协作operation的方法。具体来说,OT基于一个中心进行ops的转换来解决ops之间的相互影响。例如对于文档的内容”12345“进行两个ops:1)在2后插入6;2)将5改为7.两个ops可以分别为: 1) insert "6" at (2); 2)change (4) to "7". 经过OT,两个ops变为:1) insert "6" at (2); 2) change (5) to "7". CRDT基于分布式的粒度更细的ops方式。比如,对于每一个字符进行标记。这样上面的对于不同字符的操作就可以互相独立,互不影响了。
2. 系统性能的瓶颈最可能出现在orchestrator service。当有多个client同时更新一个文档时,orchestrator会有大量的ops进行处理和fanout。我们的策略是每个document处于一个shard。在有多个document需要频繁更新时,它们可以做horizontal scale。如果一个document的更新仍然可能成为瓶颈的话,我们可以考虑进一步让一个document里的每一个chunk使用一个shard。
3. 整个系统的source of truth为ops DB。如果系统其他部分break,比如blob DB,或者redis pub/sub,我们可以replay ops DB里的ops对系统其他部分的信息进行恢复。
4. client和service之间的websocket连接期望通过connection gateway(或者proxy)进行。在connection gateway可以对client进行traffic control,恢复broken connection。这样可以简化后端service的设计也可以更好地维护client connection。
