system-design-notes:邮件附件如何存储?本地目录存储的目录结构设计详解
【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes
📬system-design-notes是经典系统设计面试书籍System Design Interview: An Insider's Guide的配套笔记仓库,用 28 个章节拆解了从缓存扩展到分布式邮件服务的真实系统设计。本章聚焦第 23 章 Distributed Email Service——设计一个类似 Gmail 的分布式邮件系统,其中「邮件附件如何存储」是一个极具代表性的问题:从小型邮件服务器上的本地目录存储(Maildir 目录结构设计),到大规模场景下附件分离到对象存储的演进,本文带你一次看懂。
一、传统邮件服务器:一封邮件就是一个文件
在设计分布式方案之前,先理解传统邮件服务器是怎么存邮件的。传统架构下,Alice 通过 SMTP 发送邮件,Outlook 服务器查询 DNS 的 MX 记录,把邮件投递给 Gmail 服务器,Bob 再通过 IMAP/POP 拉取邮件:

这种模式在用户量有限时运行良好。其核心存储策略非常朴素:每封邮件都是本地文件系统上的一个独立文件。当规模增长后,磁盘 I/O 会成为瓶颈,而且单台服务器一旦宕机,所有数据都面临丢失风险,无法满足高可用与可靠性要求。
二、Maildir 目录结构设计详解
那么,"每封邮件一个文件"具体是怎么组织目录的?业界事实标准是Maildir 方案。仓库中的这张图展示了它的典型结构:

以 Linux 为例,目录结构如下:
home/ ├── user1/ │ └── Maildir/ │ ├── cur/ # 当前(已读/处理中)的邮件 │ ├── new/ # 新到达、尚未处理的邮件 │ └── tmp/ # 正在写入中的临时邮件 └── user2/ └── Maildir/ ├── cur/ ├── new/ └── tmp/1️⃣ cur、new、tmp 三个子目录各自做什么?
| 目录 | 作用 |
|---|---|
| new | 新邮件先落在这里。邮箱程序轮询时从这里发现新邮件,读完后文件被移走 |
| cur | 表示"当前已交付"的邮件集合。客户端正在查看、已标记已读或保留的邮件都在这里 |
| tmp | 临时缓冲区。新邮件先完整写入 tmp,写完后通过一次原子 rename 移动到 new |
2️⃣ 为什么 tmp 目录是 Maildir 的精髓?
🔑原子性写入:邮件写入过程分两步——先写入tmp,再用rename()原子地移动到new。rename 在同一文件系统上是原子操作,因此任何读者看到的要么是完整的旧状态,要么是完整的邮件,永远不会读到"写了一半"的邮件。
🔑无锁设计:所有状态变化都通过"文件在目录间移动"来表达,目录里有什么文件就是什么状态。不需要文件锁,天然支持多进程并发读。
🔑故障恢复简单:如果服务器在写入时崩溃,tmp中残留的不完整文件在重启时直接清理即可,不会污染new和cur。
三、本地目录存储在大规模下的瓶颈
回到 README.md 中的估算:10 亿用户、每天 1000 万封邮件、约 20% 邮件带平均 500KB 的附件,一年附件存储量就达 1460PB——本地磁盘目录显然撑不住。本地目录存储的三大硬伤:
- ⚠️磁盘 I/O 瓶颈:海量小文件随机读写,IOPS 吃紧
- ⚠️单点故障:磁盘损坏或服务器宕机即数据丢失
- ⚠️无法水平扩展:本地目录天然绑定单台服务器
四、演进方向:附件元数据与文件本体分离
所以分布式邮件服务中,附件存储发生了关键转变——元数据进数据库,文件本体进对象存储(如 Amazon S3):

数据库中的附件表非常精简,只保存文件名和对象存储 URL的映射,而不是附件本体:

| 表 | 字段 | 说明 |
|---|---|---|
| emails_by_user | attachments:LIST<filename\|size> | 邮件行内只记录附件的文件名和大小 |
| attachments | email_id (TIMEUUID), filename (K), url | 文件名作为主键,url 指向对象存储 |
这样设计的好处:
- ✅ 读取邮件列表时不触碰大文件,列表页响应快
- ✅ 附件按需从对象存储拉取,可独立水平扩展
- ✅ 补充细节:邮件附件在网络传输时是base64 编码的,主流邮件服务通常限制在 25MB 以内
💡 进阶优化点(可作为系统设计加分项):去重。不同的用户多次发送同一个附件时,可以用内容哈希(如 SHA-256)判重,只存储一份,进一步节省对象存储成本。
五、小结:一张图看懂附件存储的演进
| 阶段 | 附件存储位置 | 代表 | 适合规模 |
|---|---|---|---|
| 传统方案 | 本地 Maildir 目录(new/cur/tmp) | 传统邮件服务器 | 单服务器、有限用户 |
| 分布式方案 | 元数据在数据库 + 本体在对象存储(S3) | Gmail 类系统 | 十亿级用户、PB 级存储 |
Maildir 的 cur/new/tmp 三目录结构是单机邮件存储的经典设计,理解它的原子写入和无锁思想;而附件与元数据分离、大文件下沉对象存储,则是任何大规模文件/附件类系统(网盘、云相册)通用的设计模式。想深入完整的设计推导(发送流程、收件流程、搜索、多数据中心容灾),可阅读 23. Distributed Email Service 章节 原文。
【免费下载链接】system-design-notesNotes of the book System Desgin Interview - An Insider's Guide项目地址: https://gitcode.com/GitHub_Trending/sy/system-design-notes
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考