自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

實現(xiàn)一個多人協(xié)作在線文檔有哪些技術(shù)難點?

網(wǎng)絡(luò)
用于多人協(xié)同編輯 Autodesk Maya 文檔OT算法維持一致性的基本思路是根據(jù)先前執(zhí)行的并發(fā)操作的影響將編輯操作轉(zhuǎn)換為新形式,以便轉(zhuǎn)換后的操作可以實現(xiàn)正確的效果,并確保復(fù)制的文檔相同。

 這是一篇鴿了很久的回答,正巧 Cloud Studio 也實現(xiàn)了多人協(xié)作代碼編輯,技術(shù)原理上來說是差不多的,這里把之前我的一篇博客發(fā)上來吧。協(xié)同編輯基本實現(xiàn)思路有兩種,分別是 CRDT(Conflict-Free Replicated Data Types) 和 OT(Operational-Transformation)。

CRDT

CRDT即無沖突可復(fù)制數(shù)據(jù)類型,看上去很難理解(其實我也不怎么理解),這是一些分布式系統(tǒng)中適應(yīng)于不同場景且可以保持最終一致性的數(shù)據(jù)結(jié)構(gòu)的統(tǒng)稱。也就是說CRDT本身只是一個概念,應(yīng)用于協(xié)作編輯中需要自行實現(xiàn)數(shù)據(jù)結(jié)構(gòu),比如GitHub團隊開源的。

ATOM的實時協(xié)作功能就是基于這個庫來實現(xiàn)的,數(shù)據(jù)傳輸采用WebRTC,只有在最初的邀請/加入階段依賴GitHub的服務(wù)器外,所有的傳輸都是點對點的(peer-to-peer),同時以確保隱私,所有數(shù)據(jù)都是加密的。

OTO

perational-Transformation 或者叫操作轉(zhuǎn)換,是指對文檔編輯以及同時編輯沖突解決的一類技術(shù),不僅僅是一個算法。與CRDT不同的是,OT算法全程依賴于服務(wù)器來保持最終一致性。成本而言,CRDT優(yōu)于OT,但因CRDT的實現(xiàn)復(fù)雜性(沒學會),本文主要介紹基于OT算法的實時協(xié)同編輯。OT算法不僅可用于純文本操作,同時還支持一些更為復(fù)雜的場景:

  • 協(xié)同圖形編輯

支持實時協(xié)作的多媒體編輯器,可以讓多個用戶在同一 Adobe Flash 中同時編輯同一文檔

  • 協(xié)同HTML/XML以及富文本編輯

基于網(wǎng)絡(luò)的實時協(xié)作編輯器

  • 協(xié)同電子表格、Word文檔等
  • 計算機輔助設(shè)計(Maya)

用于多人協(xié)同編輯 Autodesk Maya 文檔OT算法維持一致性的基本思路是根據(jù)先前執(zhí)行的并發(fā)操作的影響將編輯操作轉(zhuǎn)換為新形式,以便轉(zhuǎn)換后的操作可以實現(xiàn)正確的效果,并確保復(fù)制的文檔相同。事實上,并不是在多人同時編輯相鄰字符時才必須要使用OT,OT的適用性與并發(fā)操作的字符/對象數(shù)量無關(guān),無論這些目標對象是否相互重疊,無論這些字符相鄰遠近,OT都會針對具有位置依賴關(guān)系的對象進行并發(fā)控制。

OT將文檔變更表示為三類操作(Operational)

  1. Insert 插入
  2. Retain 保留
  3. Delete 刪除

例如對于一個原始內(nèi)容為“abc”的文檔,假設(shè)用戶O1在文檔位置0處插入一個字符“x”,表示為`Insert[0,"x"]`,用戶O2在文檔位置2處刪除一個字符,表示為`Delete[2,1]`(或者Delete[2,'c']),這將產(chǎn)生一個并發(fā)操作。在OT的控制下,本地操作會如期執(zhí)行,遠端服務(wù)器收到兩個操作后會進行轉(zhuǎn)換`Transformation`,具體過程如下

  1. 用戶O1首先執(zhí)行插入操作,文檔內(nèi)容變?yōu)?ldquo;xabc”。然后O2的操作到達且被轉(zhuǎn)換為`O2' = T(O2,O1) = Delete[3,1]`,產(chǎn)生了一個新的操作,此時位置增加了1,因為O1插入了一個字符。然后在文檔“xabc”執(zhí)行O2',此時文檔內(nèi)容變?yōu)?ldquo;xab”,即“c”被正確的刪除。(如果不進行轉(zhuǎn)換,會錯誤的刪除“b”)。
  2. 用戶O2首先執(zhí)行刪除操作,文檔內(nèi)容變?yōu)?ldquo;ab”,然后O1的操作到達且被轉(zhuǎn)換為`O1' = T(O1, o2) = Insert[0,"x"]`,也產(chǎn)生了一個新的操作,由于先前執(zhí)行的O2與O1互不影響,轉(zhuǎn)換后的O1'與O1相同,文檔內(nèi)容變?yōu)?ldquo;xab”。

