題干與適用場景
設計一個支援段落、標題、列表、粗體和評論錨點的即時協同編輯器。系統有 2000 萬名每日活躍使用者,峰值維持 200 萬條線上連線,其中 20 萬名使用者正在輸入;活躍編輯者平均每秒產生 2 個更新,單個熱點文件最多 100 人同時編輯。線上協作者看到遠端修改的 p95 低於 200 毫秒。使用者可以離線編輯最多 24 小時, 重連後要合併修改;伺服器端已確認的編輯不能遺失。游標、選區和線上狀態允許短暫遺失。
系統還要支援查看、評論和編輯權限,權限變更,版本歷史,按使用者撤銷,以及多區域存取。題目不要求在面試中 從零證明一種 CRDT 或 OT 算法,也不要求復刻某家產品的內部實現。候選人要選定一種衝突解決模型,解釋它 如何與編輯器資料結構、網路、持久化和權限邊界配合,並說明方案不能保證什麼。
2026 年的中英文公開面試材料都直接把協同編輯器列為系統設計題,內容涵蓋 OT 與 CRDT、WebSocket、文件房間、 游標 presence、離線編輯、持久更新和快照恢復。Yjs 官方文件進一步給出可交換、可結合、冪等的更新語義, 狀態向量差量同步以及不持久化 awareness 的邊界。因此這道題既有目前代表性,也能用一手技術資料核驗。
面試官考察點
第一,看候選人是否抓住“併發修改如何收斂”,而不是只回答“使用 WebSocket”。WebSocket 提供雙向訊息通道, 但不會決定兩個使用者在同一位置插入字元時的結果。答案要比較伺服器端 OT 與 CRDT 的約束,並在題目條件下作出 明確選擇。
第二,看本機體驗與伺服器端承諾是否分開。輸入必須先在本機套用,不能等待跨區域往返;“已確認不丟”則要求 伺服器端在回傳 ack 前跨可用區持久化更新。用戶端在 ack 前儲存待確認更新,超時後用同一個操作識別碼重試。
第三,看持久內容和臨時 presence 是否分層。正文、評論錨點和版本歷史需要恢復;游標移動頻繁、過期快, 斷線後應自動消失。把每次游標移動寫入正文日誌會放大成本,也會讓過期狀態污染恢復流程。
第四,看候選人能否處理富文字語義。字元序列收斂不等於整棵文件樹一定合法。列表、表格、評論錨點、Schema 升級和撤銷範圍都需要明確模型、版本與不變量;普通整數下標也會被遠端插入立即推移。
最後,看容量、熱點、多區域、權限和驗證能否閉環。強答案會計算寫入與單房間扇出,限制慢用戶端,解釋離線 編輯遇到權限撤銷時為何不能直接合併,並用亂序、重複、網路分割區和故障切換證明所有副本最終一致。
回答前需要澄清的問題
- 編輯物件是什麼? 主設計是結構化富文字樹;圖片二進位走物件儲存,正文只儲存引用和屬性。
- 衝突後的產品語義是什麼? 併發更新必須確定性收斂且不覆蓋彼此;不承諾機器自動理解兩段文字的業務意圖。
- “已確認”指什麼? 更新寫入本區域跨三個可用區複製的持久日誌後,伺服器端才回傳 ack。
- 是否要求全域線性順序? 不要求。正文依靠 CRDT 收斂;日誌偏移只用於審計、恢復和確認水位。
- 離線多久? 最多 24 小時。用戶端保留本機 CRDT 狀態和未確認更新,重連時先重新授權再同步差量。
- 權限如何劃分?
viewer只能讀,commenter只能修改評論域,editor可修改正文;每次重連和寫入都驗證。 - 游標需要持久化嗎? 不需要。presence 依賴心跳和 TTL,遺失後等待下一次狀態更新。
- 多區域能否同時接收同一文件寫入? 主方案為每個文件分配 home region,其他區域轉發寫入,降低授權、審計和故障切換複雜度。
- 版本歷史保留多久? 假設使用者版本保留 30 天;它與用於線上同步的壓縮快照是兩種不同產品物件。
- 端到端加密是否在範圍內? 主設計是伺服器端可驗證權限和內容 Schema;端到端加密作為追問討論。
30 秒回答框架
“我會採用伺服器端中繼的 CRDT:本機編輯立即生效,更新經 WebSocket 傳送,伺服器端重新授權並在跨三個可用區 持久化後 ack,再按文件房間廣播。重連用狀態向量補差量,差距過大則載入快照;正文可恢復,游標等 presence 走獨立 TTL 通道。尖峰是 40 萬更新/秒、約 120 MB/s;100 人熱點房間約有 1.98 萬次遠端投遞/秒,因此需要 批次處理和限制慢用戶端。最後用亂序重複、離線 24 小時、權限撤銷和故障切換驗證收斂與耐久性。”
分步驟深入解答
先定義七個不變量:
- 本機輸入不等待網路,遠端更新最終收斂到同一合法文件狀態。
- 只有跨可用區持久化成功的正文更新才能被伺服器端確認。
- 重試、重複廣播和亂序交付不能重複套用或改變最終結果。
- 目前身分與權限來自伺服器端會話,不能相信更新酬載中的角色。
- 正文、評論和版本歷史可恢復;游標與線上狀態可以過期。
- Schema 不兼容的用戶端不能繼續寫入未知結構。
- 任何丟棄、拒絕、降級和恢復都必須有可觀測結果。
第一步:在 OT 與 CRDT 之間作出選擇。
OT 通常由一個有序伺服器端版本作為上下文,把到達的操作與併發操作逐一轉換。它適合強伺服器端權威和已有轉換 引擎的系統,但轉換函數、歷史保留與離線 rebase 必須一起正確。CRDT 把併發關係編碼進資料結構,更新可在 不同順序到達並重複套用,拿到全部更新的副本最終收斂;代價是更多因果元資料、刪除標記、複雜的富文字綁定, 以及需要單獨處理授權和產品級語義衝突。
題目要求離線 24 小時和跨區域存取,主方案選擇成熟的序列/樹 CRDT,並透過伺服器端中繼與持久化控制權限和 可靠性。CRDT 不等於去中心化部署,也不等於無需伺服器端。伺服器端仍負責身分、Schema、大小限制、耐久 ack、 歷史、合規和房間扇出。
第二步:定義結構化文件和訊息契約。
文件根節點包含有穩定 ID 的塊;段落、標題和列表項擁有 CRDT 文字與格式屬性;評論單獨儲存討論串狀態,並用 相對位置錨定範圍。Schema 版本規定允許的節點、屬性和遷移規則。普通整數位置不穩定:遠端在索引前插入一個 字元後,原索引會指向別處;相對位置附著於 CRDT 元素,所有副本收斂後可轉換到一致位置。
WebSocket 子協議可以使用以下應用層訊息:
join {
documentId, sessionId, schemaVersion, stateVector
}
update {
documentId, clientId, clientSeq, schemaVersion, payload
}
ack {
clientId, clientSeq, durableOffset
}
sync {
payload, durableOffset, schemaVersion
}
presence {
sessionId, relativeCursor, relativeSelection, statusSeq
}(documentId, clientId, clientSeq) 是重試冪等鍵。durableOffset 便於確認和審計,不參與 CRDT 合併正確性。 訊息有最大大小、解壓上限和 Schema 白名單;未知節點或越權域修改被明確拒絕,不能直接廣播。
第三步:拆分載入、即時房間和儲存路徑。
Client
-> HTTPS snapshot service -> metadata + snapshot store
-> WebSocket gateway -> document room router -> collaboration service
-> durable update log
-> room pub/sub -> gateways
Client
-> presence channel -> regional ephemeral store -> room fan-out用戶端先透過 HTTPS 取得文件元資料、目前 Schema 和最近壓縮快照,再建立 WebSocket。閘道持有連線和房間訂閱, 不自創合併規則。協同服務按文件路由到 home region,驗證權限與更新,持久化成功後 ack 並發布。房間訊息只 傳送一次到每個有訂閱者的閘道,由閘道在本機扇出,避免伺服器端為每個接收者重複發布到跨節點總線。
第四步:把本機編輯、確認和重試做成明確狀態機。
本機編輯立即更新視圖,並追加到本機持久 pending 佇列。線上時按 20 到 50 毫秒的短時間窗合併相鄰擊鍵,減少 訊息數;計時是輸入假設,需要用體驗測試調整。伺服器端收到更新後依次執行:認證會話、查詢目前權限、驗證 Schema 與資源限制、寫入複製日誌、回傳 ack、廣播。只有 ack 後用戶端才能從 pending 佇列刪除該更新。
若連線在提交後、ack 前斷開,用戶端用相同 clientSeq 重試。伺服器端唯一約束回傳原確認結果,或依賴 CRDT 更新冪等性安全重複套用,但審計和計費仍應去重。慢用戶端有有限傳送緩衝;超過水位後停止傳送 presence, 再要求用戶端從狀態向量重新同步,不能讓一個慢連線無限佔用房間記憶體。
第五步:用狀態向量完成上線、離線和重連同步。
CRDT 更新的可交換、可結合和冪等性質允許副本以不同順序接收並安全重試。用戶端重連時傳送狀態向量,描述 自己已經擁有的因果狀態;伺服器端據此計算缺失差量。若差量過大、Schema 已升級或歷史超出線上保留時間窗, 伺服器端傳送目前完整 CRDT 快照,再套用用戶端仍獲授權的本機更新。
同步順序必須先認證、再授權、再協商 Schema,最後交換內容。使用者離線期間若編輯權限被撤銷,伺服器端不能因 更新能收斂就接收它;應回傳穩定的 permission_revoked,保留本機草稿供使用者匯出或複製,但不寫入共享文件。 這條邊界犧牲“所有離線編輯必合併”,換取目前存取控制正確性。
第六步:設計快照、版本歷史和壓縮。
持久日誌按 documentId 分割區,記錄更新 ID、作者、Schema、酬載、接收時間和耐久偏移。背景壓縮器載入 CRDT 狀態,將多個增量合成可直接載入的快照,並記錄覆蓋到的偏移。只有新快照驗證透過、物件儲存耐久且仍保留 恢復點後,才刪除超出恢復與審計時間窗的舊增量。
合併二進位更新只會消除重複資訊,並不自動垃圾回收已刪除內容。刪除標記、離線同步、評論錨點、按使用者撤銷 和歷史版本相互影響,壓縮策略必須用真實文件驗證。使用者可見版本歷史儲存獨立的命名快照或變更索引,不能把 線上同步壓縮等同於產品版本歷史。
第七步:分離 presence,並控制熱點扇出。
presence 包含會話、顯示名、顏色、相對游標、選區和單調 statusSeq,透過心跳刷新 TTL。它不進入正文 CRDT 和耐久日誌;亂序狀態按序號丟棄,斷線後自動過期。遺失一次游標更新只影響短暫顯示,下一次更新會覆蓋。
一個 100 人熱點房間在每人 2 次更新/秒時有 200 次正文更新/秒;每次向另外 99 人傳送,約為 19,800 次 遠端投遞/秒,還未計算 presence。閘道要把同一時間片的更新批次編碼,按文件房間聚合發布,presence 採樣到 固定上限,並對每連線設定位元組和訊息水位。超熱點文件可獨佔房間 actor 和 pub/sub 分割區,但不能透過把正文 更新採樣來降載。
第八步:復算全域容量和連線資源。
20 萬名編輯者每秒 2 個更新,峰值為 40 萬更新/秒。按平均 300 位元組二進位酬載計算,原始寫入約 120 MB/s, 一天約 10.37 TB;複製、索引、訊息頭、快照和版本歷史另計。若每條線上連線在閘道佔用 50 KiB,200 萬連線 約需 100 GiB 連線狀態;按每臺閘道 2 萬連線計算,至少 100 臺,再加故障和發布餘量。
這些數值是容量起點。真正瓶頸往往是熱點房間出站頻寬、TLS 與編碼 CPU、慢用戶端緩衝和持久日誌分割區,而非 原始正文儲存。監控應按文件和租戶觀察更新率、扇出率、ack 延遲、狀態向量差量、重連次數、緩衝水位、快照 年齡和收斂驗證失敗。
第九步:約束多區域、故障切換與安全。
文件元資料記錄 home region 和遞增 epoch。用戶端就近接入閘道,但正文寫入路由到 home region;本機先套用 使輸入不受跨區域 RTT 影響。home region 故障時,控制面先提升 epoch,再由新區域從複製日誌和最新快照恢復。 舊 actor 的 epoch 被持久層拒絕,避免兩個房間所有者同時作出耐久確認。
CRDT 能讓內容更新收斂,卻不能替代單一授權決策、耐久 ack 或故障切換 fencing。安全上還要限制文件大小、 單次更新、更新頻率和壓縮比;驗證 Origin、會話與文件權限;加密傳輸和靜態資料;審計分享、權限與匯出; 不把存取權杖、完整文件或游標細節寫入普通日誌。
第十步:用性質測試和故障注入證明設計。
同一初始文件生成多用戶端更新,把它們隨機亂序、重複、分批和延遲套用,所有副本最終序列化結果必須相同且 Schema 合法。再覆蓋同位置插入、交叉刪除、刪除父塊同時編輯子塊、格式與文字併發、評論錨點、按使用者撤銷和 Schema 升級。
系統測試要在日誌寫入成功後、ack 前殺死協同服務,確認重試只產生一個審計更新;在 ack 後、廣播前殺行程, 確認恢復會補發;斷網 24 小時後重連,驗證狀態向量差量和快照路徑;撤銷權限後提交離線更新,必須拒絕共享 寫入並保留本機恢復入口。最後對 100 人熱點房間壓測更新與 presence,確認 p95 延遲、記憶體水位和降級順序。
高品質示範回答
“我先把目標拆成三條:本機輸入立即生效;拿到全部更新的副本最終收斂;只有寫入跨三個可用區持久日誌的 更新才對用戶端確認。題目需要 24 小時離線編輯,因此我選成熟的結構化 CRDT,並保留伺服器端作為認證、Schema、 持久化和扇出的權威。
用戶端載入 CRDT 快照後加入文件房間。本機編輯先套用,並以 clientId + clientSeq 寫入 pending 佇列;線上時 透過 WebSocket 傳送二進位更新。協同服務重新驗證目前權限、Schema 和資源上限,持久化後 ack,再向房間發布。 重試使用相同序號,CRDT 更新本身可亂序、重複套用。重連時用戶端傳送狀態向量,伺服器端回傳缺少的更新;差距 過大則傳送完整快照。權限在離線期間被撤銷時,更新不能合併到共享文件,只允許匯出本機草稿。
正文與 presence 分開。游標使用相對位置,presence 依賴心跳和 TTL,不寫耐久日誌。每個文件有 home region 和 epoch;故障切換先提升 epoch,再從複製日誌與快照恢復,舊房間所有者不能繼續確認更新。
容量上,20 萬編輯者每秒 2 次更新是 40 萬更新/秒;按 300 位元組為 120 MB/s、10.37 TB/日原始更新。100 人 熱點文件約 200 次更新/秒、19,800 次遠端投遞/秒,所以按房間聚合發布、批次編碼、限制慢用戶端緩衝,並只對 presence 採樣。驗證上我會隨機排列和重複同一組更新,要求所有副本收斂;再注入 ack 前後崩潰、24 小時離線、 權限撤銷、Schema 升級和 home region 故障,證明確認資料不丟、越權更新不進入共享狀態。”
常見錯誤
- 只說使用 WebSocket → 傳輸層不解決併發衝突 → 明確選擇 OT 或 CRDT,並說明收斂條件和代價。
- 每次儲存整篇文件 → 併發使用者會互相覆蓋,頻寬也隨文件大小增長 → 傳送可合併的增量更新。
- 伺服器端收到記憶體就 ack → 行程崩潰會丟掉已確認編輯 → 以跨可用區耐久日誌提交作為確認邊界。
- 把 CRDT 當成權限系統 → 數學收斂不會阻止被撤權使用者寫入 → 每次加入、重連和寫入都重新授權。
- 用整數儲存遠端游標 → 併發插入會讓位置漂移 → 使用與 CRDT 元素關聯的相對位置。
- 把游標寫進正文日誌 → 高頻臨時狀態放大儲存與恢復成本 → presence 使用 TTL 和獨立通道。
- 聲稱 exactly-once → 斷線重試和廣播可能重複 → 使用冪等更新、唯一操作識別碼和可重放狀態。
- 快照成功後立即刪完歷史 → 離線、回滾、撤銷或 Schema 遷移可能失去依據 → 按明確恢復時間窗和驗證水位回收。
- 只算正文寫入 → 熱點房間的扇出和慢用戶端常先耗盡資源 → 同時計算每房間投遞、出站頻寬和緩衝。
- 跨區域全部主動寫卻沒有控制面 → 授權、ack 和故障切換邊界模糊 → 用 home region 與 epoch 簡化耐久所有權。
追問及應對
追問一:為什麼選擇 CRDT,不選擇 OT?
離線 24 小時和可能亂序、重複的跨區域傳輸更符合 CRDT 的更新模型,成熟實現還能用狀態向量交換差量。OT 並非錯誤;若已有經過驗證的伺服器端轉換引擎、所有編輯都經過單一有序服務、離線範圍很小,OT 可能有更可控的 元資料和伺服器端語義。選擇依據應來自產品約束與團隊能力,不能只說某個名詞更先進。
追問二:CRDT 是否意味著可以刪除持久更新日誌?
不可以。CRDT 解決更新合併與收斂;已確認不丟、審計、版本歷史、恢復和新裝置載入仍需要耐久狀態。可以把 多個增量壓縮為快照並按恢復時間窗回收舊日誌,但必須保留可驗證的耐久邊界和恢復點。
追問三:離線使用者回來時權限已被撤銷怎麼辦?
先重新認證和授權,再交換內容。伺服器端拒絕共享寫入並回傳穩定錯誤;用戶端保留本機副本,允許使用者複製或 匯出,不能悄悄丟棄。若業務允許管理員複核,可把草稿提交到隔離審核區,但它不能繞過目前文件 ACL。
追問四:如何實現多人環境下的撤銷?
預設撤銷目前使用者自己的最近本機操作,並讓逆操作繼續透過 CRDT 合併;不能簡單恢復整篇舊快照,否則會抹掉 他人後續編輯。操作來源要標記使用者和交易邊界,刪除內容與歷史保留策略也要支援目標撤銷時間窗。複雜結構變更 需要編輯器繫結層定義可逆語義,並透過併發測試驗證。
追問五:如何防止游標在併發編輯後跳到錯誤位置?
游標和評論錨點儲存相對 CRDT 元素的位置,而非絕對字元下標。遠端更新套用後再把相對位置解析為目前索引。 如果錨定元素和父結構都被刪除,解析可能失敗,產品應隱藏游標、把評論標記為失去錨點,或回退到最近有效區塊。
追問六:一個文件突然有一萬人圍觀怎麼辦?
把編輯者和只讀觀看者分開。正文更新在房間總線上發布一次,由各閘道本機扇出;只讀端可使用合併後的較低頻 更新或短輪詢快照,但不能改變編輯者的正確性。presence 只對可見或抽樣使用者展示,並設定每連線傳送緩衝。 若產品確實要求一萬人都看全部游標,需要單獨預算頻寬和 UI,可用性不能靠無限廣播假設。
追問七:能否做端到端加密?
可以讓用戶端加密 CRDT 更新,伺服器端只中繼和持久化密文,但代價是伺服器端難以驗證富文字 Schema、搜尋、內容 審核、資料遺失恢復和細粒度匯出;金鑰輪換、成員移除及舊成員離線更新也更複雜。應先定義威脅模型,再決定 文件級金鑰、裝置金鑰和成員變更協議,不能只在 WebSocket 外層加 TLS 就稱為端到端加密。
追問八:如何發現副本已經靜默分叉?
用戶端在安靜時間窗或重連後上報文件狀態向量和非敏感狀態摘要;伺服器端比較同一耐久水位的結果,發現不一致就 觸發完整重同步並保留診斷樣本。持續 canary 文件從多個區域執行已知併發更新,再驗證最終摘要、Schema、ack 數量與日誌水位。摘要必須綁定相同快照和更新集合,否則不同在途水位會製造假告警。