多人协同编辑系统架构实战:基于OT方案从零搭建指南
多人协同编辑这个功能很多团队一开始都会觉得“不就是让几个人同时打开一个文档吗”真正动手做之后才发现难点根本不是“谁能打开”而是“大家同时改同一个位置时文档内容到底以谁为准”。我前后做过两版多人文档编辑器踩过不少坑这套方案是从实际项目里整理出来的覆盖系统架构、核心领域模型、业务流程设计、API接口文档以及技术实现细节可以作为从零搭建或重构时的参考底稿。我也先打个预防针多人协同编辑的完整方案不等于“引一个第三方库就完事”它涉及编辑内核、实时通信通道、版本管理、权限控制和应用层存储的联动设计。如果只解决编辑冲突不管架构和数据模型后面扩展权限、离线恢复、审计日志时会非常痛苦。这篇文章就是按“一条链路到底”的思路来讲的既讲原理也讲落地。1. 多人协同编辑的三条技术路线先选型再动手多人协同编辑在2024到2025年这个时间点主流技术路线基本就三种diff-patch方案、OT操作转换方案、CRDT无冲突复制方案。我在选型时把这三种都过了一遍分别做了小规模原型验证下面直接说结论。diff-patch方案是很多人最容易想到的每个人本地编辑每隔几秒把整份文档发给服务端服务端用diff算法找出差异再用patch合并。这个方案做三五分钟同步一次的需求可行比如企业内部多人轮流编辑一篇周报、一个合同草案效率要求不高可以接受。但如果是真正的实时协同编辑两个用户同一秒内修改同一个段落diff-patch会产生大量互相覆盖的冲突经常出现“我刚改的字被对方覆盖了”的情况。另外diff算法本身不稳定不同diff引擎生成的结果不一样合并逻辑很难编写和测试。CRDT方案近几年热度很高核心思想是每个副本维护一个可交换、可结合、幂等的操作数据结构不依赖中心化服务端排序理论上天然解决并发冲突。实际调研下来CRDT的实现复杂度比OT更高Yjs是其中最成熟的实现。Yjs可以胜任很多场景比如多人白板、多人文档、协同画布社区也活跃。但是CRDT对数据结构有侵入性文档内容要以CRDT的数据格式存储编辑器要适配Yjs的协议层后续如果要接入自己的文档权限体系、内容快照、离线导出会多一层适配成本。OT操作转换方案是最“传统”且被大量生产环境验证过的路线Google Docs早期也采用类似的中心化OT思想。核心概念是每个用户产生的编辑动作被抽象成操作序列服务端收到操作后根据操作之间的位置偏移量做转换让不同用户的修改能自动对齐到正确位置。OT方案的优点是内容按照“操作原语”而非“整个文档”存储后端可以实时做冲突调整也方便做权限校验和版本追踪。我做技术选型时最终选了OT原因是我们需要支持字级别的实时同步需要服务端做强校验还需要基于文档版本做快照和审计OT与这套业务模型的契合度最高。如果你想要的只是“几个同事改文本文档5秒同步一次”diff-patch就够了如果想要离线优先、完全去中心化可以考虑Yjs这类CRDT实现。下面的架构和模型设计都以OT为主线展开。2. 系统架构设计编辑链路、接入层与协作服务的拆分多人协同编辑的系统架构本质上是“客户端编辑器、连接网关、协作服务、业务服务、存储层”五部分的联动。很多人一开始会把业务逻辑直接塞进WebSocket服务里做成一个大而全的实时服务这样短期能跑但后期加接口、加权限、加审计都会互相牵制。我落地时采用了分层架构各层职责如下组件主要职责关键技术与说明客户端编辑器内核捕获用户输入生成操作原语渲染文档应用远端操作基于ContentEditable或ProseMirror自研编辑器不必太重核心是操作原语化接入网关鉴权、WebSocket接入、REST API反向代理负责连接生命周期管理承担用户身份识别不做业务计算协作服务核心接收客户端操作、执行OT转换、更新文档版本、广播增量无状态可横向扩展通过Redis发布订阅同步跨实例消息文档业务服务创建文档、成员管理、权限配置、元数据查询独立HTTP服务与协作服务通过内部接口或事件联动存储层文档元数据、操作变更集、内容快照、成员数据PostgreSQL存储业务数据Redis承载实时状态和广播对象存储保存历史快照和导出产物这套架构里最关键的一条设计准则是业务查询走REST API实时同步走WebSocket两类流量在接入层就分开。创建文档、拉取文档列表、配置权限这类操作低频且语义复杂用REST更清晰光标位置、文本插入、删除操作这类高频轻量事件用长连接推送更高效。WebSocket在整个链路中承担的是“操作透传广播”的角色协作服务内部需要维护一份当前在线用户与文档的映射关系。如果只有单实例部署直接用内存Map存储docId - connectionId - userId即可但线上通常是多实例部署用户A连接到实例1用户B连接到实例2实例之间必须借助Redis这类中间件广播。我最早做MVP时偷懒只用内存Map单实例压测时一切正常上线后一扩容就发现用户A看不到用户B的光标问题就出在跨实例广播链路没打通。这里也顺便说一下广播方案的选择。很多人一上来就上Kafka但多人编辑场景的实时消息延迟要求很高Kafka的吞吐优势在这个场景里并不突出而且引入Kafka会增加运维成本。我的建议是中小规模单文档几百人同时在线用Redis Pub/Sub就够了简单直接且毫秒级延迟只有在需要持久化操作流、重放审计、跨多个数据中心同步时才考虑Kafka或Pulsar这类消息中间件。关于系统架构还有一点容易被忽略接入网关和协作服务必须支持优雅断连和会话恢复。用户网络抖动导致WebSocket断开如果直接把他的操作上下文全部清掉重连后要重新拉全量文档内容体验会很差。正确做法是连接断开后保留一定时间比如30秒到2分钟的会话上下文包括客户端当前版本号、操作缓冲、游标状态重连成功时根据版本号做增量补齐。这块能力在导讲领域模型时会详细展开。3. 核心领域模型设计文档、版本、变更集与会话很多方案文档会一上来就画几十个表结构但多人协同编辑真正核心的领域实体其实是有限的。我把它收敛成六块文档、文档成员、操作变更集、文档内容快照、游标、连接会话。理解这六块就理解了整个协作领域的骨架。3.1 文档与成员模型文档实体不只存储标题和内容还要记录当前版本号、最近编辑人、最近编辑时间和锁状态。版本号是每次内容变更后自增的数字它是后续所有增量同步和冲突处理的基础。内容不能直接存在文档表里否则每次保存都把整个文档内容更新一次并发量大时数据库锁竞争会非常严重。成员关系是独立表每个成员记录文档ID、用户ID、角色所有者/编辑者/只读者、加入时间和权限来源。多人协同编辑的权限控制会贯穿到每条操作的校验不能让其中一个人是只读者还能通过WebSocket推送insert操作。每次收到客户端操作时协作服务都要先查权限再做OT转换。3.2 操作变更集模型操作变更集是OT方案的核心也是控制并发冲突的关键。一次文本输入、一次删除、一次粘贴都会被转换成一组操作原语序列。我常用的操作原语有三种retain(n)向前跳过n个字符不修改内容相当于定位游标。insert(text)在当前位置插入一段字符。delete(n)删除当前位置起的n个字符。例如在“你好世界”这段文本中把“世界”改成“地球”操作序列可以表示为retain(2), delete(2), insert(地球)。每个操作变更集必须带有基础版本号表示它基于哪个版本的文档产生。客户端编辑时本地维护一个递增版本号每次服务端确认后会更新版本号如果服务端返回的版本号与客户端本地不一致说明过程中有其他用户的修改被合并进来了客户端要基于服务端返回的新版本重新校准。变更集存储表的设计里字段包括变更集ID、文档ID、基础版本号、目标版本号、操作用户ID、操作内容JSON格式、操作时间戳。这条记录既是同步的基础也是历史审计的依据。我把操作内容存为JSON数组比如[{retain:2},{delete:2},{insert:地球}]虽然比二进制序列化冗余一些但排查问题时可以直接肉眼阅读开发调试极其方便。3.3 文档内容快照与恢复模型存量数据不能只存变更集因为随着编辑次数增加变更集链会越来越长新用户加入时如果从第一条变更集慢慢回放会造成漫长的等待。解决方案是定期生成内容快照。快照表记录文档ID、版本号、全量内容、生成时间和大小。比如版本100是一个快照节点新用户加入时只要加载版本100的快照再依次应用100到当前版本之间的增量变更即可不需要从头回放。我实际项目中的策略是版本每累计200次变更且超过20分钟就自动生成一个快照时间触发和数量触发两者取其优。这样既避免了频繁写快照对数据库的压力又能保证新用户进入时增量补齐的代价可控。快照内容建议压缩后存到对象存储数据库里只保留快照的元数据和访问地址。恢复机制也要设计好。如果某次操作导致服务端保存的内容损坏需要找到最近的快照和快照之后的变更集按顺序重新应用。变更集是不可变的一旦写入就不能被后续操作修改只能追加新版本这是保证恢复正确性的前提。3.4 游标与会话模型多人编辑中“看到别人的光标位置”是体验的一部分但游标不应该和文档内容混存。我单独建了游标表记录文档ID、用户ID、连接ID、光标偏移量、选区范围和最后更新时间。游标消息通过WebSocket实时广播不做持久化审计因为游标本身是瞬态状态持久化没有意义。连接会话模型服务于重连恢复。每次WebSocket建立连接都会为这个连接分配一个会话ID记录用户ID、文档ID、当前版本号、最近活跃时间和状态。断线时标记会话失效但保留上下文重连时根据会话ID找到上下文对比版本号差异后增量同步。如果连接空闲超过阈值比如180秒没有操作会主动释放会话。以下是核心表结构的一个紧凑示意字段只展示最能说明问题的部分表名核心字段说明documentsid, title, current_version, owner_id, status文档元数据不存全文document_membersid, document_id, user_id, role文档成员与权限doc_changesid, document_id, base_version, target_version, user_id, op_json, created_at不可变形变集doc_snapshotsid, document_id, version, content_url, created_at内容快照元数据cursorsdocument_id, user_id, connection_id, position, selection_start, selection_end, updated_at在线游标状态connectionsid, user_id, document_id, current_version, status, expires_at连接会话状态4. 业务流程设计从输入到广播的完整链路领域模型确定后业务流就清晰了。多人协同编辑的完整编辑链路可以分为四条主流程正常编辑同步流程、并发冲突合并流程、保存与快照流程、断线重连恢复流程。每一条都要想清楚异常情况。4.1 正常编辑同步流程用户按下键盘时编辑器先产生一个操作变更集这个变更集包含用户输入的内容以及光标位置。以在位置3插入“你好”为例操作序列可能是retain(3), insert(你好)。客户端先把操作应用到本地内容上实现无感知的乐观更新同时把操作发送给服务端。服务端收到操作后做三段工作第一步校验用户是否有编辑权限第二步校验操作基础版本号与当前版本号是否一致第三步把操作应用到内容上生成新的版本号。如果基础版本号一致说明期间没有其他用户的修改直接提交并广播。如果版本号不一致说明本地修改期间服务端已经应用了其他人的操作这时需要走冲突合并逻辑。服务端提交成功后会把当前版本号通知给客户端。客户端收到确认消息后把本地版本号更新为服务端版本号。同时服务端会把这条操作广播给该文档的其他在线用户他们收到广播后在自己的文档副本上执行同样的操作就能看到远端用户的修改。4.2 并发冲突合并流程两人同时在文档中间插入内容是协同编辑里最典型的并发冲突。假设文档当前内容是“ABCDEF”版本号为10。用户A在位置2插入“你好”用户B在位置4插入“世界”。如果A的操作先到达服务端服务端版本号变成11内容变成“AB你 好CDEF”注意是原文“ABCDEF”在位置2处插入“你好”。随后B的插入操作到达它的基础版本号是10和当前版本号11不一致。如果服务端直接应用“位置4插入世界”会插入到错误的位置因为服务端内容已经因为A的插入而发生了偏移。此时服务端要做OT转换把B的操作基于A的操作进行转换。A在位置2插入了2个字符B原本在位置4插入相对A而言位置要向后偏移2位变成位置6插入“世界”。转换后B的操作应用到最新内容上结果是“AB你 好CD世界EF”。这个结果既保留了A的修改也没有丢失B的插入。如果两个操作发生在完全相同的位置比如都在位置3插入字符服务端会约定一个全局规则比如后到达的操作位置保持不变但排在前一个操作之后保证操作顺序稳定。关键点是所有实例必须遵循同一套转换规则不能在多实例中各自实现一套逻辑。OT转换的具体实现可以利用操作原语相互转换当处理insert与insert时后到操作的位置加前到操作的插入长度处理insert与delete时删除位置的偏移同样要加上插入长度。同时处理delete与delete时要取交集部分避免重复删除同一批字符。我把这些转换规则写成了独立的纯函数库配合几十个单元测试用例来覆盖边界情况上线之后这块基本没再出过大规模问题。4.3 保存与快照压缩流程保存流程的核心原则是“变更集实时写、快照定期生成”。每个操作变更集都会实时写入存储保证任何时刻宕机都不丢失用户操作。但不能每次操作都触发全量快照所以快照生成采用定时任务触发。当文档当前版本号达到快照阈值时定时任务读取最新快照版本和当前版本之间的所有变更集依次应用后生成新快照。成功后把新快照地址写入快照表老快照可以被清理。注意这里有一个坑如果在生成新快照的过程中又有新的操作提交新快照不能覆盖这些新操作。我早期的实现里定时任务直接读取“当前最新版本”生成快照期间恰好有用户提交了新版本导致快照内容和已记录变更集重叠。后来改为快照任务生成时锁定一个版本号边界比如生成到版本300最新版本311那快照明确是到300为止301到311的变更集继续保留。4.4 断线重连恢复流程客户端断线后服务端会话仍保留一段时间。重连时需要做增量同步而不是重新拉全量。客户端重连时上报自己最后一次确认的版本号服务端基于这个版本号查询该版本号后的所有变更集逐条发给客户端。这里有三种情况第一种客户端落后版本不多可以直接补发变更集。第二种客户端落后版本太多比如落后超过100个版本直接推送100条变更集效率低不如先发送最新快照再发送快照之后的少量变更集。第三种客户端版本号已经被清理例如旧快照被删除后无法从某个版本继续回放那就只能全量拉取当前快照。我在设计同步恢复时设置了一个简单策略落后版本数小于50增量补发大于等于50走快照加增量如果版本对应的快照或变更集已被清理走全量无优化路径。这个策略的具体阈值可以根据文档平均编辑频率调整。5. API接口设计REST与WebSocket协议拆分多人协同编辑系统的API要分成两部分REST API负责低频业务操作WebSocket协议负责高频实时操作。两者数据模型一致但交互方式不同。5.1 REST API设计REST API参考以下设计接口方法路径说明创建文档POST/api/v1/documents创建新文档获取文档详情GET/api/v1/documents/{docId}获取标题、版本号等信息获取文档内容GET/api/v1/documents/{docId}/content?versionxx按版本获取快照或内容添加成员POST/api/v1/documents/{docId}/members给文档添加协作成员修改成员权限PUT/api/v1/documents/{docId}/members/{userId}设置角色获取变更历史GET/api/v1/documents/{docId}/changes?limit50查看编辑历史记录生成快照POST/api/v1/documents/{docId}/snapshot手动触发快照获取文档内容这个接口比较特殊它要支持版本参数。如果请求的版本正好有对应快照直接返回快照内容如果没有就返回最近快照加增量变更集。返回体里要有base_version快照对应版本和changes增量变更集列表客户端可以根据这两个字段恢复内容。成员管理接口的权限变更必须与协作服务联动。比如某个成员被改成只读服务端要主动推一条权限变更消息给这个用户的连接。如果他正试图推送insert操作服务端在校验时直接拒绝。如果被移出文档还要强制断开他的连接会话否则他仍然持有旧连接还能继续推送操作。5.2 WebSocket协议设计WebSocket连接地址可以设计为/ws/documents/{docId}连接建立时在query参数或首条消息中携带鉴权token。连接成功后的通信协议我按消息类型来划分客户端发往服务端的消息类型join加入文档表示客户端开始监听该文档的实时事件参数带文档ID和最后已知版本号。op推送操作变更集参数包含基础版本号和操作序列。cursor推送自己的光标位置和选区信息。ping心跳消息维持连接活性。服务端发往客户端的消息类型ready加入成功返回当前文档版本号。remote_op广播远端用户的操作变更集。remote_cursor广播远端用户的光标和选区位置。confirm确认本地操作已应用返回新版本号。kickout权限变更或连接被踢下线时通知客户端。reject拒绝操作附带拒绝原因和最新版本号。pong响应心跳。这里有一个比较重要的细节confirm和remote_op要区分清楚。confirm是给操作发起者的确认只发给当前连接不能发广播remote_op是给其他用户的增量要发给该文档除去发起者以外的所有在线用户。如果把confirm误发成广播会造成其他用户重复执行操作内容错乱。5.3 错误码设计REST和WebSocket共用的错误码必须覆盖协同编辑的常见异常。我定的错误码大概是这样的错误码含义处理建议401未登录或token过期客户端重新鉴权后重连403无权限操作提示用户当前是只读模式409版本冲突操作无法应用客户端基于最新版本重新拉取增量并合并422操作序列格式非法检查客户端操作生成逻辑429操作太频繁触发限流客户端退避等待后重试480会话不存在或已过期重新加入文档并走快照恢复版本冲突错误码409是重点。当遇到409时客户端不能直接丢弃用户的操作应该把当前本地内容与服务端返回的最新内容做合并保留用户尚未确认的输入然后基于新版本重新生成操作并推送。这个过程要无缝进行否则用户在输入时突然被“打断”重来体验会非常差。6. 技术实现细节操作转换、存储设计与会话稳定性6.1 操作转换核心实现思路OT的难点不在概念而在实现细节。我用一个简化版的转换思路来说明。假设有两个操作opA和opB都基于版本10产生服务端先应用了opA现在要把opB转换成基于版本11的新操作使其能正确应用到opA之后的内容上。两个操作都是操作原语数组时转换本质上是用两个指针分别遍历opA和opB的指令序列比较当前指令影响的位置范围。比如opB当前是insertopA当前是insert则opB的位置要增加opA插入的长度opA当前是retain(n)opB的位置要减去n直到位置对齐opA当前是delete(n)如果opB的位置落在删除区间内则要把opB的位置调整到删除区间开头。实际项目中的transform代码比这复杂会涉及更多边界情况但核心就是这个“基于位置偏移动态调整”的思路。强烈建议把transform实现成纯函数输入opA、opB输出opB不访问任何外部状态。这样单元测试时只需要构造操作序列就能覆盖大部分并发场景。6.2 前端编辑器内核选型与适配前端编辑内核是操作产生的源头三条技术路线都会涉及编辑器适配。如果全部自研做文本操作原语化并不困难监听textarea或ContentEditable的输入事件记录输入前后文本差异转换成插入或删除操作。但ContentEditable的细节非常多浏览器会把粘贴、拖拽、组合输入尤其是中文输入法都拆成不同事件处理不好就会出现光标跳跃、内容重复。我的经验是优先考虑支持OT的编辑器框架比如ProseMirror。ProseMirror本身以文档状态模型为内核每次变更都能得到结构化的步骤可以很方便地映射成操作变更集。如果团队有富文本需求用ProseMirror会省大量功夫。如果只做纯文本协同也可以考虑CodeMirror 6的state与transaction机制同样能产出结构化的变更。纯文本协同中特别注意输入法场景。中文输入时用户连续输入拼音浏览器会产生compositionstart、compositionupdate、compositionend事件在composition期间的任何文本变更都不能直接作为操作提交必须等compositionend后再对比完整文本差异生成一个批量操作。很多协同编辑器在中文环境下出现“一个字重复输入两次”的问题绝大多数是因为没有正确处理输入法合成阶段。6.3 服务端会话管理与状态存储在服务端实现中我使用一个SessionManager管理所有在线连接与会话。每个文档对应一个会话集合保存该文档所有连接的用户信息、版本号、连接状态。操作广播时向会话集合中所有连接发送增量除去来源连接。多实例部署时每个实例的SessionManager只能管理自己实例上的连接跨实例广播用Redis Pub/Sub解决。具体是文档A的实例1收到操作后先在本地做OT转换和版本提交然后发布一条消息到Redis频道doc:{docId}:ops所有订阅该频道的实例收到消息后再转发给自己的连接。这里需要注意消息的去重实例1自己也会收到Redis广播需要过滤掉来源连接所在的实例避免同一连接收到两条重复消息。保持会话稳定还有一个关键措施心跳。WebSocket本身有ping/pong但每层网络设备超时时间不一样比如某些负载均衡设备会在90秒内自动断开空闲连接。客户端每30秒发送一次ping消息服务端返回pong既能确认连接还活着也能刷新负载均衡的session状态。如果连续三次未收到pong客户端应主动断开并重连。6.4 存储优化与并发控制文档元数据、变更集和快照的存储方案要区分对待。元数据用PostgreSQL实时状态用Redis快照放对象存储。变更集写入PostgreSQL时要注意并发控制。同一文档的两个操作可能同时到达如果都去更新documents表的current_version字段会出现脏写。我给documents表增加了version version 1的条件更新也就是带乐观锁的更新方式。实际提交时根据当前文档版本号是否为操作的基础版本号来决定是否接受。如果不匹配则走OT转换流程转换后再尝试提交。为了避免一个文档在密集编辑时频繁触发死锁我会把同一文档的操作提交串行化比如按docId进行哈希后分片同一个文档的操作路由到同一台实例处理减少跨实例竞争和版本冲突。操作量很大时批量写入和批量广播都很有必要。客户端本地可以做一个100毫秒的聚合窗口把窗口内的操作合并成一个大操作再推送减少网络包数量。但注意聚合窗口不能太长否则交互延迟会超过200毫秒影响打字体验。服务端广播时也可以采用批量发送WebSocket允许在一次send中携带多条消息能明显降低框架层的消息开销。7. 常见问题与排查技巧实录多人协同编辑系统排障比普通项目难因为问题经常是“偶发”“只在多人同时操作时出现”。我整理了实战中遇到的高频问题和排查思路做成速查表。现象可能原因排查方法用户A能看到自己的修改但B看不到跨实例广播未打通或广播时过滤逻辑错误检查Redis频道是否订阅成功在广播代码中加日志打印连接ID和目标连接ID重连后文档内容重复断线期间本地操作未确认重连后既发了增量又发了本地缓存操作重连时先清空未确认操作缓存基于服务端最新版本做合并中文输入时文字重复未处理输入法composition事件检查编辑器是否在composition期间提交操作禁止该阶段发送两个人同时编辑时偶发内容丢失OT转换没有覆盖delete与insert交叉场景打开双方操作日志重放操作序列用OT转换纯函数测试回归服务端CPU高但连接数不多每个操作都触发全量内容序列化广播优化为增量操作广播禁止广播时发送整个文档内容新用户加入很慢每次都回放全部变更集检查快照生成是否正常加入时优先加载快照WebSocket连接频繁断开负载均衡空闲超时配置太短调整LB超时到120秒以上客户端增加心跳保活踢人后用户仍能继续编辑只更新了数据库权限未断开连接踢人时必须主动关闭连接并让协作服务拒绝该用户的后续操作其中“重连后文档内容重复”这个问题我印象最深。最开始设计时客户端在断线期间继续接收用户输入生成了本地操作但没有确认。重连成功后服务端补发了断线期间的增量操作客户端本地把这些增量应用到已有内容上同时又把断线期间产生的本地操作推送给服务端两边都执行了同一批内容导致重复。解决办法是重连成功后先暂停本地编辑操作等服务端增量补齐完毕再拿服务端最新版本与本地内容做一次统一合并合并完成后再恢复可编辑状态。限流也要做。极端情况下某个用户疯狂点击粘贴会产生大量操作打满服务端。限制策略可以按连接维度做令牌桶比如每个连接每秒最多提交50个操作原语超过直接返回429。这不会影响正常打字速度因为普通用户每秒输入的操作通常不超过20个。关于监控我建议重点盯三个指标操作延迟客户端发出到收到confirm的时间、广播成功率、文档版本增长速率。操作延迟超过500毫秒就需要排查网络和Redis性能广播成功率下降通常意味着Redis连接异常版本增长速率异常说明某篇文档正在被密集编辑可以为它单独扩容。最后再补充一个多实例部署时特别容易踩的坑OT转换和版本提交必须保证原子性。如果服务端在应用操作时先更新了版本号再写变更集中途进程崩溃会导致版本号跳跃客户端收到新版本号却找不到对应变更集永久卡在增量同步状态。我的做法是使用数据库事务把版本号更新、变更集插入和文档内容最新状态更新放进同一个事务任何一步失败则整体回滚。这个看似微小的实现细节决定了整个同步链路的一致性边界。根据我个人实际项目的经验真要完整落地一套多人协同编辑系统建议把复杂度控制在自己团队的消化能力范围内。第一版不要追求跨数据中心CRDT和全离线能力先保证同一房间内几十到几百人实时编辑稳定不丢字再逐步扩展。做好操作原语化、版本管理、OT转换和会话恢复这四个核心点这个系统就能支撑起主流的在线文档协作场景了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →