在线协同技术调研
2023-07-19 15:19:58 1 举报
AI智能生成
在线协同技术调研
作者其他创作
大纲/内容
技术理解
Automerge(以及 Yjs 和其他 CRDT)将共享文档视为字符列表。 文档中的每个字符都有一个唯一的 ID,每当您插入到文档中时,您都会为要插入的内容命名。
Automerge (RGA) 的行为由该算法定义:
构建树,将每个Item连接到其父项
当一个项目有多个子项时,先按序列号再按 ID 对它们进行排序。
结果列表(或文本文档)通过使用深度优先遍历把树打平制作。
构建树,将每个Item连接到其父项
当一个项目有多个子项时,先按序列号再按 ID 对它们进行排序。
结果列表(或文本文档)通过使用深度优先遍历把树打平制作。
有严重的性能问
编辑轨迹有 260 000 次编辑,最终文档大小约为 100 000 个字符。
正如我上面所说,automerge 需要不到 5 分钟的时间来处理此跟踪。 这只是每秒 900 次编辑,这可能没问题。 但是当它完成时,automerge 正在使用 880 MB 的 RAM。 哇! 这是每次按键 10kb 的内存。 在高峰期,automerge 使用了 2.6 GB 的 RAM!题
编辑轨迹有 260 000 次编辑,最终文档大小约为 100 000 个字符。
正如我上面所说,automerge 需要不到 5 分钟的时间来处理此跟踪。 这只是每秒 900 次编辑,这可能没问题。 但是当它完成时,automerge 正在使用 880 MB 的 RAM。 哇! 这是每次按键 10kb 的内存。 在高峰期,automerge 使用了 2.6 GB 的 RAM!题
Yjs 不需要整篇博文来讨论如何使其更快,因为它已经非常快了,我们很快就会看到。
Yjs 只是将所有Item放在一个单一的列表中:
Yjs 只是将所有Item放在一个单一的列表中:
yjs 算法优化,以及性能也优化好了
每一个客户端一个数据结构
富文本
协同编辑是构建在富文本编辑器之上的技术,它的实现一定程度上依赖于富文本数据模型的设计,这里介绍两个比较有代表性的数据模型:
2012 年 Quill -> Delta (github 35K)
数据结构图
定义三种操作(insert、retain、delete),编辑器产生的每一个操作记录都保存了对应的操作数据,然后用一些列的操作表达富文本内容,操作列表即最终的结果
2016 年 Slate -> JSON (github 26.8k)
数据结构
协同主要面对的问题
问题一:脏路径问题
藏路径
问题二:并发冲突问题
问题三:undos/redos 问题
问题四:工程落地问题
开源技术
1.ShareDB 方案
ShareDB is a full-stack library for realtime JSON document collaboration. It provides a Node.js server for coordinating and committing edits from multiple clients. It also provides a JavaScript client for manipulating documents, which can be run either in Node.js or in the browser.
github 5.7k
架构图
子主题
方案流程图
2.Yjs 方案
github 11.2k
提供了完善的生态
y-websocket - 提供协同编辑时的消息通讯,包含服务端实现和前端集成的SDK
y-protocols - 定义消息通讯协议,包括消息服务初始化、内容更新、鉴权、感知系统等
y-redis - 持久化数据到 Redis
y-indexeddb - 持久化数据到 IndexedDB
y-protocols - 定义消息通讯协议,包括消息服务初始化、内容更新、鉴权、感知系统等
y-redis - 持久化数据到 Redis
y-indexeddb - 持久化数据到 IndexedDB
方案流程图
yjs协同编辑的逻辑
1.数据结构
子主题
总数据结构是StoreStruct Map类型,每一个用户一个id:【item,item,item】-----所有人的item一起形成一个链表
2.协同编辑的情况
1.总数据是一个链表,新增一个数据 无非是在 原有的链表上插入一个item
2.每一个item有唯一不变的id,所以2人同时插入,一个在头,一个在尾没有任何影响,不会冲突
3.有冲突的情况 只会是2人同时在一处:如MN中间插入了字符,第一人插入后变为MON, 第二人去插入发现中间已经有了冲突了
4.对于代码而言,同一时刻只会处理一个冲突,也就是说无论多少人同时开发,同一时刻只解决一个冲突
5.解决冲突:当插入一个数据 Item(left) 和 Item(right) 应该是相邻的,如果不相邻即发现冲突,------解决逻辑(先找Item(left),插入他的右边就好了, 找不到Item(left), 那就找Item(right),插入它的左边就好了)
2.每一个item有唯一不变的id,所以2人同时插入,一个在头,一个在尾没有任何影响,不会冲突
3.有冲突的情况 只会是2人同时在一处:如MN中间插入了字符,第一人插入后变为MON, 第二人去插入发现中间已经有了冲突了
4.对于代码而言,同一时刻只会处理一个冲突,也就是说无论多少人同时开发,同一时刻只解决一个冲突
5.解决冲突:当插入一个数据 Item(left) 和 Item(right) 应该是相邻的,如果不相邻即发现冲突,------解决逻辑(先找Item(left),插入他的右边就好了, 找不到Item(left), 那就找Item(right),插入它的左边就好了)
3.同步机制
4. yjs后台websocket需要做什么?
y-websocket内部也有服务的脚本 /bin/server.js 处理过程很简单,建立连接后 处理收到的消息,再发送出去
后端
// 导入WebSocket模块:
const WebSocket = require('ws');
console.log(WebSocket)
// 引用server类 就是说真正的服务在大对象中的Server
const WebSocketServer = WebSocket.Server;
// ws:localhost:8500
const server = new WebSocketServer({
port:8500
})
// open 事件
server.on('open',()=>{ console.log("webSocket open") })
// 绑定close 事件
server.on('close',()=>{ console.log("webSocket close") })
// 绑定error 事件
server.on('error',()=>{ console.log("webSocket error")})
// 绑定connection 事件
// 如果有WebSocket请求接入,server对象可以响应connection事件来处理这个WebSocket:
server.on('connection',(ws)=>{
console.log("connection 只要有人连接,那么我就会被触发,有人进来了");
// 在这里面绑定message 事件 msg是前端发送过来的数据
// 接收前端传递的数据,最终需要广播出去给每一个客户端
ws.on('message',(msg)=>{
console.log("message")
console.log(msg)
// 那到底如何广播出去呢?server.clients 记录着保存所有连接到server上的客户端
// 通过forEach遍历得到每一个用户
server.clients.forEach(item=>{
// item下有个send方法,把这msg广播出去再返回给前端 前端在message事件处理函数中就会收到
item.send(msg)
})
})
})
// 导入WebSocket模块:
const WebSocket = require('ws');
console.log(WebSocket)
// 引用server类 就是说真正的服务在大对象中的Server
const WebSocketServer = WebSocket.Server;
// ws:localhost:8500
const server = new WebSocketServer({
port:8500
})
// open 事件
server.on('open',()=>{ console.log("webSocket open") })
// 绑定close 事件
server.on('close',()=>{ console.log("webSocket close") })
// 绑定error 事件
server.on('error',()=>{ console.log("webSocket error")})
// 绑定connection 事件
// 如果有WebSocket请求接入,server对象可以响应connection事件来处理这个WebSocket:
server.on('connection',(ws)=>{
console.log("connection 只要有人连接,那么我就会被触发,有人进来了");
// 在这里面绑定message 事件 msg是前端发送过来的数据
// 接收前端传递的数据,最终需要广播出去给每一个客户端
ws.on('message',(msg)=>{
console.log("message")
console.log(msg)
// 那到底如何广播出去呢?server.clients 记录着保存所有连接到server上的客户端
// 通过forEach遍历得到每一个用户
server.clients.forEach(item=>{
// item下有个send方法,把这msg广播出去再返回给前端 前端在message事件处理函数中就会收到
item.send(msg)
})
})
})
0 条评论
下一页