免费完整指南

文件同步系统

设计文件同步系统。请覆盖File chunking strategy and content-hash based deduplication across users, Delta sync: rsync-style block diffing instead of re-uploading whole files, Metadata service (file tree, versions)...

00

练习检查点

面试节奏保持精简,让页面能把注意力花在真正的设计决策上。

  1. 01
    澄清范围
  2. 02
    需求 + 规模
  3. 03
    API + 数据模型
  4. 04
    画出架构
  5. 05
    深入探讨
  6. 06
    取舍决策
01

塑造设计的需求

不要只是陈述需求,要主动问出来。每张卡把设计约束和一句你可以在画架构前说出口的澄清问句配成一组。

功能需求

01单个文件最大可以到多大?

用户可从任何设备上传与下载,单文件最大 50 GB。

02同步是自动的吗,离线编辑会怎样?

文件在所有设备间自动同步;离线时所做的编辑在重新连接时协调——服务器上的副本是真相来源。

03分享是什么意思——一份副本,还是同一个文件?

分享是授予对同一个文件的访问权:收件者在自己的视图中看到它,而更新会传播给每一个有访问权的人。

04文件夹、移动与重命名算是文件操作吗?

不是——文件夹是元数据:移动与重命名是只动元数据的操作,永远不碰已存储的块,所以不论文件多大都是瞬间完成。

05分享带有权限吗——只读 vs 可编辑?

会:分享授权带有一个角色(viewer/editor),在元数据服务端强制执行,而 presigned URL 的范围被限定在提出请求的那个角色。

06删除会同步吗,用户能救回被删的文件吗?

删除像任何编辑一样以 tombstone 传播,并在块被回收之前有一段回收站期——把误同步救回来需要一套撤销机制。

范围外原地协同编辑(那是协同文档编辑那一题) · 不下载就预览与渲染 · 版本历史的 UI(数据模型保留 latest_version,但浏览历史不在范围内)

非功能需求

01当网络分区时,什么必须继续运作?

可用性优先于一致性:文件列表过时个几秒没关系;让上传失败则不行。

02一个 50 GB 的上传在 49 GB 时挂掉会怎样?

它从最后一个已验证的块续传——绝不重来。块状态在服务器端追踪,所以任何设备都能接续这次上传。

03我们如何知道一个文件在传输途中没有损坏?

每一个块与整个文件都带有 SHA-256 指纹;一个块只有在存储层确认字节之后才被标记为已上传。

04两位用户上传同一支 2 GB 视频——我们会存两份吗?

不会:相同的指纹就代表相同的内容——元数据把两位用户都指向同一批已存储的块。

05跨设备同步需要多实时?

几秒:在线的设备会被推送变更通知,并以周期性轮询作为安全网。

持续追问 —— 面试是一场对话

真实面试探得比一份整齐清单深得多。这些范围问句,区分出真正拷问问题的人和只是背诵的人。

  • 最大文件大小是多少——几 MB 还是数十 GB?
  • 原地协同编辑在范围内吗,还是只把文件当成 blob?
  • 当两个设备离线编辑同一个文件,用户最后应该看到什么?
  • 我们需要版本历史,还是只要最新版本?
  • 不同用户上传的相同内容应该只存一份吗?
02

逼出架构决策的数字

把每个估算都当成一种压力,用来合理化一个组件:缓存、队列、分片、副本、worker pool 或退路。

01

大文件的块数

50 GB 文件 ÷ 5 MB 块50,000 MB ÷ 5 MB = 10,000 个块

一万个各自独立、并行、可单独续传的上传——这就是为什么一个巨大的 POST 永远行不通。

02

失败后的续传成本

上传在 98% 时挂掉重试 = 剩下的约 200 个块,而不是 10,000 个

块状态追踪把一次灾难性的重来变成 2% 的补齐。

03

去重的效益

同一个文件被 N 位用户上传,指纹相符存储成本 = 1 份副本 + N 行元数据

以内容定址的存储让第二次以及之后的每一次上传几乎免费。

04

改一个字节

固定大小分块 vs 内容定义分块(CDC)固定:插入 1 字节会位移每一个边界 → 几乎所有块都重新上传 · CDC:只有被动到的块

CDC(rolling hash)正是让被编辑过的文件做 delta sync 变便宜的关键。

05

presigned URL 的时间窗

URL 有效约 5 分钟,范围限定在单一块泄露窗口 = 几分钟 · 波及半径 = 一个块

短效、窄范围的 URL 就是客户端直传的安全论述。

决策示例

数字

一个 50 GB 的文件是一万个 5 MB 块。在那个尺寸下,有意思的问题不是存储——而是续传、去重,以及当两个设备编辑同一个文件时会发生什么。

我的选择

我会让客户端在本机把文件分块并算指纹,然后用短效的 presigned URL 把块并行地直传到 blob 存储——API 只居中处理元数据,并在把块标记为完成之前验证块的 ETag。同步是一个推送通知加上一个 changes-since 的 cursor,让每个设备去协调。至于冲突:主副本采用 last-write-wins,而落败的那次编辑会被保留为旁边的一份冲突副本——用户什么都不会失去,并自行解决。

避免

