Go 协作文档冲突解决:OT 算法和 CRDT 的并发编辑实现
Go 协作文档冲突解决OT 算法和 CRDT 的并发编辑实现一、两个人同时改同一行保存后其中一个人的修改丢了协作文档类似 Google Docs/飞书文档的核心技术挑战是并发编辑冲突。当用户 A 在第 5 行插入项目延期了用户 B 正在第 5 行删除进度正常两个操作几乎同时到达服务器。如果简单按到达顺序处理必然有一个人的操作被覆盖。解决这个问题的经典方案有两种OTOperational Transformation操作变换和 CRDTConflict-free Replicated Data Type无冲突复制数据类型。OT 通过变换操作保证一致性CRDT 通过数据结构设计保证操作可交换。两者在工程上有不同的取舍。二、OT 算法的核心原理OT 的核心想法是当两个并发操作冲突时不是拒绝其中一个而是变换其中一个操作使其在另一个操作之后执行仍能产生正确的结果。OT 的核心是transform(op1, op2)函数它对操作做位置偏移变换。这个算法在 Google Docs 中使用多年成熟稳定。三、Go 实现简化的 OT 引擎package ot import ( fmt sync time ) // OpType 操作类型 type OpType int const ( OpInsert OpType iota OpDelete ) // Operation 编辑操作 type Operation struct { Type OpType json:type Position int json:position // 操作位置 Content string json:content // 插入的文本Delete 时为空 Length int json:length // 删除的长度Insert 时为0 UserID string json:user_id Timestamp int64 json:timestamp Revision int json:revision // 基于的版本号 } // Document 协作文档 type Document struct { mu sync.RWMutex content string revision int history []*Operation } // NewDocument 创建文档 func NewDocument(content string) *Document { return Document{ content: content, revision: 0, history: make([]*Operation, 0), } } // Apply 应用一个操作到文档 func (d *Document) Apply(op *Operation) error { d.mu.Lock() defer d.mu.Unlock() // 版本检查 if op.Revision ! d.revision { return fmt.Errorf( 版本冲突: 期望 %d, 当前 %d, op.Revision, d.revision, ) } switch op.Type { case OpInsert: if op.Position 0 || op.Position len(d.content) { return fmt.Errorf(插入位置越界: %d, op.Position) } d.content d.content[:op.Position] op.Content d.content[op.Position:] case OpDelete: if op.Position 0 || op.Positionop.Length len(d.content) { return fmt.Errorf(删除范围越界) } d.content d.content[:op.Position] d.content[op.Positionop.Length:] } d.revision d.history append(d.history, op) return nil } // Transform 变换两个并发操作 // 返回变换后的 op2使 op2 在 op1 之后仍正确 func Transform(op1, op2 *Operation) (*Operation, error) { if op1.Position op2.Position { // op1 在 op2 之后不影响 op2 的位置 return op2, nil } transformed : Operation{ Type: op2.Type, UserID: op2.UserID, Timestamp: op2.Timestamp, Revision: op2.Revision, } switch op1.Type { case OpInsert: // op1 在 op2 前面插入op2 的位置需要后移 transformed.Position op2.Position len([]rune(op1.Content)) transformed.Content op2.Content transformed.Length op2.Length case OpDelete: if op1.Positionop1.Length op2.Position { // op1 删除的内容完全在 op2 前面 transformed.Position op2.Position - op1.Length } else if op1.Position op2.Positionop2.Length { // op1 删除的内容完全在 op2 后面不影响 transformed.Position op2.Position } else { return nil, fmt.Errorf( 操作冲突: op1删除范围与op2重叠, ) } transformed.Content op2.Content transformed.Length op2.Length } return transformed, nil } // OTEngine OT 引擎服务端 type OTEngine struct { documents sync.Map // docID - *Document } // NewOTEngine 创建 OT 引擎 func NewOTEngine() *OTEngine { return OTEngine{} } // HandleOperation 处理客户端发来的操作 func (e *OTEngine) HandleOperation( docID string, op *Operation, ) (*Operation, bool, error) { docInterface, _ : e.documents.LoadOrStore( docID, NewDocument(), ) doc : docInterface.(*Document) doc.mu.Lock() // 检查是否需要变换 if op.Revision doc.revision { // 客户端的版本落后需要对操作做变换 pendingOps : doc.history[op.Revision:] for _, pendingOp : range pendingOps { var err error op, err Transform(pendingOp, op) if err ! nil { doc.mu.Unlock() return nil, false, fmt.Errorf( 操作变换失败: %w, err, ) } } op.Revision doc.revision } // 应用操作 if err : doc.Apply(op); err ! nil { doc.mu.Unlock() return nil, false, err } doc.mu.Unlock() // 返回变换后的操作用于同步给其他客户端 return op, true, nil } // GetContent 获取文档内容 func (e *OTEngine) GetContent(docID string) string { docInterface, ok : e.documents.Load(docID) if !ok { return } doc : docInterface.(*Document) doc.mu.RLock() defer doc.mu.RUnlock() return doc.content }四、边界分析与 Trade-offsOT vs CRDT 的选择OT 需要中心服务器Google Docs 模式适合对一致性要求极高的文档协作。CRDT 支持离线编辑后再同步Figma 模式适合需要离线能力的场景。但 CRDT 的存储开销大需要保留所有历史操作且某些复杂操作如富文本格式的 CRDT 实现极其复杂。企业文档协作场景下 OT 是更务实的选择。操作粒度的影响按字符做 OT 变换会导致操作极多一次粘贴可能产生几百个 Insert 操作。实际使用中通常按词或块做操作合并——客户端将连续插入合并为一个操作后再发给服务端。但这可能导致变量——如果两个用户在不同位置修改同一个词块仍然需要 OT 处理。版本管理的存储增长OT 的历史操作列表会无限增长。可以定期如每天做快照——将当前文档内容保存为新的基线版本清理之前的操作历史。下一个版本从快照开始重新计数。网络延迟下的用户体验OT 要求操作要先过服务端才能看到效果这在网络延迟大时体验很差。优化客户端本地先套用操作乐观更新服务端确认后再修正。如果服务端变换后的结果和客户端不同再做本地修正——这就是 Google Docs 的用户体验设计。五、总结OT 算法的核心是transform(op1, op2)函数它的作用是如果两个操作同时发生变换其中一个使两者都能正确执行。代码实现上要注意位置偏移的计算Insert 导致位置增加Delete 导致位置减少以及对重叠删除的冲突处理。Go 语言实现 OT 的优势是并发安全sync.Mutex 保护好文档的临界区和性能不需要处理复杂的异步回调。如果要从零实现协作文档建议从纯文本 OT 开始跑通后再扩展到富文本——那是另一个维度的复杂度。

