在线协同技术调研
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 条评论
 下一页
  
   
   
   
   
  
  
  
  
  
  
  
  
  
  
 