這里忽略了光標操作,實際上多用戶實時編輯時,應(yīng)用在編輯器上,并不會真正的去移動光標,只會在相應(yīng)的位置插入一個fake cursor。Monaco-Editor 與 ot.js我們使用ot.js來實現(xiàn)Monaco-Editor的協(xié)同編輯。ot.js包含客戶端與服務(wù)端的實現(xiàn),在客戶端,它將編輯操作轉(zhuǎn)換為一系列的operation。

  1. // 對于文檔“Operational Transformation” 
  2. const operation = new ot.Operation() 
  3.   .retain(11) // 前11個字符保留 
  4.   .insert("color"); // 插入字符 
  5. // 這將使文檔變更為 "Operationalcolor" 
  6.  
  7. // “abc” 
  8. const deleteOperation = new ot.Operation() 
  9.   .retain(2) // 
  10.   .delete(1) 
  11.   .insert("x"// axc 

同時operation也是可組合的,比如將兩個操作組合為一個操作

  1. const operation0 = new ot.Operation() 
  2.   .retain(13) 
  3.   .insert(" hello"); 
  4. const operation1 = new ot.Operation() 
  5.   .delete("misaka "
  6.   .retain(13); 
  7.  
  8. const str0 = "misaka mikoto"
  9.  
  10. const str1 = operation0.apply(str0); // "misaka mikoto hello" 
  11. const str2a = operation1.apply(str1); // "mikoto hello" 
  12.  
  13. // 組合 
  14. const combinedOperation = operation0.compose(operation1); 
  15. const str2b = combinedOperation.apply(str0); // "mikoto dolor" 

應(yīng)用到Monaco中,我們需要監(jiān)聽編輯器的onChange事件以及光標相關(guān)操作事件(selectionChange,cursorChange,blur等)。在文本內(nèi)容修改的事件中,將每次修改產(chǎn)生的`changes`轉(zhuǎn)換為一個或多個操作,也叫`operation`。光標的操作很好處理,轉(zhuǎn)換成一個`Retain`操作即可。

  1.  const editor = monaco.editor.create(container, { 
  2.   language: 'php'
  3.   glyphMargin: true
  4.   lightbulb: { 
  5.     enabled: true
  6.   }, 
  7.   theme: 'vs-dark'
  8. }); 
  9.  
  10. editor.onDidChangeModelContent((e) => { 
  11.   const { changes } = e; 
  12.   let docLength = this.editor.getModel().getValueLength(); // 文檔長度 
  13.   let operation = new TextOperation().retain(docLength); // 初始化一個operation,并保留文檔原始內(nèi)容 
  14.   for (let i = changes.length - 1; i >= 0; i--) { 
  15.       const change = changes[i]; 
  16.       const restLength = docLength - change.rangeOffset - change.text.length; // 文檔 
  17.       operation = new TextOperation() 
  18.         .retain(change.rangeOffset) // 保留光標位置前的所有字符 
  19.         .delete(change.rangeLength) // 刪除N個字符(如為0這個操作無效) 
  20.         .insert(change.text) // 插入字符 
  21.         .retain(restLength) // 保留剩余字符 
  22.         .compose(operation); // 與初始operation組合為一個操作 
  23. }); 

這段代碼首先創(chuàng)建了一個編輯器實例,監(jiān)聽了`onDidChangeModelContent`事件,遍歷changes數(shù)組,change.rangeOffset代表產(chǎn)生操作時的光標位置,change.rangeLength代表刪除的字符長度(為0即沒有刪除操作),restLength是根據(jù)文檔最終長度 - 光標位置 - 插入字符長度得出,用于在文檔中間位置插入字符時保留剩余字符的操作。

但同時我們也要考慮到撤銷/重做,ot.js中對撤銷/重做的處理是每次編輯操作都需要產(chǎn)生對應(yīng)的`逆操作`,并存入撤銷/重做棧,在上面代碼的循環(huán)體中,我們還需要添加一個名為`inverse`的操作。

  1. let inverse = new TextOperation().retain(docLength); 
  2.  
  3. // 獲取刪除的字符,實現(xiàn)略 
  4. const removed = getRemovedText(change, this.documentBeforeChanged); 
  5.   inverse = inverse.compose( 
  6.     new TextOperation() 
  7.       .retain(change.rangeOffset) // 與編輯相同 
  8.       .delete(change.text.length) // 插入變?yōu)閯h除 
  9.       .insert(removed) // 刪除變?yōu)椴迦?/span> 
  10.       .retain(restLength); // 同樣保留剩余字符 

這樣就產(chǎn)生了一個編輯操作和一個用于撤銷的逆操作,編輯操作會發(fā)送到服務(wù)端進行轉(zhuǎn)換同時再發(fā)送到給其他客戶端,逆操作保存在本地用于實現(xiàn)撤銷。

撤銷/重做的思路很簡單,因為不論如何都會對編輯器產(chǎn)成一個change事件,并且實時編輯的狀態(tài)下,兩個用戶的撤銷/重做棧需要互相獨立,也就是說A的操作不能進入B的撤銷棧,因而在B執(zhí)行撤銷的時候只能對自己先前的操作產(chǎn)生影響,不能撤銷A的編輯,所以我們需要實現(xiàn)一個自定義的撤銷函數(shù)來覆蓋編輯器自帶的撤銷功能。

我們需要覆蓋默認的撤銷

  1. this.editor.addAction({ 
  2.   id: 'cuctom_undo'
  3.   label: 'undo'
  4.   keybindings: [ 
  5.     monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_Z 
  6.   ], 
  7.   run: () => { 
  8.     this._undoFn() 
  9.   } 
  10. }) 

這里_undoFn的實現(xiàn)不再贅述,實際就是將先前change事件中產(chǎn)生的逆操作保存在一個自定義的undoManager中,每次執(zhí)行撤銷就undoStack.pop()拿出最近一次的操作并應(yīng)用在本地,同時發(fā)送給協(xié)作者,因為undoManager中并未保存協(xié)作者的逆操作,所以執(zhí)行撤銷不會影響協(xié)作者的操作。

ot.js還包含了服務(wù)端的實現(xiàn),只需要將ot.js的服務(wù)端代碼運行在nodejs中,同時搭建一個簡單的websocket服務(wù)器即可。

  1. const EditorSocketIOServer = require('ot.js/socketio-server.js'); 
  2. const server = new EditorSocketIOServer("", [], 1); 
  3.  
  4. io.on('connection'function(socket) { 
  5.   server.addClient(socket); 
  6. }); 

服務(wù)端接收到每個協(xié)作者的operation并進行轉(zhuǎn)換后下發(fā)到其他協(xié)作者客戶端,轉(zhuǎn)換操作實際是調(diào)用一個`transform`函數(shù),可以戳這里transform查看,實際上這個函數(shù)也正是OT技術(shù)的核心,由于時間有限,所以不再詳細解讀這個函數(shù)的源碼(逃隨著 Cloud Studio 的架構(gòu)升級和改進,我們正在準備拋棄 OT 轉(zhuǎn)向 CRDT,所以等全部實現(xiàn)完成再來分享。

 

責任編輯:梁菲 來源: 互聯(lián)網(wǎng)
相關(guān)推薦

2021-09-15 14:53:35

在線文檔多人協(xié)作

2011-11-30 16:37:58

sync

2021-08-30 17:35:17

開發(fā)在線文檔多人協(xié)作

2022-06-16 15:54:32

前端

2016-10-18 20:50:00

android鎖屏App

2018-09-18 14:03:57

OpenStack知識難點

2021-03-28 17:21:15

Git分支策略

2019-03-21 09:12:06

CryptPad開源編輯器

2011-11-30 16:08:14

YC新銳Stypi

2011-11-30 15:57:18

2011-11-30 16:06:16

20個實用的在線協(xié)作

2024-05-06 08:34:17

GolangGo程序

2024-03-14 09:07:05

刷數(shù)任務(wù)維度后端

2022-03-04 09:02:01

StoryBoard工具git

2023-06-28 15:49:35

2025-01-20 00:35:00

vitestvite組件

2023-10-11 07:52:09

遍歷方式Java

2012-06-12 09:51:43

在線思維

2023-01-03 12:30:25

架構(gòu)CPUGPU
點贊
收藏

51CTO技術(shù)棧公眾號