
如果你用过任何需要多设备协作的软件你一定遇到过这种情况电脑上写了一篇 3000 字的笔记改完前三段后保存关闭。通勤路上掏出手机发现开头有个错别字改完之后又顺手在结尾补了两段新的内容。晚上回到家打开电脑——弹出一个冲突提示你得手动决定保留哪个版本。这个体验的根源在于传统同步工具把文件当作一个整体来处理。电脑说我改了文件 A手机说我也改了文件 A——同步引擎看着两个文件 A不知道你们改的是不同段落它只能汇报冲突你来解决。Nutstore Sync 1.4.0版引入的 Yjs CRDT 合并引擎从根本上改变了这个局面。它不再把文件当成黑盒而是追踪每一次编辑操作。你在电脑上改前三段手机上改后三段——这两个操作在结构上互不重叠Yjs 能自动识别并合并完全不需要你介入。这篇文章会从 CRDT 的技术原理讲起用最通俗的方式然后通过三个实测场景验证 Yjs 智能合并的实际表现最后说明它和传统同步方案在架构层面的本质差异。开始之前说一句Nutstore Sync是坚果云官方Obsidian同步插件是免费的每个月都有免费的1G上传流量3G下载流量。坚果云已经稳定运行了15年可以放心使用插件通过坚果云账号登录如果你还没有账号可以先注册一个开始体验坚果云官网。传统同步的架构性缺陷从三个根因说起在解释 Yjs 做了什么之前先要理解传统同步方案为什么在多设备同时编辑这个场景下永远做不好。这背后有三个架构层面的根本问题。根因一文件即原子单元传统的文件同步——无论是 Dropbox、OneDrive 还是基于 WebDAV 的 Remotely Save——在底层的同步模型是一致的以文件为最小同步单位。一个文件被修改后系统做的事情是计算文件的哈希值或比较修改时间戳 → 发现与云端版本不同 → 标记为需要同步 → 上传整个文件或上传差异块。这个模型在同一时间只有一台设备修改文件时没有任何问题。但当两台设备同时修改同一个文件时问题出现了——同步引擎只知道文件变了不知道文件里哪一段变了、谁变的、什么时候变的。它能做的只有三件事用最新版本覆盖丢失旧修改、生成冲突副本让你手动合并、或者干脆报错。这不是某家公司的技术能力问题而是以文件为原子单元的同步模型在面对并发编辑时的结构性局限。根因二编辑操作被压缩了你在一篇笔记上的编辑其实是一连串的操作序列在第 3 行插入一段文字→删掉第 5 行的句子→把第 10 行的列表改成标题→在第 20 行之后新增三个段落。但当你按下 CtrlS或 Obsidian 自动保存时所有这些操作被压缩成了一个结果——“新的文件内容”。操作序列消失了。另一台设备收到这个新的文件内容后不知道你经历了哪些中间步骤也不知道哪些改动是重要的、哪些是临时的。Git 之所以能比 Dropbox 更好地处理冲突就是因为它保留了操作序列——每次 commit 都是一个明确的变更记录。但 Git 是版本控制系统不是实时同步系统。在实时同步场景中保留操作序列需要代价——更大的元数据开销、更复杂的合并逻辑。大多数同步工具选择了不保留操作序列这条更简单的路。根因三冲突检测宁可误报绝不漏报传统同步方案在处理两台设备同时修改同一个文件时有一条默认的安全策略——宁可误报冲突也不能静默丢失任何一方的修改。这听起来很合理。但合理意味着即使你只改了文件的第一段、我只改了文件的最后一段两个修改之间隔着 2000 字毫无关联的内容系统仍然会标记为冲突。这就是为什么你明明在手机和电脑上改的是完全不同的段落还是会看到那个恼人的冲突提示。Yjs CRDT 的核心思想操作而不是结果CRDT 的全称是 Conflict-free Replicated Data Types无冲突复制数据类型。名字很学术——但它做的事情其实可以用一个很简单的类比来理解。从 Google Docs 说起打开一个 Google Docs 文档邀请一个同事同时编辑。你在开头写了一段导语同事在结尾加了一段总结。你们不需要等对方写完——可以同时编辑。Google Docs 不会弹出冲突提示不会问保留谁的版本你们的修改会无缝地合并到同一个文档中。Google Docs 能做到这一点是因为它底层追踪的不是文件的最终内容而是每个人的每一次编辑操作——“用户 A 在位置 5 插入了字符串 ‘hello’”“用户 B 在位置 200 删除了 3 个字符”“用户 A 在位置 50 插入了一个换行符”。Yjs 是一个实现了类似机制的 JavaScript 库。它把文档表示为一连串的操作单元每个操作单元都有三个关键属性唯一标识ID每个操作有一个全局唯一的 ID由客户端 ID 和递增的时钟值组成位置信息这个操作在文档的什么位置发生操作内容插入什么字符、或者删除多少个字符当两台设备各自产生了一组编辑操作后Yjs 不是比较两个最终文件的差异而是把两组操作合并到一起然后重放。因为每个操作都有唯一 ID 和精确的位置信息Yjs 能判断哪些操作是可以安全合并的修改不同位置哪些是存在真正冲突的修改同一位置。改不同段落 自动合并改同一条句子 标记冲突这里给出 Yjs 判断冲突的精确逻辑当两个编辑操作影响的是文档的不同区域它们就是不冲突的——可以被安全地合并。比如你在段落 3 插入了一句话我在段落 7 删除了两句话。这两个操作在文档结构中完全独立合并后的结果在数学上是唯一确定的。当两个编辑操作影响的是文档的同一个区域就存在真正的冲突。比如说你和我都修改了同一句话——你把产品将在下周三发布改成了延期到下周五我把同一句话改成了提前到下周一——Yjs 无法判断哪个版本是正确的。这时候它不会自作主张而是标记冲突把最终判断交给你。在 Nutstore Sync 中当 Yjs 标记冲突后插件会使用 Diff3 算法生成 Git 风格的合并标记让你在 Obsidian 编辑器中手动处理。最妙的是——内置 AI 可以分析两边的差异给出合并建议。这个AI 辅助冲突判决的能力建立在 Nutstore Sync 的 AI 板块之上是目前其他同步插件完全不具备的。用一个简单的例子感受 Yjs 的智能假设原始文档是一首四行的打油诗“春眠不觉晓处处闻啼鸟。夜来风雨声花落知多少。”情况一不同位置修改自动合并设备 A把第一句改成春眠不觉晓处处蚊子咬。设备 B把第四句改成花落全没了。Yjs 检测到修改位置不同第一句 vs 第四句自动合并结果“春眠不觉晓处处蚊子咬。夜来风雨声花落全没了。”——零冲突。情况二同一位置修改标记冲突设备 A把第一句改成春眠不觉晓处处蚊子咬。设备 B把第一句改成春眠不觉晓处处有飞鸟。Yjs 检测到同一位置被两边修改标记冲突。插件生成 Diff3 标记让你选择哪个版本或者手动合并成一句新诗。这个简单的例子揭示了 Yjs 设计的核心原则能自动处理的不打扰用户真正无法判断的交给用户决定不替用户做选择题。三个实测场景Yjs 到底有多智能下面用三个接近真实使用场景的测试来验证 Yjs 智能合并的实际表现。实测一电脑改三段 手机续两段 零冲突场景设置一篇技术方案文档总共 15 个段落。电脑上修改了前三个段落的内容补充数据、调整措辞手机上在文档末尾新增了两个段落补充了实施步骤。两台设备的修改时间相差 3 分钟。传统方案的表现因为文件被两台设备都修改了触发时间戳冲突。如果以最新时间戳覆盖电脑上改的前三段丢失。如果以本地保留手机上新增的两段消失。最终你不得不手动把两边的修改拼接起来——打开两个版本、找出差异、逐段合并。Nutstore Sync Yjs 的表现Yjs 将电脑上的前三段修改操作和手机上的末尾新增操作识别为互不重叠。合并过程完全自动化不需要任何人工介入。同步完成后打开笔记——前三段是新修改的内容末尾两段是手机上新增的——完整且正确。这个场景之所以重要是因为它是多设备编辑中最常见的情况——你在不同设备上编辑的是同一篇文档的不同部分。传统方案对这种场景的一刀切冲突是误报Yjs 则实现了精准的差异识别。实测二一个人在手机上调格式 一个人在电脑上改内容 各自无感场景设置一篇会议纪要长度约 2000 字。同事 A 在电脑上修改了若干段落的措辞和补充了数据。同事 B 在手机上把几处加粗标题改成了 Markdown 标题格式#语法。两个修改交叠但作用在不同的位置和不同的语义层级上。Yjs 的表现因为格式修改加粗改标题和内容修改措辞和数据在文档中的操作位置不同Yjs 自动合并。结果是内容更新 格式同步更新——两边的工作都没有丢失。关键洞察如果格式和内容修改恰好作用在同一行比如同事 A 把某行文字改成了加粗同事 B 同时把同一行文字的内容改了Yjs 会将这个冲突标记出来。但实际工作中格式调整和内容重写很少发生在完全相同的字符位置所以自动合并的概率远高于标记冲突。实测三三设备并发编辑——极限压力测试场景设置一台台式机、一台笔记本、一台手机三方同时对同一篇 5000 字的长文进行修改。台式机改第一部分笔记本改第二部分手机改第三部分。三方修改之间没有重叠。三台设备同时间隔在 5 秒内触发同步。Yjs 的表现三方的编辑操作在坚果云服务端被收集Yjs 引擎在后台完成合并。所有设备拉取合并结果后文档包含三方全部修改零冲突零人工介入。为什么能做到Yjs 的 CRDT 算法保证了一个性质——不管操作以什么顺序到达、在哪台设备上先被处理最终的合并结果总是确定的、一致的。这个性质在分布式系统中被称为强最终一致性Strong Eventual Consistency。它是 Yjs 能处理三设备甚至更多设备并发编辑的数学基础。五种同步方向 Yjs 合并不只是自动是可预测的自动Nutstore Sync 的同步机制有一个独特的设计——每次手动同步前你可以从五种同步方向中选择一个双向同步默认上传本地变更下载云端变更两边合并仅发送只上传本地变更不下载云端变更仅发送并覆盖云端变更强制以本地版本覆盖云端仅接收只下载云端变更不上传本地变更仅接收并还原本地变更强制以云端版本覆盖本地五种方向和 Yjs 智能合并的关系是什么答案是Yjs 合并只在双向同步模式下工作。如果你选择了单向方向仅发送或仅接收就跳过了合并过程直接以某一端为准。这个设计的价值在于可预测性。日常使用时默认双向同步 Yjs 智能合并让你享受自动化的便利。但在某些特殊场景下——比如你刚在一台离线设备上做了大量修改回到在线状态后想以本地为准覆盖所有云端变更——你可以切换到单向模式完全控制。每次手动同步前Nutstore Sync 会弹出一个确认框显示本次同步的执行列表哪些文件将上传、哪些将下载、哪些发生冲突以及 4 项操作提醒。这个设计让自动不是不可见的自动而是经过你确认的自动——这是它和 Remotely Save 等通用同步工具在用户体验上的一个重要差异。Remotely Save 没有执行列表预览点击同步后你只能等它跑完中间无法知道哪些文件被处理、处理结果如何。Yjs 智能增量同步为什么改一个字只传一个字Yjs 智能合并带来一个隐形的性能收益——它和坚果云底层的智能增量同步技术高度互补。传统文件同步在检测到文件变更后至少需要传输整个文件或整个压缩块。如果一个 2MB 的笔记文件因为只修改了 10 个字就需要重新上传整个文件在弱网环境下体验会很糟糕。Yjs 改变了这个问题。因为它追踪的是编辑操作而不是文件内容——你改了 10 个字Yjs 传输的就是这 10 个字的操作数据通常只有几十到几百字节而不是整个 2MB 的文件。坚果云的智能增量同步引擎在此基础上做了第二层优化即使是二进制文件如图片、PDF也只传输发生变化的文件块而非整个文件。两层优化叠加——文本文件通过 Yjs 的操作级增量传输二进制文件通过坚果云的块级增量传输——让 Nutstore Sync 在弱网和移动网络下的传输效率远超仅做文件级比较的方案。用数字说话假设一个典型的 Obsidian vault 有 500 个 Markdown 文件总计 50MB。你在手机上修改了其中 3 个文件的各几段内容新增了约 500 字。传统同步需要重新比较 500 个文件的时间戳或者检测整个 vault 的变更。Yjs 智能增量同步只需要传输那 500 个字的操作数据——实际传输量可能不到 5KB。效率差距是 1000 倍量级的。对比 Remotely Save为什么它做不到 Yjs 级别的合并Remotely Save 是 Obsidian 社区中知名度较高的通用同步插件。它支持 WebDAV、S3、OneDrive 等多种云存储后端。但在冲突合并这件事上它和 Nutstore Sync 存在结构性的能力差距。以下是两者的核心差异对比维度Nutstore SyncYjs 引擎Remotely Save冲突检测粒度逐操作字符级逐文件文件级不同段落修改自动合并零冲突标记为冲突生成冲突副本同一位置冲突Diff3 标记 AI 辅助合并生成冲突副本手动合并合并引擎Yjs CRDT操作级重放无合并引擎仅文件比较传输粒度操作级改多少传多少文件级或块级并发设备数支持任意数量CRDT 数学保证理论上支持但冲突率随设备数上升这个差距的根源不是 Remotely Save “做得不好”而是它的架构定位决定了它不可能做到 Yjs 级别的合并。Remotely Save 的设计目标是把 Obsidian vault 的文件同步到各种云存储后端。要支持 WebDAV、S3、OneDrive、Dropbox 等不同的后端它必须将对后端的操作抽象到最低的共同标准——文件的上传和下载。而 Yjs 需要后端能存储和分发操作单元这要求后端有特定的数据结构和 API 支持。简单说Remotely Save 是通用适配方案追求后端兼容性Nutstore Sync 是垂直集成方案追求最佳体验。两种设计思路各有道理但在多设备并发编辑不冲突这件事上垂直集成方案有通用适配方案无法跨越的优势——Yjs 引擎需要后端的深度配合。不要混淆Yjs 自动合并 ≠ 永远没有冲突在结束之前有必要澄清一个容易被误读的点。Yjs 智能合并的意义不是消除冲突而是只在你真正需要介入的时候才让你介入。有三类情况仍然需要你手动处理1. 语义冲突你和我改了同一句话但表达了完全相反的意思。Yjs 标记冲突因为它无法判断哪个版本更正确——这需要人的判断。2. 结构性冲突你删除了一个段落我在同一个段落中修改了内容。Yjs 会标记冲突——因为它无法确定你是想删除整个段落还是删除原段落但保留修改。3. 格式 内容的同位置冲突你把某段文字改成加粗我把同一段文字的内容全改了。如果加粗的标记和文字修改作用在完全相同的字符范围上Yjs 会标记冲突。好消息是这三类情况在实际使用中的占比很低。根据分布式协作系统的实践经验大约 85%-90% 的并发编辑属于不同位置修改可以被 Yjs 自动合并。剩下的 10%-15% 才需要人工介入——而这个介入也因为 Diff3 标记和 AI 辅助判决的存在变得比过去高效得多。QAQ1Yjs 的自动合并会不会合并错比如把两段不相干的文字拼在一起不会。Yjs 合并的确定性由 CRDT 的数学性质保证——相同的一组操作不管以什么顺序在哪些设备上处理最终结果是一致的。它不会猜你的意图只是严格地按照操作位置进行合并。唯一可能出现你不想要的结果的情况是语义冲突两个人在同一位置写了矛盾的内容这种情况 Yjs 会标记冲突让你处理而不是偷偷合并。Q2如果我在没有网络的情况下编辑了很久恢复网络后 Yjs 还能正确合并吗能。Yjs 的操作是持久化的——你离线时产生的编辑操作会保存在本地恢复网络后按序上传。CRDT 算法不依赖操作到达服务器的顺序所以离线的时长不影响合并结果的正确性。Q3Yjs 的合并是否依赖坚果云服务端如果服务端暂时不可用会怎样Yjs 的操作在本地产生和暂存不依赖服务端的实时参与。服务端主要用于操作的中转和持久化存储。如果服务端暂时不可用你的本地编辑不受影响——操作会排队等待服务端恢复后再上传。Q4Yjs 和 Nutstore Sync 中的四种冲突策略是什么关系Yjs 是第一层——自动处理不同位置的操作合并。当 Yjs 无法自动合并同一位置冲突时才会进入你设置的冲突策略——Diff3 合并生成标记或者本地优先/服务器优先覆盖。可以把 Yjs 理解为尽力自动合并冲突策略是自动失败后的兜底方案。Q5多人同时编辑 Obsidian 的 Canvas 白板文件Yjs 能处理吗Canvas 文件本质上是 JSON 格式。Nutstore Sync 在底层使用 Yjs 处理 JSON 结构的合并——你在白板上新增一个节点同事移动另一个节点这两个操作通常能被自动合并。但如果两个人同时修改同一节点的同一属性比如同时拖动同一张卡片就会触发冲突。Q6Yjs 会增加插件的内存占用吗Yjs 需要在内存中维护文档的操作历史用于合并计算。对于常规的 Obsidian 笔记几千到几万字的 Markdown 文件内存开销可以忽略不计。只有当你编辑超大型文件比如几十万字的纯文本时内存占用才会变得显著——但这类文件在 Obsidian 的典型使用场景中很少见。Q7我怎么知道 Yjs 已经自动合并了一些修改而不是漏掉了每次同步完成后Nutstore Sync 会显示同步结果摘要包括成功合并的文件数标记冲突的文件数等。你也可以在故障排查标签中查看冲突异常汇总看到每一次合并自动或手动的记录。透明性是你的——自动不代表不可见。Q8如果我一定要手动处理所有冲突能关掉 Yjs 自动合并吗你可以选择仅接收并还原本地变更或仅发送并覆盖云端变更作为同步方向——这两种模式下不触发合并。或者在冲突策略中选择本地优先覆盖服务器或服务器优先覆盖本地——这会让 Yjs 在遇到冲突时直接按你指定的方向覆盖而不是标记冲突。完全的手动控制是可能的但大多数用户会发现让 Yjs 先自动处理不重叠的修改、只把真正的冲突交给你——是效率和掌控力的最佳平衡点。