具代表性的面試主題

系統設計面試:如何設計雲端檔案儲存與多裝置同步服務?

系統設計困難
OfferCC 編輯團隊發佈 更新

題幹

設計一個雲端檔案儲存與多裝置同步服務:系統有 5,000 萬註冊使用者、500 萬日活躍使用者,平均每位使用者儲存 10 GB 邏輯資料;每天產生 1 億個新檔案版本,平均每個版本新增或修改 4 MB,尖峰流量按日均的 5 倍估算。單一檔案最大 50 GB,線上裝置應在 5 秒內看到中繼資料變更,中繼資料服務可用性目標為 99.99%。請說明 API、資料模型、分片與秒傳、同步協定、一致性、衝突與刪除、容量估算、故障處理和驗證方案。

題幹與適用場景

設計一個類似 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。刪除先保留墓碑,分片 經寬限期與清單二次核對後再回收。」

分步驟深入解答

先定義五個不變量:

  1. 已發布版本只能參考已存在且校驗通過的分片。
  2. 只有 baseVersionId 等於目前版本時,提交才能替換 currentVersionId。
  3. 每個命名空間的變更序號嚴格遞增,用戶端游標只向前推進。
  4. 刪除先產生墓碑;在同步與還原窗口結束前,不實體刪除仍可能被參考的資料。
  5. 垃圾回收只刪除經過寬限期與清單核對後仍無參考的分片。

第一步:拆分用戶端、中繼資料與內容平面。

用戶端包含檔案監視器、本機索引、持久化操作日誌、分片器與同步引擎。程序崩潰後,它從本機日誌恢復「哪些 分片已上傳、哪個提交仍未確認」,不重新掃描並上傳全部檔案。伺服器包含 API 閘道、驗證與配額、中繼資料 服務、上傳協調器、物件儲存、命名空間變更日誌、通知服務、下載 CDN、惡意檔案掃描與垃圾回收工作。

中繼資料按 namespaceId 路由。個人空間是一個命名空間,共享目錄也可成為獨立命名空間,讓權限、有序 變更與熱點隔離維持在同一邊界。每個命名空間先指定一個主寫區域,跨可用區同步複製中繼資料;下載可從就近 CDN 或物件副本讀取。這樣可避免跨區域兩個主節點同時接受互相衝突的目錄更新。

第二步:定義資料模型。

text
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 用戶端會對同名檔案得出不同結論。

第三步:設計可恢復的分片上傳。

text
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 查詢既有結果,再校驗分片大小、雜湊、權限與配額。接著在一個中繼資料 交易中:

  1. 鎖定或條件讀取 FileEntry.currentVersionId。
  2. 確認它仍等於請求的 baseVersionId。
  3. 寫入不可變 FileVersion 與 VersionChunk 清單。
  4. 更新 currentVersionId。
  5. 寫入下一筆 Change.seq 與交易 outbox。

物件位元組先於中繼資料交易完成,因此交易不會指向不存在的分片。交易提交後通知傳送失敗也不影響正確性: outbox 會重試,裝置也會透過游標補拉。若提交回應遺失,用戶端用同一冪等鍵重試並取得原 fileVersionId,不會產生多個邏輯版本或重複扣減配額。

第五步:用游標完成多裝置同步。

text
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。端對端測試隨機丟棄、複製與打亂通知,但不竄改變更日誌;裝置仍應透過游標收斂。再在「套用變更但 尚未儲存游標」與「儲存本機內容但程序崩潰」等邊界注入故障,確認重播冪等,最終所有裝置達到相同版本圖。

公開來源

同類題目

相關面試工具

用 Solve 整理系統設計回答

從澄清需求開始,展開規模、架構、元件選擇和取捨。

查看工具