Google Docs系統設計詳解(協作文檔編輯)
1 協作文檔編輯服務的設計方式
1.1 C/S架構的集中式設施
為所有用戶提供文檔編輯服務。所有用戶都連接到一個中心服務器,該服務器負責存儲和處理文檔數據,用戶通過連接到該服務器來協作編輯文檔。提供更好的安全性和可控性,但有單點故障問題
1.2 點對點技術設計
以便在單個文檔上協作。將文檔數據分散存儲在多個用戶設備,每個用戶都可直接編輯文檔并將更改同步到其他用戶設備。提供更好靈活性和可擴展性,但可能會有數據同步不及時或數據沖突問題
大多數商業方案側重C/S架構,以實現更精細控制。因此,本文也使用C/S架構設計服務。
2 需求
2.1 功能性
① 文檔協作
多用戶能同時編輯文檔。大量用戶應能查看文檔。
② 沖突解決
系統應將一個用戶做的編輯推送給所有其他協作者。若他們正在編輯文檔同一部分,系統還應解析用戶之間的沖突。
③ 建議
- 在文檔中完成常用單詞、短語和關鍵詞的建議
- 修復語法錯誤的建議
④ 查看計數
文檔的編輯應能看到該文檔的查看計數。
⑤ 歷史
用戶能查看文檔的協作歷史。
2.2 非功能性
① 延遲
不同的用戶可連接起來協作同一份文檔。為來自不同區域的用戶維護低延遲訪問。
② 一致性
系統應能解析用戶并發編輯文檔時之間的沖突,從而實現文檔的一致視圖。不同區域的用戶應看到文檔的更新狀態。
對連接到同一區域和不同區域的用戶來說,保持一致性都很重要。
③ 可用性
該服務應一直可用,并展示對故障的魯棒性。
④ 可擴展性
大量用戶應能同時使用該服務。他們可查看相同的文檔,也可創建新文檔。
3 組件
3.1 數據存儲
- 關系數據庫,用于保存用戶信息和文檔相關信息以施加特權限制
- NOSQL,用于存儲用戶評論以獲得更快的訪問速度
- 時間序列,用于保存文檔的編輯歷史記錄
- Blob 存儲,用于存儲文檔中的視頻和圖像
- 緩存,分布式緩存如 Redis 和 CDN,為最終用戶提供良好的性能。使用 Redis存儲不同數據結構,包括用戶會話、類型預期服務的功能、頻繁訪問的文檔。CDN存儲頻繁訪問的文檔和重量級對象如圖像和視頻。
3.2 處理隊列
針對每次微小字符更改使用 HTTP 調用是低效的。因此使用 WebSockets 減少開銷,并通過不同用戶實時觀察文檔的更改。
3.3 其他
會話服務器,維護用戶的會話信息。通過會話服務器管理文檔訪問權限。還要有配置、監控、發布-訂閱和日志記錄服務來處理監控任務,如在服務器失敗時監控和選舉領導者,排隊用戶通知等任務及記錄調試信息。
協作文檔編輯服務的詳細設計:
圖片
4 工作流程
4.1 協作編輯和沖突解決
每個請求都會轉發到操作隊列。這是解析同一文檔的不同協作者之間沖突的地方。如果沒有沖突,則通過會話服務器將數據批量存儲在時間序列數據庫中。像視頻和圖像這樣的數據會被壓縮以優化存儲,而字符會被立即處理。歷史:借助時間序列數據庫,可以恢復文檔的不同版本。可以使用 DIFF 操作來比較版本并標識差異以恢復同一文檔的舊版本。
4.2 異步操作
通知、電子郵件、查看次數和評論都是可以通過像 Kafka 這樣的發布-訂閱組件排隊的異步操作。API 網關生成這些請求并將它們轉發到發布-訂閱模塊。
4.3 建議
建議以類型提前服務(typeahead service)的形式出現,該服務提供通常使用的單詞和短語的自動完成功能。類型提前服務還可以從文檔中提取屬性和關鍵詞并向用戶提供建議。由于單詞數量可能很高,我們將為此目的使用 NoSQL 數據庫。此外,最常用的單詞和短語將存儲在像 Redis 這樣的緩存系統中。
4.4 導入和導出文檔
應用程序服務器執行許多重要任務,包括導入和導出文檔。應用程序服務器還將文檔從一種格式轉換為另一種格式。例如,.doc 或 .docx 文檔可以轉換為 .pdf 或反之亦然。應用程序服務器也負責為類型預期服務提取特征。
5 詳細設計
5.1 文檔編輯器
文檔由以特定順序排列的字符組成。每個字符都有一個值和一個位置索引。字符可以是字母、數字、回車()或空格。索引表示字符在有序字符列表中的位置。
文本或文檔編輯器的作用是在文檔中的字符上執行插入()、刪除()、編輯()等操作。下面是文檔的描繪以及編輯器將如何執行這些操作。
文檔編輯器如何執行各種操作
圖片
5.2 并發性
不同用戶對同一文檔的協作可能導致并發問題。若多個用戶編輯文檔的同一部分,可能出現沖突。由于用戶在本地有文檔的副本,服務器上的最終文檔狀態可能與用戶在他們端看到的不同。在服務器推送更新版本后,用戶會發現意外結果。
① 在同一位置索引處添加字符
兩個用戶修改同一字符可能導致并發問題:
圖片
② 刪除同一字符
刪除同一字符,可能導致意外更改:
圖片
第二個例子表明,不同用戶應用相同的操作不會是冪等的。因此,在多個協作者同時編輯文檔同一部分時,需沖突解決。
協作編輯中并發問題的解決方案應遵循規則:
- 交換律:應用操作的順序不應影響最終結果
- 冪等性:重復的相似操作只應用一次
6 沖突解決技術
6.1 操作轉換(OT)
廣泛用于協作編輯中的沖突解決的技術,一種【無鎖】、【非阻塞】的沖突解決方法。若協作者之間的操作沖突,OT會解析沖突并將正確的匯聚狀態推給最終用戶。因此,OT為用戶提供一致性。
OT 使用位置索引方法執行操作來解析上面討論的那些沖突。通過保持交換律、冪等性來解決上述問題。
OT示例:
圖片
基于 OT 的協作編輯器在滿足以下兩個屬性時一致:
- 因果關系保持:如果操作 a 發生在操作 b 前,那先執行操作 a,然后執行操作 b
- 收斂:不同客戶端上的所有文檔副本最終相同
上述屬性是 CC 一致性模型的一部分,CC 一致性模型是協作編輯中一致性維護的模型。
OT的缺點
對字符的每個操作都可能需要更改位置索引。這意味著操作之間存在順序依賴關系。它的開發和實現具有挑戰性。
OT是一組復雜算法,其正確實現在實際應用中已被證明有挑戰性。Google Wave 團隊花兩年時間實現OT。
6.2 無沖突復制數據類型 (CRDT)
是為了改進 OT。CRDT 具有:
- 復雜的數據結構
- 但簡化的算法
CRDT 通過為每個字符分配兩個關鍵屬性來滿足交換律和冪等性:
- 為每個字符賦予全局唯一標識
- 全局訂購每個字符
每個操作現在都有一個更新后的數據結構:CRDT 簡化的數據結構
圖片
SiteID 唯一標識請求操作的用戶站點,帶有一個值和一個 PositionalIndex。PositionalIndex值可以是分數:
- 某些操作不會改變其他字符的 PositionalIndex
- 避免不同用戶操作之間的順序依賴性
示例描繪來自站點 ID 123e4567-e89b-12d3 的用戶在 PositionalIndex 為 1.5 的位置插入一個值為 A 的字符。盡管添加了新字符,但使用小數索引保留了現有字符的位置索引。因此,避免了操作之間的順序依賴性。如下所示,在 O 和 T 之間插入()并沒有影響 T 的位置。
防止操作之間的順序依賴性:
圖片
CRDT 確保用戶之間的強一致性。即使一些用戶處于離線狀態,當他們重新聯機時,最終用戶處的本地副本也將匯聚。
盡管眾所周知的在線編輯平臺如 Google 文檔、Etherpad 和 Firepad 使用 OT,但 CRDT 使協作文檔編輯中的并發和一致性變得容易。事實上,使用 CRDT,可以實現無服務器點對點協作文檔編輯服務。
6.3 注意
OT 和 CRDT 是協作編輯中沖突解決的良好解決方案,但我們使用 WebSockets 可高亮協作者的光標。其他用戶也能預期該協作者的下一個操作的位置,并自然和自覺地避免沖突。
7 總結
7.1 一致性
操作轉換(OT)和沖突不定決議數據類型(CRDT)在文檔中實現沖突解決的強一致性。
時間序列數據庫能保留事件的順序。一旦 OT 或 CRDT 解析了任何沖突,最終結果就保存在數據庫。這有助我們在單個操作方面實現一致性。
在IDC內的不同服務器之間保持文檔狀態的一致性。要在同一IDC內同時復制更新后的文檔狀態,可使用 Gossip 協議這樣的點對點協議。這不僅提高一致性,還會提高可用性。
7.2 可用性
我們的設計通過使用副本以及使用監控服務監控主副本服務器來確保可用性。操作隊列和數據存儲等關鍵組件在內部管理自己的復制。
由于使用 WebSockets,WebSocket 服務器可將用戶連接到會話維護服務器,這些服務器將確定用戶是否正在主動查看或協作文檔。
因此,保留多個 WebSocket 服務器將增加設計的可用性。最后,采用緩存服務和 CDN 改進可用性。
7.2 可擴展性
由于使用微服務,若操作隊列的請求數量超過其容量,可輕松單獨擴展每個組件。可使用多個操作隊列。此時,每個操作隊列將負責單個文檔。可將不同用戶請求的與單個文檔相關的操作轉發到特定隊列。生成的隊列數量將等于活動文檔的數量。因此可實現水平擴展性。
參考:
- 編程嚴選網