題幹與適用場景
設計一個類似 Dropbox 或 Google Drive 的雲端檔案儲存與多裝置同步服務。系統有 5,000 萬註冊使用者、 500 萬日活躍使用者,平均每位使用者儲存 10 GB 邏輯資料。每天產生 1 億個新檔案版本,平均每個版本新增 或修改 4 MB;尖峰流量按日均的 5 倍估算。單一檔案最大 50 GB,線上裝置應在 5 秒內看到建立、修改、 移動與刪除等中繼資料變更,中繼資料服務可用性目標為 99.99%。
題目中的規模、延遲、可用性與 4 MiB 目標分片大小都是面試設計輸入,不代表任何雲端儲存產品的公開承諾。 範圍包括上傳、下載、斷點續傳、版本還原、多裝置同步、離線編輯、衝突副本、簡單分享與刪除回收。多人同時 編輯同一文件的字元級協作、Office 檔案語意合併、完整企業權限系統與跨區域主動多主寫入不在主問題範圍內。
2026 年仍有公開系統設計題目以 Dropbox、Google Drive 或通用檔案同步服務為背景,要求候選人討論大型 檔案分片、增量同步、版本、離線操作與衝突。物件儲存官方文件也能驗證分片上傳的工程價值:分片可平行上傳, 單片失敗只需重傳該片,但未完成的分片需要主動終止或透過生命週期清理。面試重點是把「內容傳輸」與「檔案 命名空間狀態」分開,並關閉失敗恢復與資料回收的正確性迴路。
面試官考察點
第一,看候選人能否把檔案內容與中繼資料分層。大型檔案位元組適合放在便宜、持久、不可變的物件儲存中; 檔名、目錄關係、目前版本、刪除標記與同步游標需要支援條件寫入與有序變更。若把 50 GB 檔案直接寫入 關聯式資料庫,或讓物件儲存鍵名承擔全部目錄語意,修改、移動、交易提交與恢復都會變得脆弱。
第二,看上傳是否有明確提交點。用戶端可以先平行上傳缺少的分片,但只有伺服器確認全部分片存在且校驗通過, 並原子更新 currentVersionId 與變更日誌後,新版本才對其他裝置可見。否則會出現中繼資料指向缺失內容, 或檔案位元組已經上傳但使用者永遠看不到的懸掛狀態。
第三,看同步協定是否能抵抗通知遺失、重複與亂序。推播通知只能是「有新變化」的提示,裝置必須攜帶持久化 游標拉取權威變更。離線數天的裝置、重新安裝用戶端與游標過期都要有恢復路徑,不能把 WebSocket 連線當成 資料真相。
第四,看並行語意是否誠實。兩個離線裝置基於同一舊版本修改一般二進位檔時,通用服務無法可靠自動合併。 條件提交失敗後應保留雙方版本並產生衝突副本,不能靜默使用最後寫入者覆蓋。Dropbox 的公開說明文件也指出, 同時或離線修改可能產生 conflicted copy,需要使用者人工合併。
最後,看容量、成本與回收是否形成完整系統。候選人要區分邏輯容量與去重後的實體容量,估算版本提交與位元組 流量,說明孤兒分片、歷史版本、刪除墓碑、配額、熱門命名空間,以及垃圾回收為什麼不能只依靠即時參考計數。
回答前需要澄清的問題
- 同步哪些內容? 本題同步一般檔案與目錄;不提供字元級即時協作。
- 一致性目標是什麼? 上傳成功的裝置需要讀到自己的新版本;其他線上裝置在 5 秒內最終看到中繼資料變化。
- 離線多久仍需增量恢復? 先假設變更日誌保留 30 天;游標更舊時回傳命名空間快照再繼續增量。
- 如何處理並行修改? 同一
baseVersionId只允許一個版本成為目前版本,後到者保留為衝突副本。 - 刪除能否立即實體清理? 不能。先寫墓碑並保留 30 天,以支援離線裝置同步與使用者還原。
- 是否做跨使用者全域去重? 預設只在同一帳戶或租戶內去重,避免洩漏內容是否存在,也降低加密與刪除耦合。
- 分享範圍多大? 主設計支援檔案或目錄的唯讀分享連結;複雜組織權限放到追問。
- 加密與惡意檔案怎麼辦? 傳輸與靜態儲存加密,短期簽名 URL,上傳後非同步惡意軟體掃描;端對端加密不在主範圍。
- 多區域如何寫入? 每個命名空間有一個主寫區域;物件可跨區域複製,中繼資料跨區域非同步災備。
- 秒傳是否一定命中? 不假設固定去重率。邏輯配額按使用者檔案大小計費,實體節省另行觀測。
30 秒回答框架
「我會把檔案切成約 4 MiB 的不可變分片放入物件儲存,把目錄、目前版本、分片清單、墓碑與有序變更放入 強一致中繼資料層。用戶端只上傳缺少分片,再用 baseVersionId 與冪等鍵原子提交版本。裝置持久化同步 游標,通知只觸發 /changes?cursor= 拉取,所以遺失通知不會遺失資料;並行離線提交失敗時保留衝突副本。 每天 1 億版本的 5 倍尖峰約 6,000 次提交/s,400 TB 日均寫入的尖峰約 25 GB/s。刪除先保留墓碑,分片 經寬限期與清單二次核對後再回收。」
分步驟深入解答
先定義五個不變量:
- 已發布版本只能參考已存在且校驗通過的分片。
- 只有
baseVersionId等於目前版本時,提交才能替換currentVersionId。 - 每個命名空間的變更序號嚴格遞增,用戶端游標只向前推進。
- 刪除先產生墓碑;在同步與還原窗口結束前,不實體刪除仍可能被參考的資料。
- 垃圾回收只刪除經過寬限期與清單核對後仍無參考的分片。
第一步:拆分用戶端、中繼資料與內容平面。
用戶端包含檔案監視器、本機索引、持久化操作日誌、分片器與同步引擎。程序崩潰後,它從本機日誌恢復「哪些 分片已上傳、哪個提交仍未確認」,不重新掃描並上傳全部檔案。伺服器包含 API 閘道、驗證與配額、中繼資料 服務、上傳協調器、物件儲存、命名空間變更日誌、通知服務、下載 CDN、惡意檔案掃描與垃圾回收工作。
中繼資料按 namespaceId 路由。個人空間是一個命名空間,共享目錄也可成為獨立命名空間,讓權限、有序 變更與熱點隔離維持在同一邊界。每個命名空間先指定一個主寫區域,跨可用區同步複製中繼資料;下載可從就近 CDN 或物件副本讀取。這樣可避免跨區域兩個主節點同時接受互相衝突的目錄更新。
第二步:定義資料模型。
FileEntry(id, namespaceId, parentId, name, type, currentVersionId, deletedAt)
FileVersion(id, fileId, baseVersionId, size, manifestHash, createdBy, createdAt)
VersionChunk(versionId, ordinal, chunkHash, size)
UploadSession(id, fileId, baseVersionId, state, expiresAt, idempotencyKey)
Change(namespaceId, seq, entityId, operation, versionId, createdAt)目錄結構使用穩定 fileId 與 parentId,移動或重新命名只更新中繼資料,不複製檔案內容。FileVersion 不可變;VersionChunk 按順序組成檔案清單。manifestHash 用於校驗清單,不取代每個分片的校驗。 UploadSession 記錄工作階段狀態與冪等鍵。Change.seq 是命名空間內單調序號,刪除也作為事件寫入。
目錄下的名稱唯一性用 (namespaceId, parentId, normalizedName) 條件約束維護。大小寫規則必須在產品定義 中固定,否則 Windows、macOS 與 Linux 用戶端會對同名檔案得出不同結論。
第三步:設計可恢復的分片上傳。
POST /files/{fileId}/upload-sessions
{ baseVersionId, size, chunks[], idempotencyKey }
-> { uploadSessionId, missingChunks[], signedUrls[] }
PUT {signedChunkUrl}
Content-Checksum: ...
POST /upload-sessions/{uploadSessionId}/commit
{ manifestHash, idempotencyKey }
-> { fileVersionId, changeSeq }
GET /files/{fileId}/download-manifest
-> { fileVersionId, chunks[], signedUrls[] }用戶端預設切成約 4 MiB 分片並計算強雜湊。伺服器在帳戶或租戶範圍查詢已存在分片,只為缺少分片發放 短期、限物件、限操作的簽名 URL。用戶端可有限平行上傳,失敗只重試單片。AWS 與阿里雲的官方分片上傳 文件都採用「初始化、上傳各片、完成」的工作階段模型,也提醒未完成分片會持續占用儲存,因此上傳工作階段 必須過期,背景也要主動終止長期未完成的工作階段。
固定大小分片簡單、易於平行,適合大多數檔案。若大量工作負載是在檔案開頭插入少量位元組,固定邊界會讓 後續所有分片雜湊變化;這時可對大型檔案採用內容定義分塊,以更多 CPU 與實作複雜度換取更好的差量命中。 本題先用固定分片,依真實修改模式再升級,不能把複雜演算法當成預設答案。
第四步:把版本提交做成唯一可見性邊界。
提交介面先按 idempotencyKey 查詢既有結果,再校驗分片大小、雜湊、權限與配額。接著在一個中繼資料 交易中:
- 鎖定或條件讀取
FileEntry.currentVersionId。 - 確認它仍等於請求的
baseVersionId。 - 寫入不可變
FileVersion與VersionChunk清單。 - 更新
currentVersionId。 - 寫入下一筆
Change.seq與交易 outbox。
物件位元組先於中繼資料交易完成,因此交易不會指向不存在的分片。交易提交後通知傳送失敗也不影響正確性: outbox 會重試,裝置也會透過游標補拉。若提交回應遺失,用戶端用同一冪等鍵重試並取得原 fileVersionId,不會產生多個邏輯版本或重複扣減配額。
第五步:用游標完成多裝置同步。
GET /changes?namespaceId={id}&cursor={lastSeq}&limit=1000
-> { changes[], nextCursor, hasMore }裝置把 lastSeq 與本機檔案索引放入同一持久化交易。收到「命名空間有變化」的通知後,它循環拉取直到 hasMore=false,按序套用建立、更新、移動與墓碑,然後才提交新游標。重複事件由 seq 去重;通知亂序 不影響拉取結果;通知完全遺失時,前景喚醒與週期性檢查仍會發現新序號。
若游標早於 30 天保留窗口,服務回傳 cursor_expired 與一致的命名空間快照位置。用戶端先下載快照, 核對本機未提交操作,再從快照水位繼續增量,不能直接刪除所有本機檔案。通知是效能最佳化,游標日誌才是 同步協定。
第六步:處理衝突、刪除與版本還原。
裝置 A、B 都從版本 10 離線編輯。A 先以 baseVersionId=10 提交版本 11。B 再提交時條件更新失敗, 服務保留 B 上傳的內容,建立類似「檔名(B 的衝突副本)」的新項目或衝突版本,並向命名空間寫入一筆變化。 一般二進位檔不做靜默自動合併;文字或特定文件的合併能力應作為獨立產品功能。
刪除只把 deletedAt 與墓碑事件寫入中繼資料。線上裝置收到後移入垃圾桶,離線裝置重新上線也能得知刪除。 30 天還原窗口內,歷史版本與分片參考仍然有效。窗口結束後,清理器先計算所有活躍版本清單,再標記候選 無參考分片,等待寬限期並二次核對後才刪除。即時參考計數可能因交易重試、延遲事件或修復工作出錯,不能 單獨成為不可逆刪除依據。
第七步:估算容量並處理熱點。
5,000 萬 × 10 GB = 500 PB 邏輯儲存。副本、歷史版本與去重會改變實體容量,但題目沒有提供這些比例, 所以不能給出假精確結果。每天 1 億 × 4 MB = 400 TB 邏輯寫入,日均約 4.6 GB/s,5 倍尖峰約 25 GB/s。版本提交日均約 100,000,000 / 86,400 ≈ 1,157/s,尖峰取整約 6,000/s。
若每次變更平均提示 3 台線上裝置,通知尖峰可達約 18,000 次/s,但通知可合併為「某命名空間有新變化」, 不必逐檔可靠投遞。中繼資料按 namespaceId 雜湊分片,同一命名空間的有序寫入落在同一主分片。大型共享 空間可能成為熱點,可限制單一目錄操作速率、批次合併通知,並在確有證據時把檔案記錄按 fileId 子分片, 同時保留獨立的命名空間序號產生器。
第八步:關閉故障、安全與驗證迴路。
- 上傳中斷:用戶端查詢工作階段並只補傳缺少分片;過期工作階段由生命週期工作終止。
- 分片完成但提交失敗:分片暫時成為孤兒,寬限期後由清單核對回收。
- 提交成功但通知失敗:交易 outbox 重試,裝置游標拉取仍可恢復。
- 提交成功但回應遺失:同一冪等鍵回傳原結果。
- 並行離線寫入:
baseVersionId條件失敗,儲存衝突副本。 - 分片損壞:上傳與下載都校驗強雜湊,錯誤內容不能進入已發布清單。
- 游標過期:下載快照,再從快照水位繼續增量。
- 主區域無法寫入:停止該命名空間寫入或按明確 RPO/RTO 故障轉移,不能讓兩個區域同時成為主寫者。
安全上,簽名 URL 必須短期有效並綁定帳戶、物件、大小與操作;伺服器在提交前重新做權限與配額校驗。跨使用者 全域秒傳容易形成「某內容是否存在」的旁路,也會讓每使用者加密金鑰與刪除權互相耦合,因此預設只在租戶內 去重。核心指標包括上傳與提交 p99、同步落後、游標過期、衝突副本數、孤兒分片位元組、去重命中、垃圾回收 候選與實際刪除差異、熱門命名空間、校驗失敗與恢復成功率。
驗證要涵蓋 50 GB 檔案斷點續傳、每個上傳階段崩潰、重複提交、通知遺失與亂序、兩個離線裝置並行修改、 刪除後舊裝置上線、校驗和損壞、主分片切換、過期游標、配額邊界與垃圾回收誤刪保護。最重要的端對端斷言是: 任何用戶端可見的 fileVersionId 都能下載並校驗出完整內容,而且仍在還原窗口內的版本不會被回收。
高品質示範回答
「我先把系統拆成內容平面與中繼資料平面。檔案內容按約 4 MiB 切成不可變分片,存入物件儲存;目錄、穩定 fileId、目前版本、版本清單、墓碑與命名空間變更序號放入支援條件寫入的中繼資料層。移動與重新命名只改 中繼資料,歷史版本不覆蓋。
上傳時用戶端計算分片雜湊,並用 baseVersionId 與冪等鍵建立工作階段。伺服器只回傳租戶內缺少分片的 短期簽名 URL。所有分片上傳並校驗後,提交交易確認目前版本沒有改變,寫入新版本與清單,更新 currentVersionId,同時附加 Change.seq 與 outbox。分片先完成、中繼資料後發布,確保可見版本不參考 缺失內容;回應遺失時同一冪等鍵回傳原結果。
同步以持久化游標為準。通知只說命名空間可能有變化,裝置呼叫 /changes?cursor= 依序拉取並在套用後推進 游標,所以通知遺失、重複與亂序都不會遺失資料。游標超過 30 天保留窗口時,用一致快照重建,再從快照水位 繼續。兩個裝置從同一版本離線修改時,後到提交不能覆蓋新版本,而是保留為衝突副本。
容量上,邏輯儲存是 500 PB;每天新增或修改 400 TB,平均約 4.6 GB/s、5 倍尖峰約 25 GB/s。每天 1 億版本平均約 1,157 次提交/s,尖峰約 6,000/s。中繼資料按命名空間分片,每個命名空間一個主寫區域; 物件下載可經 CDN 與跨區域副本擴充。
刪除先寫墓碑並保留 30 天。歷史版本到期後,垃圾回收根據全部活躍清單標記候選,等待寬限期並二次核對, 不能因一次參考計數為零就立即刪除。最後我會故障注入每個上傳與提交階段,驗證通知遺失、離線衝突、過期 游標、校驗損壞、主節點切換與 GC 安全,並持續檢查所有可見版本都能完整下載。」
常見錯誤
- 把檔案位元組放入中繼資料庫 → 大型物件拖累備份、複製與交易 → 中繼資料保存參考,內容進入不可變物件儲存。
- 上傳完第一片就建立可見檔案 → 其他裝置可能讀到殘缺版本 → 全部分片驗證後再原子提交中繼資料。
- 把 WebSocket 通知當同步真相 → 遺失封包或離線會永久漏變化 → 通知只觸發基於游標的權威拉取。
- 提交不帶
baseVersionId→ 離線裝置靜默覆蓋較新版本 → 使用條件提交,失敗時保留衝突副本。 - 失敗重試產生新工作階段與新冪等鍵 → 重複版本、重複配額與孤兒分片增加 → 穩定冪等鍵找回原結果。
- 預設跨使用者全域秒傳 → 暴露內容存在性並耦合加密、刪除 → 預設限制在帳戶或租戶範圍。
- 刪除後立刻清理分片 → 離線同步、版本還原與延遲交易會參考已刪內容 → 墓碑、保留期、寬限期與二次核對。
- 用固定去重率推算實體容量 → 工作負載未知導致假精確 → 報告 500 PB 邏輯量,實體節省用監控校準。
- 每個檔案變化都可靠推送給每台裝置 → 通知成本與重試狀態爆炸 → 合併為命名空間提示,由用戶端拉增量。
- 直接做跨區域主動多主 → 同名、移動與目前版本衝突難以收斂 → 每個命名空間先維持單主寫入。
追問及應對
追問一:固定分片與內容定義分塊如何選?
固定 4 MiB 左右的分片實作簡單、可預測,適合附加、局部覆蓋與多數媒體檔案。若使用者常在檔案開頭插入 內容,固定邊界會讓後續雜湊全部變化;內容定義分塊能找回相同內容,但消耗更多 CPU,邊界演算法也需要穩定 版本化。先用固定分片上線並測量「修改後可重用位元組比例」,只有真實收益足以覆蓋複雜度時,再對特定大型 檔案啟用內容定義分塊。
追問二:如何保證移動目錄時同步順序正確?
移動操作在中繼資料交易中更新 parentId,並寫入一個命名空間序號。用戶端嚴格按 seq 套用;若一次 移動涉及大量子孫檔案,不逐一重寫路徑,因為路徑由父鏈推導。跨命名空間移動不能假裝成單筆本機更新,可 建成可重試的複製加刪除工作流程,並在 UI 中顯示處理中。
追問三:如何支援端對端加密與秒傳?
用戶端端對端加密後,伺服器通常只能看到密文。若每個使用者使用不同金鑰,相同明文不會產生相同密文, 跨使用者去重基本失效;強行做收斂加密又會引入內容確認攻擊與金鑰風險。應明確產品優先順序:高隱私模式 接受較低去重,租戶託管金鑰模式可在租戶邊界內去重,不能同時承諾無條件全域秒傳與強端對端保密。
追問四:一個超大型共享目錄成為熱點怎麼辦?
先合併通知、限制批次操作、快取唯讀目錄頁,並測量熱點來自序號產生、名稱唯一約束或列表查詢。檔案中繼 資料可按 fileId 子分片,但命名空間變更仍需要一個有序水位;可以把多個內部事件批次分配序號區段或寫入 分區日誌,再提供穩定的對外游標。不能為了水平擴充放棄用戶端可恢復的順序語意。
追問五:垃圾回收如何避免誤刪?
GC 不直接相信即時參考計數。它先從仍在保留期內的所有版本清單建立「活躍集合」,把不在集合中的分片標記 為候選,等待超過最大交易、複製與恢復延遲的寬限期,再用最新清單二次核對後刪除。刪除工作冪等並記錄稽核; 發現清單缺失或核對差異時寧可延遲回收。物件儲存未完成的 multipart parts 還需要獨立的工作階段過期與 abort 策略。
追問六:如何做跨區域災難恢復?
每個命名空間正常只有一個 home region 接受中繼資料寫入,其他區域非同步複製日誌與物件。故障時先確認 原主不再寫入,再把災備副本提升到新紀元並切換路由;RPO 取決於日誌複製落後,RTO 取決於偵測與提升流程。 若要求 RPO=0,就需要跨區域同步多數派並接受更高寫入延遲。不能讓舊主恢復後直接繼續寫,必須校驗命名空間 領導紀元。
追問七:如何證明同步不會漏資料?
建立狀態模型,產生上傳、提交、移動、刪除、衝突與重試序列,斷言用戶端套用到序號 N 後的狀態等於伺服器 快照 N。端對端測試隨機丟棄、複製與打亂通知,但不竄改變更日誌;裝置仍應透過游標收斂。再在「套用變更但 尚未儲存游標」與「儲存本機內容但程序崩潰」等邊界注入故障,確認重播冪等,最終所有裝置達到相同版本圖。