可視化+多人協(xié)同技術(shù)原理和案例分享
前言
hi,大家好,我是徐小夕,之前和大家分享了很多可視化低代碼的技術(shù)實踐,最近也做了一款非常有意思的文檔搭建引擎——Nocode/Doc:
也做了一些分享:
接下來和大家分享另一個比較有意思的話題——多人協(xié)同技術(shù)。
文章大綱
- 多人協(xié)同技術(shù)方案探討
- OT和CRDT算法
- 插曲(互斥鎖(Mutex)原理和代碼實現(xiàn))
- yjs協(xié)同框架使用
- yjs多人協(xié)同案例
多人協(xié)同技術(shù)方案探討
多人協(xié)同技術(shù)方案常見的應用場景主要有:
- 原型工具(axure,某刀,mastergo等)
- 文檔辦公類 (飛書文檔,釘釘文檔,石墨文檔等)
- 設計工具(即時設計,figma等)
主要目的是實現(xiàn)多個人同時編輯一份共享資源, 來提高工作效率。
拋開已有技術(shù)本身,我們拿最簡單的富文本編輯器為例子, 如果我們想讓它實現(xiàn)多人同時編輯,有哪些可以想到的方案呢?
- 覆蓋模式
即每個人保存時都強制以自己的版本為主,即保存最后一次修改,這樣會導致的問題是無法實現(xiàn)真正意義上的共享協(xié)作。
- 鎖模式
也就是對文件”上鎖“。當某個用戶正在編輯文檔時,對此文檔進行加鎖處理,避免多人同時編輯,從而避免文檔的內(nèi)容沖突。 缺點就是用戶體驗不友好,并且需要等待時間。
- diff 模式
我們可以采用類似 git 的版本管理模式,多人編輯時利用 webrtc / socket 與服務端通信,保存時通過服務端進行差異對比、合并,自動進行沖突處理,再通過服務推送如SSE(服務端實時主動向瀏覽器推送消息的技術(shù))的方式推送給其他人。
弊端是會出現(xiàn)類似 git 修改同一行,純靠服務端無法處理,需要手動處理沖突。
這里給大家推薦一個有意思的庫 NodeGit。
github地址: https://github.com/nodegit/nodegit
以下是 NodeGit 的一些主要特點:
- 全功能:幾乎支持 Git 的所有命令,如克隆、提交、拉取、合并等。
- 高性能:直接調(diào)用 C 庫,提供接近原生速度的性能。
- 易于集成:作為一個 Node.js 模塊,可輕松融入任何 Node.js 項目,無需額外的構(gòu)建步驟或依賴。
- 跨平臺:支持 Windows、macOS 和 Linux,讓開發(fā)者可以在各種操作系統(tǒng)上工作。
- 文檔齊全:提供詳細的 API 文檔和示例代碼,便于理解和使用。
- 社區(qū)活躍:開源社區(qū)活躍,問題和 PR 能得到及時響應,不斷更新改進。
NodeGit 可以用于多個領(lǐng)域,例如自動化部署、協(xié)作工具、代碼分析、教育工具和 CI/CD 系統(tǒng)等。通過使用 NodeGit,我們能以編程方式訪問和操作 Git 存儲庫,實現(xiàn)更靈活和自動化的版本控制流程。
當然以上這幾種方式很難應對復雜場景的多人協(xié)作。
OT和CRDT算法
OT 算法是一種用于實時協(xié)同編輯的算法,它通過操作 & 轉(zhuǎn)換來實現(xiàn)數(shù)據(jù)的一致性。在 OT 算法中,每個用戶對數(shù)據(jù)的操作(如修改、刪除等)都被記錄下來,并在其他用戶的客戶端進行相應的轉(zhuǎn)換,從而實現(xiàn)多個用戶對同一份數(shù)據(jù)的協(xié)同編輯。
OT 算法的優(yōu)點在于它可以實時地反映用戶的操作,并且可以很好地處理并發(fā)沖突。但是 OT 算法需要在中心化的服務器上進行協(xié)同調(diào)度,因此對于大規(guī)模的分布式系統(tǒng)來說不太適用。
操作 Operational
基于 OT 的協(xié)同編輯核心是:將文檔的每一次修改看作是一個操作,即操作原子化處理,如在第 N 個位置插入一個字符時,客戶端會將操作發(fā)送到服務端去處理。
以quill富文本編輯器舉例, 它通過 retain、insert、delete 三個操作完成整篇文檔的描述與操作,如下:
[ // Unbold and italicize "Gandalf" { retain: 7, attributes: { bold: null, italic: true } }, // Keep " the " as is { retain: 5 }, // Insert "White" formatted with color #fff { insert: 'White', attributes: { color: '#fff' } }, // Delete "Grey" { delete: 4 } ]
相關(guān)地址:https://quilljs.com/docs/delta
Transformation 轉(zhuǎn)換
用戶將原子化的操作發(fā)送到服務端時(必須有中央服務器進行調(diào)度), 服務端對多個客戶端的操作進行轉(zhuǎn)換,對客戶端操作中的并發(fā)沖突進行修正,確保當前操作同步到其他設備時得到一致的結(jié)果,因為對沖突的處理都是在服務端完成,所以客戶端得到的結(jié)果一定是一致的,也就是說 OT 算法的結(jié)果保證強一致性。
轉(zhuǎn)換完成后,通過網(wǎng)絡發(fā)送到對應用戶,用戶合并操作,從而得到一致結(jié)果。
這意味著 OT 算法對網(wǎng)絡要求更高,如果某個用戶出現(xiàn)網(wǎng)絡異常,導致一些操作缺失或延遲,那么服務端的轉(zhuǎn)換就會出現(xiàn)問題。
OT算法可視化模型:https://operational-transformation.github.io/index.html
CRDT
CRDT 算法全稱 Conflict-free Replicated Data Type,即無沖突復制數(shù)據(jù)類型,是一種基于數(shù)據(jù)結(jié)構(gòu)的無沖突復制數(shù)據(jù)類型算法,它通過數(shù)據(jù)結(jié)構(gòu)的合并來實現(xiàn)數(shù)據(jù)的一致性。
在 CRDT 算法中,每個用戶對數(shù)據(jù)的修改都會被記錄下來,并在其他用戶的客戶端進行合并,以實現(xiàn)數(shù)據(jù)的一致性。CRDT 算法的優(yōu)點在于它可以適用于大規(guī)模的分布式系統(tǒng),并且不需要中心化的服務器進行協(xié)同調(diào)度。
但是,CRDT 算法在處理復雜操作時可能會存在合并沖突的問題,需要設計復雜的合并函數(shù)來解決。
Yjs 是專門為在 web 上構(gòu)建協(xié)同應用程序而設計的CRDT.
CRDT 包含以下兩種:
- CmRDT:基于操作的 CRDT,OP-based-CRDT
- CvRDT:基于狀態(tài)的 CRDT,State-based CRDT
基于狀態(tài)的 CRDT 更容易設計和實現(xiàn),每個 CRDT 的整個狀態(tài)最終都必須傳輸給其他每個副本,每個副本之間通過同步全量狀態(tài)達到最終一致狀態(tài),這可能開銷很大;
而基于操作的 CRDT 只傳輸更新操作,各副本之間通過同步操作來達到最終一致狀態(tài),通常很小。
穿插一個小概念:
向量時鐘(Vector Clock),它是一種在分布式系統(tǒng)中用于記錄事件順序的時間戳機制。它的主要目的是在分布式環(huán)境中實現(xiàn)事件的并發(fā)控制和一致性。
向量時鐘的基本思想是為系統(tǒng)中的每個節(jié)點維護一個向量,其中每個分量對應一個節(jié)點,用于記錄該節(jié)點的事件發(fā)生次數(shù)。當一個節(jié)點發(fā)生事件時,它會增加自己分量的值。向量時鐘的關(guān)鍵是在不同節(jié)點之間傳遞這些向量,并在合并時確保一致性。
目前協(xié)同算法底層都會采用向量時鐘的模式來設計操作算法。
插曲(互斥鎖(Mutex)原理和代碼實現(xiàn))
先上代碼:
const createMutex = () => { let token = true return (f, g) => { if (token) { token = false try { f() } finally { token = true } } else if (g !== undefined) { g() } }}
它用于創(chuàng)建一個互斥鎖(Mutex)。互斥鎖是一種用于控制資源訪問的機制,確保在任何給定的時間只有一個線程(在這里可以理解為一個函數(shù)調(diào)用)可以訪問被保護的資源或代碼塊。
下面是對代碼中每個部分的解釋:
- let token = true:創(chuàng)建一個名為token的變量,并將其初始化為true。token用于表示互斥鎖的狀態(tài)。
- return (f, g) => { … }:返回一個箭頭函數(shù),該函數(shù)接受兩個參數(shù)f和g。
- if (token) { … }:如果token為true,表示互斥鎖可用。
- token = false:將token設置為false,表示當前函數(shù)獲取了互斥鎖。
- try { f() } finally { token = true }:在try塊中執(zhí)行傳入的函數(shù)f。如果在執(zhí)行f的過程中發(fā)生異常,會跳轉(zhuǎn)到finally塊中。在finally塊中,將token重新設置為true,表示釋放互斥鎖。
- else if (g !== undefined) { g() }:如果token為false,表示互斥鎖已被其他函數(shù)獲取。如果同時還傳遞了第二個參數(shù)g,則執(zhí)行g(shù)函數(shù)。
通過這種方式,createMutex 函數(shù)創(chuàng)建了一個簡單的互斥鎖機制。只有在互斥鎖可用時,才能執(zhí)行f函數(shù)。如果互斥鎖已被其他函數(shù)獲取,將跳過f函數(shù)的執(zhí)行,并在可能的情況下執(zhí)行g(shù)函數(shù)。
這種互斥鎖的實現(xiàn)通常用于在多線程或異步環(huán)境中確保對共享資源的安全訪問。
yjs協(xié)同框架使用
Yjs 本身是一個數(shù)據(jù)結(jié)構(gòu),原理是:當多人協(xié)作時,對于文檔內(nèi)容修改,通過中間層將文檔數(shù)據(jù)轉(zhuǎn)換成 CRDT 數(shù)據(jù);通過 CRDT 進行數(shù)據(jù)數(shù)據(jù)更新這種增量的同步,通過中間層將 CRDT 的數(shù)據(jù)轉(zhuǎn)換成文檔數(shù)據(jù),另一個協(xié)作方就能看到對方內(nèi)容的更新。
中間內(nèi)容的更新是基于 Yjs 數(shù)據(jù)結(jié)構(gòu)進行的,沖突處理等核心都是 Yjs 承擔的,通信基于 websocket 或 webrtc,所以我們只需要簡單的使用就可以實現(xiàn)多人協(xié)同的應用。
下面是我總結(jié)的一個結(jié)構(gòu):
Yjs 基于數(shù)據(jù)結(jié)構(gòu)層面處理沖突,比 OT 更加穩(wěn)健,對復雜網(wǎng)絡的適應性更強。網(wǎng)絡延時或離線編輯對數(shù)據(jù)結(jié)構(gòu)來說,處理沒有任何差異。
我總結(jié)了一下它的幾個核心特點:
- 協(xié)同列表及光標位置
Yjs 提供的 Awareness(意識)模塊,名如其意,讓協(xié)作者能夠意識到其他人的位置在哪,有效避免沖突可能性。
- 離線編輯
基于 CRDT 的內(nèi)容合并,天然支持離線編輯,瀏覽器端做本地化存儲。
- 版本歷史支持
Yjs 自身提供了快照機制,保存歷史版本不用保存全量數(shù)據(jù),只是基于 Yjs 打一個快照,后續(xù)基于快照恢復歷史版本。
- 系統(tǒng)編輯人數(shù)上限
上限人數(shù)很高,可支持很多人同時編輯。
目前主流的 figma 也是采用的 CRDT 開發(fā)協(xié)同編輯功能。
yjs使用
以上我根據(jù)自己的理解整理了一下yjs的核心模塊。接下來我以數(shù)組結(jié)構(gòu)為例子給大家介紹一下它的用法:
import * as Y from 'yjs'const ydoc = new Y.Doc()// 1: 定義一個類型為數(shù)組的共享數(shù)據(jù)結(jié)構(gòu)const yarray = ydoc.getArray('my doc') // 2. 向數(shù)組中插入數(shù)據(jù),在第一個位置插入3條數(shù)據(jù)yarray.insert(0, [1, 2, 3]) // 3. 在第二個位置刪除一條數(shù)據(jù)yarray.delete(1, 1)// 4. 獲取可用的結(jié)果yarray.toArray() // => [1, 3]// 5. 監(jiān)聽數(shù)據(jù)變化,執(zhí)行操作yarray.observeDeep((event) => { console.log(event)})// 將連續(xù)的操作合并到transact 中ydoc.transact(() => { yarray.insert(1, ['a', 'b']) yarray.delete(2, 2) // deletes 'b' and 2}) // => [{ retain: 1 }, { insert: ['a'] }, { delete: 1 }]
transact方法用于執(zhí)行事務操作。事務是共享文檔上的一系列更改,這些更改會在一個事務中進行處理,以保證數(shù)據(jù)的一致性和正確性。每個事務都會觸發(fā)Observer調(diào)用和update事件,我們可以在這些事件中進行相應的處理。
通過將更改捆綁到單個事務中,可以減少事件調(diào)用的次數(shù),并確保數(shù)據(jù)的一致性。在事務中,我們可以進行多種操作,如插入、刪除、修改等。