我不会做的事:把文件字节绕经我的 API 服务器——它们会变成带宽瓶颈与攻击面,却毫无好处。而且我不会在冲突时默默丢掉落败的编辑:纯 last-write-wins 对一份文件「列表」没问题,但对文件「内容」而言,悄悄丢掉某人一整个下午的成果,正是你流失客户的方式。

何时改变

如果原地协同编辑进入范围,blob 层级的同步就完全是错的工具——那会变成在文档结构上做 operational transform 或 CRDT:一套不同的设计,也是一道不同的面试题。

03

架构路径

先看一张完整的图,再把每条路径各自画成一张图 —— 写入路径与读取路径承载不同流量,合理化不同的组件。

完整全貌

总览 —— 每个组件

Client (SyncAgent)API / MetadataServiceMetadata DBBlob StorageCDNNotificationService变更推送到各设备Chunk +Fingerprint每个块算 SHA-256Chunk Verify(ETags)信任但要查证

文件字节直接从客户端 → blob 存储(presigned URL);只有元数据经过 API。通知服务告诉其他设备去拉取 changes cursor。

路径 1

上传路径——分块、算指纹、直传 blob

ClientChunk +FingerprintPresigned URLsBlob Storage(并行)Metadata:完成

先做去重检查——已知的指纹不上传一个字节就完成。块并行上传;只有在每一个块的 ETag 都验证通过之后,文件才翻成完成。

路径 2

同步路径——先通知,再协调

Device A 保存Metadata Service推送通知Device B:changescursor经 CDN 下载

推送是门铃,changes cursor 才是真相:就算漏掉一个通知,下一次轮询也会自我修复。

04

API 与数据模型

在优化之前,先让契约可被检视:端点、实体、所有权、重试与状态。

POST/files/presigned-url

请求{ name, size, fingerprint, chunk_fingerprints[] }

响应200 { file_id, presigned_urls[] } · 200 { deduplicated: true }(当指纹已存在时)

客户端用这些 URL 把块直接上传到 blob 存储——文件字节永远不经过 API 服务器。

PATCH/files/{file_id}/chunks

请求{ chunk_id, etag }

响应200 chunk 状态

信任但要查证:服务器接受客户端的进度回报,然后在把块算作已上传之前,对 blob 存储核对 ETag。

GET/files/{file_id}/presigned-url

响应200 { url }(CDN 签章,短有效期)

下载来自 CDN edge;短效的签章 URL 让分享链接不会变成永久的公开 URL。

GET/files/changes?since={cursor}

响应200 [{ file_id, change, version }]

同步的骨干:推送通知「有东西变了」;这个 endpoint 才是设备用来协调的真相。

核心实体

FileMetadata

file_id (PK) · name · size · fingerprint (SHA-256) · latest_version · status · chunks[] {id, fingerprint, status}

file_id(身份)刻意与 fingerprint(内容)分开——重命名既不改内容,也不动已存储的块。

SharedFiles

user_id (partition key) · file_id (sort key)

每位用户对每个分享文件一行:「这位用户能看到什么」是一次单分区查询。

Device

device_id (PK) · user_id · last_synced_at

同步状态是逐设备的——每个设备从自己的 cursor 之后拉取变更。

05

深入方向

在面试最后三分之一挑一条路线。每条路线给你主题、它该回答的面试官问题,以及要避免的失败模式。

重点

那个 50 GB 的上传

把上传从头到尾走一遍:分块、并行、服务器追踪什么,以及它在 98% 时挂掉会怎样。

回答

把文件切块、并行上传,服务器端跟踪每块状态;在 98% 挂掉就从最后一个验证过的块续传——换任何设备都行。

避免

一个 multipart POST 加上从零重试——在这个尺寸下,续传本身就是功能。

重点

同一个文件,两个上传者

两位用户上传一支一模一样的 2 GB 视频。实际上存了什么,系统又怎么知道?

回答

两份上传的 SHA-256 指纹相同,块只存一份、两位用户的 metadata 都指向它;第二次“上传”其实只是一笔 metadata 写入。

避免

用文件名或大小去重——只有内容指纹(SHA-256)才定义身份。

重点

两个设备离线编辑

笔电和手机都离线编辑了同一个文件。两者都上线。用户最后拿到什么?

回答

先同步者干净胜出;后同步设备的版本以冲突副本保留在文件旁——由用户裁决,系统绝不默默丢弃任何编辑。

避免

默默只保留最后一次写入——落败的编辑必须以冲突副本的形式存活下来。

重点

字节实际上怎么流动

如果文件字节流经 API 服务器会坏掉什么,presigned URL 到底保护了什么?

回答

字节走 client → blob storage 直传,用短效、限定单块的 presigned URL;API 只发 URL 与记 metadata,文件流量永远压不垮它。

避免

长效或涵盖整个文件的 presigned URL——有效期只该几分钟,范围只该一个块。

重点

大文件里的小修改

用户改了 50 GB 文件中间的几个字节。我们要整个重传吗——怎么只同步变动的部分?

回答

内容定义分块(content-defined chunking):块边界由内容本身决定,小修改只影响碰到的那几块,同步就只重传那几块。

避免

对编辑过的文件用固定大小分块——插入一个字节会位移之后所有块边界,几乎每一块都变新的、都要重传。

准备好练习了吗?

把 文件同步系统 大声讲一遍,让 AI 为你的说明评分。

用 AI 练习这题 →