——送花的人走了 https://github.com/yjs/yjs https://github.com/yjs/yjs 在构建实时协作应用时,我发现了一个非常强大的开源库——Yjs。 Yjs 能够同步不同客户端的共享数据状态,即使在网络断开或冲突的情况下,所有更改都可以无缝合并,类似于 Google Docs 这样的多人协作体验。 什么是 Yjs? Yjs 具有极高的性能优化和扩展性,可以集成到各种应用场景中,例如实时文本编辑器、协作画板、表单、项目管理工具等。 Yjs 的核心功能 实时协作 支持多用户同时编辑共享数据。 安装 你可以通过 npm 或 yarn 安装 Yjs: npm install yjs 或者: yarn add yjs 2. 实时协作表单 在多人填写同一表单时,Yjs 可以确保数据一致性和同步。 存储与持久化 为了支持断线重连和离线编辑,Yjs 可以将数据持久化到本地存储。
最近使用 Yjs 给自己开源的一个思维导图加上了协同编辑的功能,得益于该框架的强大,一直觉得很复杂的协同编辑能力没想到实现起来异常的简单,所以通过本文来安利给各位。 关于 OT 和 CRDT 更详细的原理我也不会,各位可以搜索一下相关的文章,接下来看一下我是如何通过 Yjs 实现协同编辑的,先来看一下最终效果: 安装 首先安装Yjs: npm i yjs 另外Yjs 感知数据完全可以你自己来传输,但是Yjs也提供了这个能力,每个Connection Provider都支持传输感知数据,使用起来同样非常简单。 总结 本文详细介绍了我是如何使用Yjs给一个思维导图加上协同编辑的能力,可以看到使用Yjs实现协同编辑整体逻辑是非常简单清晰的,对于原有代码逻辑的入侵也非常小,只要做一下数据结构的转换工作和感知数据的渲染即可 ,所以Yjs非常适合个人开发者或小团队。
Yjs是基于CRDT(Conflict-free replicated data type 维基百科) 实现的协同库。 Yjs 对使用者提供了如 YText、YArray 和 YMap 等常用数据类型(即所谓的 Shared Types),下面是一个简单的demo: import * as Y from 'yjs' Yjs 那么,协同文档中又是如何接入yjs呢? 因为不⽤ document.execCommand,⾃主实现了文档操作。 我们拿到原子操作后,如何转换为yjs的共享数据(sharedType)类型呢? yjs也提供了redo接口,但是目前有些问题在,比如回撤以后重复,而且它没有独立的撤销栈,所以我们使用的另一套回撤实现。
文章大纲 多人协同技术方案探讨 OT和CRDT算法 插曲(互斥锁(Mutex)原理和代码实现) yjs协同框架使用 yjs多人协同案例 多人协同技术方案探讨 多人协同技术方案常见的应用场景主要有: 原型工具 Yjs 是专门为在 web 上构建协同应用程序而设计的CRDT. 中间内容的更新是基于 Yjs 数据结构进行的,冲突处理等核心都是 Yjs 承担的,通信基于 websocket 或 webrtc,所以我们只需要简单的使用就可以实现多人协同的应用。 版本历史支持 Yjs 自身提供了快照机制,保存历史版本不用保存全量数据,只是基于 Yjs 打一个快照,后续基于快照恢复历史版本。 系统编辑人数上限 上限人数很高,可支持很多人同时编辑。 yjs使用 以上我根据自己的理解整理了一下yjs的核心模块。
等等,本文就是关注于以yjs为CRDT协同框架来实现协同的实例。 或许上边的一些概念可能一时间让人难以理解,所以下面的Counter与Quill两个实例就是介绍了如何使用yjs实现协同,究竟如何通过数据结构完成协同的接入工作,当然具体的API调用还是还是需要看yjs的文档 接下来我们要进行数据结构的设计,目前在yjs中是没有Y.Number这个数据结构的,也就是说yjs没有自增自减的操作,这点就与前边OT实例不一样了,所以在这里我们需要设计数据结构。 因此为了冲突的解决yjs并没有真正的删除Item,而是采用了标记的形式,即删除的Item会被加入一个deleted标记,那么不删除会造成一个明显的问题,空间的占用会无限增长,因此yjs引入了墓碑机制,当确认了内容不会再被干涉之后 / https://github.com/yjs/yjs https://github.com/automerge/automerge https://zhuanlan.zhihu.com/p/425265438
技术栈基于 React + WebSocket + Yjs CRDT(无冲突复制数据类型),目标是打造一个轻量、开源、可嵌入的协作画布。 前两个月进展顺利:基础绘图、撤销重做、光标同步都实现了。 更糟的是,Yjs 的文档对“大规模并发”场景几乎只字未提。我在 GitHub Issues 里翻了三天,只看到一句:“Yjs is not magic. (Yjs 不是魔法,你需要合理批处理更新。) 可“合理”到底是什么?没人说清楚。 项目停滞了整整两周。投资人问进度,我只能苦笑:“技术债堆太高,快还不起了。” 我在代码顶部写下一段近乎自言自语的注释: // TODO: 当多人高频编辑时,Yjs 同步性能急剧下降。 原来问题不在 Yjs 本身,而在于我们把每个微小操作都当作独立事务,导致频繁触发 CRDT 合并与 React 重渲染。
三、界面展示 四、功能概述 基于 Tiptap + Yjs 构建的 AI 写作平台 DocFlow AI 智能写作平台 基于 Tiptap + Yjs 构建的新一代智能协作编辑器,集成 AI 续写、RAG 功能特性 富文本编辑:标题、列表、表格、代码块、数学公式、图片、拖拽等 实时协作:使用 Yjs + @hocuspocus/provider 实现高效协同 插件丰富:基于 Tiptap Pro 多种增强功能 九大核心功能模块,构建完整的 AI 驱动内容创作生态系统 沉浸式编辑器 基于 Tiptap 打造的下一代编辑体验,支持 Markdown 快捷键、块级编辑、拖拽排版,让写作如行云流水般自然 实时协作引擎 基于 Yjs 协同编辑核心,CRDT 数据结构 @hocuspocus Yjs 的服务端与客户端 Provider React 19 UI 框架,支持 Suspense 等新特性 Tailwind CSS 原子化 、依赖注入、装饰器和类型安全等特性 HTTP 服务 Fastify 高性能 Web 服务引擎,替代 Express,默认集成于 NestJS 中 协同编辑服务 @hocuspocus/server, yjs
4.2 技术选型:Yjs与YATA模型 在众多 CRDT 实现中,Yjs 以其成熟性和完整性脱颖而出。 本地操作通过 addOpData方法序列化并 push 至该数组,由Yjs负责同步,利用数组有序性保证各客户端日志一致。 实现机制:各客户端通过 Yjs 提供的 Awareness API 广播自身状态并接收他人状态,最终在UI层实时渲染用户光标、选中态等视觉反馈。 为解决这一难题,系统基于 Yjs UndoManager 构建了客户端粒度的撤销重做机制,其核心设计原则为:仅回退当前客户端自身发起的操作,避免干扰其他协作者。 框架与实战 Yjs 官方文档 https://docs.yjs.dev/ Yjs 深度解析 https://www.tag1consulting.com/blog/yjs-deep-dive-part
---- 推荐一个8K star的前端 CRDT 实时协作库 Yjs: https://github.com/yjs/yjs Yjs 主要的内部特点: 基于双向链表和 StructStore 的基础数据结构
它有自己的开源实现Yjs(https://github.com/yjs/yjs) 以下内容主要参考自论文:Near Real-Time Peer-to-Peer Shared Editing on Extensible 为了降低用户使用协作框架的成本,尤其是提供直接在浏览器中可运行的程序,论文的作者提出了“YATA”(基于CRDT思想的协作算法),并提供了开源实现Yjs。 后半部分论文主要描述了Yjs的性能表现及当前已经使用Yjs的一些产品,本文就不再详述了。 总结 文中的内容主要翻译论文和少量的个人理解,感兴趣的同学建议在阅读本文之后再去完整读一遍论文。 本文我们主要介绍YATA实现的基本思路,作为CRDT类型的协作算法,YATA及其实现库Yjs有很好的性能表现,而且支持点对点传输,为我们实现非客户端-服务器模式提供了理论基础和实践方案。 我们也会在后续的项目中落地基于Yjs的协作方案。 最后,我也在学习和使用Yjs中,欢迎大家私信一起探讨YATA和Yjs的相关技术。
\tag{1} (xisyjs)=Tθ(G(i,j))=Aθ⎝⎛xityjt1⎠⎞=⎣⎡θ11θ12θ13θ21θ22θ23001⎦⎤⎝⎛xityjt1⎠ 一般来说 ( x i s , y j s ) (x_i^s,y_j^s) (xis,yjs)都是小数,因此我们需要通过插值的方式来取得输入图像的像素值,从而作为相对应的输出feature map的坐标 \tag{2} Vi,jc=n∑Hm∑WUn,mc⋅k(xis−m;Φx)k(yjs−n;Φy)∀i,j∈[1⋯H′W′],∀c∈[1⋯C].(2)其中 V i , j c V_{i,j ),式(4)中的距离”1“就是为了控制待插值点 ( x i s , y j s ) (x^s_i,y_j^s) (xis,yjs)像素值只用周围4个点来控制,当 ( x i s , y j s ) );对照 ( x i s , y j s ) (x^s_i,y^s_j) (xis,yjs)在 U U U中采样获得 V V V。
Yjs.java代码为: ? Bks.java代码为: ? 建立POJO类对应的映射文件。 Yjs.hbm.xml配置文件为: ? ? 在TEST数据库中创建表,设计xs表(见表8.5)、yjs表(见表8.6)、bks表(见表8.7)以及它们的关联关系如下。 这种情况下只需要配置一个Xs.hbm.xml即可,在该配置文件中要把Yjs与Bks的信息配置出来。 对数据的存取: ? 执行程序后,会把xh、xm、bir记录插入xs表中,把本条记录的id值及researchResult记录插入到yjs表中。 ? 运行该段代码后,程序会根据Yjs类设置Xs表中的xsType值为“yjs”,并把值插入到Xs表中。 ?
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid
{ "apps": [], "details": [ { "appID": "7YJS2UUTF5
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid 33568' PSINO: '5' PSTM: '1610459846' ZD_ENTRY: baidu __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid =A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid
=A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid 33568' PSINO: '5' PSTM: '1610459846' ZD_ENTRY: baidu __yjs_duid 123253", "BIDUPSID": "898B917A8EF92E036B0F06DC792638A1", "__yjs_duid =A40AD6AD806FBBED1033903732FFA453:FG=1; BD_UPN=123253; BIDUPSID=898B917A8EF92E036B0F06DC792638A1; __yjs_duid