相关新闻

AI Agent技术栈解析与实战应用指南

AI Agent技术栈解析与实战应用指南

1. 2026 Agent元年的行业背景解读最近两年AI领域最令人兴奋的突破,莫过于大语言模型(LLM)从单纯的文本生成工具,进化成为能够自主规划、执行复杂任务的智能体(Agent)。这种进化不是简单的功能叠加&#xff…

2026/10/20 23:34:01 阅读更多 →
TMS570LS0714安全MCU热阻、时钟与电源管理设计实战

TMS570LS0714安全MCU热阻、时钟与电源管理设计实战

1. 项目概述与核心价值 在汽车电子、工业控制这类对可靠性要求极高的领域,选型一颗微控制器(MCU)远不止是看它的主频和内存。真正决定系统能否在严苛环境下长期稳定运行的,往往是那些数据手册里看似枯燥的参数:芯片的散…

2026/10/21 23:26:20 阅读更多 →
YOLOv8+OpenCV实现手机屏幕划痕检测系统

YOLOv8+OpenCV实现手机屏幕划痕检测系统

1. 项目背景与核心价值手机屏幕作为人机交互的核心部件,其表面质量直接影响用户体验。在手机生产线上,屏幕划痕检测一直是质检环节的难点——传统人工目检效率低(每人每天最多检测2000台)、漏检率高(约3%-5%&#xff0…

2026/10/22 6:01:04 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/22 19:06:46 阅读更多 →